You've already forked AstralRinth
forked from didirus/AstralRinth
Move charges to DB + fix subscription recurring payments (#971)
* Move charges to DB + fix subscription recurring payments * Finish most + pyro integration * Finish billing * Run prepare * Fix intervals * Fix clippy * Remove unused test
This commit is contained in:
4
.env
4
.env
@@ -103,4 +103,6 @@ FLAME_ANVIL_URL=none
|
|||||||
STRIPE_API_KEY=none
|
STRIPE_API_KEY=none
|
||||||
STRIPE_WEBHOOK_SECRET=none
|
STRIPE_WEBHOOK_SECRET=none
|
||||||
|
|
||||||
ADITUDE_API_KEY=none
|
ADITUDE_API_KEY=none
|
||||||
|
|
||||||
|
PYRO_API_KEY=none
|
||||||
82
.sqlx/query-285cdd452fff85480dde02119d224a6e422e4041deb6f640ab5159d55ba2789c.json
generated
Normal file
82
.sqlx/query-285cdd452fff85480dde02119d224a6e422e4041deb6f640ab5159d55ba2789c.json
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval\n FROM charges\n WHERE (status = 'open' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "price_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "amount",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "currency_code",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "due",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "last_attempt",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "charge_type",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "subscription_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "subscription_interval",
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Timestamptz"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "285cdd452fff85480dde02119d224a6e422e4041deb6f640ab5159d55ba2789c"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n id, user_id, price_id, interval, created, expires, last_charge, status\n FROM users_subscriptions\n WHERE expires < $1",
|
"query": "\n SELECT\n us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata\n FROM users_subscriptions us\n \n INNER JOIN charges c\n ON c.subscription_id = us.id\n AND (\n (c.status = 'cancelled' AND c.due < $1) OR\n (c.status = 'failed' AND c.last_attempt < $1 - INTERVAL '2 days')\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -30,18 +30,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"name": "expires",
|
"name": "status",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"name": "last_charge",
|
"name": "metadata",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Jsonb"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "status",
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -56,9 +51,8 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true
|
||||||
false
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "61a87b00baaba022ab32eedf177d5b9dc6d5b7568cf1df15fac6c9e85acfa448"
|
"hash": "3cbc34bc326595fc9d070494613fca57628eed279f720565fab55c8d10decd88"
|
||||||
}
|
}
|
||||||
82
.sqlx/query-457493bd11254ba1c9f81c47f15e8154053ae4e90e319d34a940fb73e33a69d4.json
generated
Normal file
82
.sqlx/query-457493bd11254ba1c9f81c47f15e8154053ae4e90e319d34a940fb73e33a69d4.json
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval\n FROM charges\n WHERE user_id = $1 ORDER BY due DESC",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "price_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "amount",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "currency_code",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "due",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "last_attempt",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "charge_type",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "subscription_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "subscription_interval",
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "457493bd11254ba1c9f81c47f15e8154053ae4e90e319d34a940fb73e33a69d4"
|
||||||
|
}
|
||||||
24
.sqlx/query-56d7b62fc05c77f228e46dbfe4eaca81b445a7f5a44e52a0526a1b57bd7a8c9d.json
generated
Normal file
24
.sqlx/query-56d7b62fc05c77f228e46dbfe4eaca81b445a7f5a44e52a0526a1b57bd7a8c9d.json
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO charges (id, user_id, price_id, amount, currency_code, charge_type, status, due, last_attempt, subscription_id, subscription_interval)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n ON CONFLICT (id)\n DO UPDATE\n SET status = EXCLUDED.status,\n last_attempt = EXCLUDED.last_attempt,\n due = EXCLUDED.due,\n subscription_id = EXCLUDED.subscription_id,\n subscription_interval = EXCLUDED.subscription_interval\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Varchar",
|
||||||
|
"Timestamptz",
|
||||||
|
"Timestamptz",
|
||||||
|
"Int8",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "56d7b62fc05c77f228e46dbfe4eaca81b445a7f5a44e52a0526a1b57bd7a8c9d"
|
||||||
|
}
|
||||||
82
.sqlx/query-86ee460c74f0052a4945ab4df9829b3b077930d8e9e09dca76fde8983413adc6.json
generated
Normal file
82
.sqlx/query-86ee460c74f0052a4945ab4df9829b3b077930d8e9e09dca76fde8983413adc6.json
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval\n FROM charges\n WHERE id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "price_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "amount",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "currency_code",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "due",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "last_attempt",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "charge_type",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "subscription_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "subscription_interval",
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "86ee460c74f0052a4945ab4df9829b3b077930d8e9e09dca76fde8983413adc6"
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n DELETE FROM users_subscriptions\n WHERE id = $1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "88d135700420321a3896f9262bb663df0ac672d465d78445e48f321fc47e09cb"
|
|
||||||
}
|
|
||||||
20
.sqlx/query-8bcf4589c429ab0abf2460f658fd91caafb733a5217b832ab9dcf7fde60d49dd.json
generated
Normal file
20
.sqlx/query-8bcf4589c429ab0abf2460f658fd91caafb733a5217b832ab9dcf7fde60d49dd.json
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO users_subscriptions (\n id, user_id, price_id, interval, created, status, metadata\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n ON CONFLICT (id)\n DO UPDATE\n SET interval = EXCLUDED.interval,\n status = EXCLUDED.status,\n price_id = EXCLUDED.price_id,\n metadata = EXCLUDED.metadata\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Int8",
|
||||||
|
"Text",
|
||||||
|
"Timestamptz",
|
||||||
|
"Varchar",
|
||||||
|
"Jsonb"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "8bcf4589c429ab0abf2460f658fd91caafb733a5217b832ab9dcf7fde60d49dd"
|
||||||
|
}
|
||||||
22
.sqlx/query-95b52480a3fc7257a95e1cbc0e05f13c7934e3019675c04d9d3f240eb590bdc4.json
generated
Normal file
22
.sqlx/query-95b52480a3fc7257a95e1cbc0e05f13c7934e3019675c04d9d3f240eb590bdc4.json
generated
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT EXISTS(SELECT 1 FROM charges WHERE id=$1)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "exists",
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "95b52480a3fc7257a95e1cbc0e05f13c7934e3019675c04d9d3f240eb590bdc4"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n id, user_id, price_id, interval, created, expires, last_charge, status\n FROM users_subscriptions\n WHERE id = ANY($1::bigint[])",
|
"query": "\n SELECT\n us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata\n FROM users_subscriptions us\n WHERE us.id = ANY($1::bigint[])",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -30,18 +30,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"name": "expires",
|
"name": "status",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"name": "last_charge",
|
"name": "metadata",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Jsonb"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "status",
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -56,9 +51,8 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true
|
||||||
false
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "07afad3b85ed64acbe9584570fdec92f923abf17439f0011e2b46797cec0ad97"
|
"hash": "a25ee30b6968dc98b66b1beac5124f39c64ad8815ff0ec0a98903fee0b4167c7"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n id, user_id, price_id, interval, created, expires, last_charge, status\n FROM users_subscriptions\n WHERE user_id = $1",
|
"query": "\n SELECT\n us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata\n FROM users_subscriptions us\n WHERE us.user_id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -30,18 +30,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
"name": "expires",
|
"name": "status",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"name": "last_charge",
|
"name": "metadata",
|
||||||
"type_info": "Timestamptz"
|
"type_info": "Jsonb"
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 7,
|
|
||||||
"name": "status",
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -56,9 +51,8 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true
|
||||||
false
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "d6d3c29ff2aa3b311a19225cefcd5b8844fbe5bedf44ffe24f31b12e5bc5f868"
|
"hash": "af1a10f0fa88c7893cff3a451fd890762fd7068cab7822a5b60545b44e6ba775"
|
||||||
}
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n DELETE FROM users_subscriptions\n WHERE id = $1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "b64651865cf9c1fbebed7f188da6566d53049176d72073c22a04b43adea18326"
|
|
||||||
}
|
|
||||||
14
.sqlx/query-bc2a2166718a2d2b23e57cde6b144e88f58fd2e1cc3e6da8d90708cbf242f761.json
generated
Normal file
14
.sqlx/query-bc2a2166718a2d2b23e57cde6b144e88f58fd2e1cc3e6da8d90708cbf242f761.json
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n DELETE FROM charges\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "bc2a2166718a2d2b23e57cde6b144e88f58fd2e1cc3e6da8d90708cbf242f761"
|
||||||
|
}
|
||||||
15
.sqlx/query-bd26a27ce80ca796ae19bc709c92800a0a43dfef4a37a5725403d33ccb20d908.json
generated
Normal file
15
.sqlx/query-bd26a27ce80ca796ae19bc709c92800a0a43dfef4a37a5725403d33ccb20d908.json
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE users\n SET badges = $1\n WHERE (id = $2)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "bd26a27ce80ca796ae19bc709c92800a0a43dfef4a37a5725403d33ccb20d908"
|
||||||
|
}
|
||||||
82
.sqlx/query-e68e27fcb3e85233be06e7435aaeb6b27d8dbe2ddaf211ba37a026eab3bb6926.json
generated
Normal file
82
.sqlx/query-e68e27fcb3e85233be06e7435aaeb6b27d8dbe2ddaf211ba37a026eab3bb6926.json
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "price_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "amount",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "currency_code",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
|
"name": "due",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 7,
|
||||||
|
"name": "last_attempt",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 8,
|
||||||
|
"name": "charge_type",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 9,
|
||||||
|
"name": "subscription_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 10,
|
||||||
|
"name": "subscription_interval",
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "e68e27fcb3e85233be06e7435aaeb6b27d8dbe2ddaf211ba37a026eab3bb6926"
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n INSERT INTO users_subscriptions (\n id, user_id, price_id, interval, created, expires, last_charge, status\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )\n ON CONFLICT (id)\n DO UPDATE\n SET interval = EXCLUDED.interval,\n expires = EXCLUDED.expires,\n last_charge = EXCLUDED.last_charge,\n status = EXCLUDED.status,\n price_id = EXCLUDED.price_id\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Int8",
|
|
||||||
"Int8",
|
|
||||||
"Text",
|
|
||||||
"Timestamptz",
|
|
||||||
"Timestamptz",
|
|
||||||
"Timestamptz",
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "e82ba8bafb4e45b8a8a100c639a9174f196a50cd74c9243ddd57d6f4f3d0b062"
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n UPDATE users\n SET badges = $1\n WHERE (id = $2)\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "f643ba5f92e5f76cc2f9d2016f52dc03483c1e578dd5ea39119fcf5ad58d8250"
|
|
||||||
}
|
|
||||||
17
migrations/20240923163452_charges-fix.sql
Normal file
17
migrations/20240923163452_charges-fix.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE charges (
|
||||||
|
id bigint PRIMARY KEY,
|
||||||
|
user_id bigint REFERENCES users NOT NULL,
|
||||||
|
price_id bigint REFERENCES products_prices NOT NULL,
|
||||||
|
amount bigint NOT NULL,
|
||||||
|
currency_code text NOT NULL,
|
||||||
|
status varchar(255) NOT NULL,
|
||||||
|
due timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
last_attempt timestamptz NULL,
|
||||||
|
charge_type text NOT NULL,
|
||||||
|
subscription_id bigint NULL,
|
||||||
|
subscription_interval text NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users_subscriptions DROP COLUMN last_charge;
|
||||||
|
ALTER TABLE users_subscriptions ADD COLUMN metadata jsonb NULL;
|
||||||
|
ALTER TABLE users_subscriptions DROP COLUMN expires;
|
||||||
181
src/database/models/charge_item.rs
Normal file
181
src/database/models/charge_item.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use crate::database::models::{
|
||||||
|
ChargeId, DatabaseError, ProductPriceId, UserId, UserSubscriptionId,
|
||||||
|
};
|
||||||
|
use crate::models::billing::{ChargeStatus, ChargeType, PriceDuration};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
pub struct ChargeItem {
|
||||||
|
pub id: ChargeId,
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub price_id: ProductPriceId,
|
||||||
|
pub amount: i64,
|
||||||
|
pub currency_code: String,
|
||||||
|
pub status: ChargeStatus,
|
||||||
|
pub due: DateTime<Utc>,
|
||||||
|
pub last_attempt: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
pub type_: ChargeType,
|
||||||
|
pub subscription_id: Option<UserSubscriptionId>,
|
||||||
|
pub subscription_interval: Option<PriceDuration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ChargeResult {
|
||||||
|
id: i64,
|
||||||
|
user_id: i64,
|
||||||
|
price_id: i64,
|
||||||
|
amount: i64,
|
||||||
|
currency_code: String,
|
||||||
|
status: String,
|
||||||
|
due: DateTime<Utc>,
|
||||||
|
last_attempt: Option<DateTime<Utc>>,
|
||||||
|
charge_type: String,
|
||||||
|
subscription_id: Option<i64>,
|
||||||
|
subscription_interval: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<ChargeResult> for ChargeItem {
|
||||||
|
type Error = serde_json::Error;
|
||||||
|
|
||||||
|
fn try_from(r: ChargeResult) -> Result<Self, Self::Error> {
|
||||||
|
Ok(ChargeItem {
|
||||||
|
id: ChargeId(r.id),
|
||||||
|
user_id: UserId(r.user_id),
|
||||||
|
price_id: ProductPriceId(r.price_id),
|
||||||
|
amount: r.amount,
|
||||||
|
currency_code: r.currency_code,
|
||||||
|
status: ChargeStatus::from_string(&r.status),
|
||||||
|
due: r.due,
|
||||||
|
last_attempt: r.last_attempt,
|
||||||
|
type_: ChargeType::from_string(&r.charge_type),
|
||||||
|
subscription_id: r.subscription_id.map(UserSubscriptionId),
|
||||||
|
subscription_interval: r
|
||||||
|
.subscription_interval
|
||||||
|
.map(|x| PriceDuration::from_string(&x)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! select_charges_with_predicate {
|
||||||
|
($predicate:tt, $param:ident) => {
|
||||||
|
sqlx::query_as!(
|
||||||
|
ChargeResult,
|
||||||
|
r#"
|
||||||
|
SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval
|
||||||
|
FROM charges
|
||||||
|
"#
|
||||||
|
+ $predicate,
|
||||||
|
$param
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChargeItem {
|
||||||
|
pub async fn upsert(
|
||||||
|
&self,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<ChargeId, DatabaseError> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO charges (id, user_id, price_id, amount, currency_code, charge_type, status, due, last_attempt, subscription_id, subscription_interval)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
|
ON CONFLICT (id)
|
||||||
|
DO UPDATE
|
||||||
|
SET status = EXCLUDED.status,
|
||||||
|
last_attempt = EXCLUDED.last_attempt,
|
||||||
|
due = EXCLUDED.due,
|
||||||
|
subscription_id = EXCLUDED.subscription_id,
|
||||||
|
subscription_interval = EXCLUDED.subscription_interval
|
||||||
|
"#,
|
||||||
|
self.id.0,
|
||||||
|
self.user_id.0,
|
||||||
|
self.price_id.0,
|
||||||
|
self.amount,
|
||||||
|
self.currency_code,
|
||||||
|
self.type_.as_str(),
|
||||||
|
self.status.as_str(),
|
||||||
|
self.due,
|
||||||
|
self.last_attempt,
|
||||||
|
self.subscription_id.map(|x| x.0),
|
||||||
|
self.subscription_interval.map(|x| x.as_str()),
|
||||||
|
)
|
||||||
|
.execute(&mut **transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
id: ChargeId,
|
||||||
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
|
) -> Result<Option<ChargeItem>, DatabaseError> {
|
||||||
|
let id = id.0;
|
||||||
|
let res = select_charges_with_predicate!("WHERE id = $1", id)
|
||||||
|
.fetch_optional(exec)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res.and_then(|r| r.try_into().ok()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_from_user(
|
||||||
|
user_id: UserId,
|
||||||
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
|
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||||
|
let user_id = user_id.0;
|
||||||
|
let res = select_charges_with_predicate!("WHERE user_id = $1 ORDER BY due DESC", user_id)
|
||||||
|
.fetch_all(exec)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.try_into())
|
||||||
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_open_subscription(
|
||||||
|
user_subscription_id: UserSubscriptionId,
|
||||||
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
|
) -> Result<Option<ChargeItem>, DatabaseError> {
|
||||||
|
let user_subscription_id = user_subscription_id.0;
|
||||||
|
let res = select_charges_with_predicate!(
|
||||||
|
"WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
|
||||||
|
user_subscription_id
|
||||||
|
)
|
||||||
|
.fetch_optional(exec)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res.and_then(|r| r.try_into().ok()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_chargeable(
|
||||||
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
|
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||||
|
let now = Utc::now();
|
||||||
|
|
||||||
|
let res = select_charges_with_predicate!("WHERE (status = 'open' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now)
|
||||||
|
.fetch_all(exec)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.try_into())
|
||||||
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(
|
||||||
|
id: ChargeId,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM charges
|
||||||
|
WHERE id = $1
|
||||||
|
",
|
||||||
|
id.0 as i64
|
||||||
|
)
|
||||||
|
.execute(&mut **transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -256,6 +256,14 @@ generate_ids!(
|
|||||||
UserSubscriptionId
|
UserSubscriptionId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
generate_ids!(
|
||||||
|
pub generate_charge_id,
|
||||||
|
ChargeId,
|
||||||
|
8,
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM charges WHERE id=$1)",
|
||||||
|
ChargeId
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Type, Hash, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Type, Hash, Serialize, Deserialize)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct UserId(pub i64);
|
pub struct UserId(pub i64);
|
||||||
@@ -386,6 +394,10 @@ pub struct ProductPriceId(pub i64);
|
|||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct UserSubscriptionId(pub i64);
|
pub struct UserSubscriptionId(pub i64);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Type, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||||
|
#[sqlx(transparent)]
|
||||||
|
pub struct ChargeId(pub i64);
|
||||||
|
|
||||||
use crate::models::ids;
|
use crate::models::ids;
|
||||||
|
|
||||||
impl From<ids::ProjectId> for ProjectId {
|
impl From<ids::ProjectId> for ProjectId {
|
||||||
@@ -571,3 +583,14 @@ impl From<UserSubscriptionId> for ids::UserSubscriptionId {
|
|||||||
ids::UserSubscriptionId(id.0 as u64)
|
ids::UserSubscriptionId(id.0 as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ids::ChargeId> for ChargeId {
|
||||||
|
fn from(id: ids::ChargeId) -> Self {
|
||||||
|
ChargeId(id.0 as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<ChargeId> for ids::ChargeId {
|
||||||
|
fn from(id: ChargeId) -> Self {
|
||||||
|
ids::ChargeId(id.0 as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub mod categories;
|
pub mod categories;
|
||||||
|
pub mod charge_item;
|
||||||
pub mod collection_item;
|
pub mod collection_item;
|
||||||
pub mod flow_item;
|
pub mod flow_item;
|
||||||
pub mod ids;
|
pub mod ids;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::database::models::{DatabaseError, ProductPriceId, UserId, UserSubscriptionId};
|
use crate::database::models::{DatabaseError, ProductPriceId, UserId, UserSubscriptionId};
|
||||||
use crate::models::billing::{PriceDuration, SubscriptionStatus};
|
use crate::models::billing::{PriceDuration, SubscriptionMetadata, SubscriptionStatus};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
pub struct UserSubscriptionItem {
|
pub struct UserSubscriptionItem {
|
||||||
pub id: UserSubscriptionId,
|
pub id: UserSubscriptionId,
|
||||||
@@ -9,9 +10,8 @@ pub struct UserSubscriptionItem {
|
|||||||
pub price_id: ProductPriceId,
|
pub price_id: ProductPriceId,
|
||||||
pub interval: PriceDuration,
|
pub interval: PriceDuration,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub expires: DateTime<Utc>,
|
|
||||||
pub last_charge: Option<DateTime<Utc>>,
|
|
||||||
pub status: SubscriptionStatus,
|
pub status: SubscriptionStatus,
|
||||||
|
pub metadata: Option<SubscriptionMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserSubscriptionResult {
|
struct UserSubscriptionResult {
|
||||||
@@ -20,9 +20,8 @@ struct UserSubscriptionResult {
|
|||||||
price_id: i64,
|
price_id: i64,
|
||||||
interval: String,
|
interval: String,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub expires: DateTime<Utc>,
|
|
||||||
pub last_charge: Option<DateTime<Utc>>,
|
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
pub metadata: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! select_user_subscriptions_with_predicate {
|
macro_rules! select_user_subscriptions_with_predicate {
|
||||||
@@ -31,8 +30,8 @@ macro_rules! select_user_subscriptions_with_predicate {
|
|||||||
UserSubscriptionResult,
|
UserSubscriptionResult,
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id, user_id, price_id, interval, created, expires, last_charge, status
|
us.id, us.user_id, us.price_id, us.interval, us.created, us.status, us.metadata
|
||||||
FROM users_subscriptions
|
FROM users_subscriptions us
|
||||||
"#
|
"#
|
||||||
+ $predicate,
|
+ $predicate,
|
||||||
$param
|
$param
|
||||||
@@ -40,18 +39,19 @@ macro_rules! select_user_subscriptions_with_predicate {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UserSubscriptionResult> for UserSubscriptionItem {
|
impl TryFrom<UserSubscriptionResult> for UserSubscriptionItem {
|
||||||
fn from(r: UserSubscriptionResult) -> Self {
|
type Error = serde_json::Error;
|
||||||
UserSubscriptionItem {
|
|
||||||
|
fn try_from(r: UserSubscriptionResult) -> Result<Self, Self::Error> {
|
||||||
|
Ok(UserSubscriptionItem {
|
||||||
id: UserSubscriptionId(r.id),
|
id: UserSubscriptionId(r.id),
|
||||||
user_id: UserId(r.user_id),
|
user_id: UserId(r.user_id),
|
||||||
price_id: ProductPriceId(r.price_id),
|
price_id: ProductPriceId(r.price_id),
|
||||||
interval: PriceDuration::from_string(&r.interval),
|
interval: PriceDuration::from_string(&r.interval),
|
||||||
created: r.created,
|
created: r.created,
|
||||||
expires: r.expires,
|
|
||||||
last_charge: r.last_charge,
|
|
||||||
status: SubscriptionStatus::from_string(&r.status),
|
status: SubscriptionStatus::from_string(&r.status),
|
||||||
}
|
metadata: serde_json::from_value(r.metadata)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +70,14 @@ impl UserSubscriptionItem {
|
|||||||
let ids = ids.iter().map(|id| id.0).collect_vec();
|
let ids = ids.iter().map(|id| id.0).collect_vec();
|
||||||
let ids_ref: &[i64] = &ids;
|
let ids_ref: &[i64] = &ids;
|
||||||
let results =
|
let results =
|
||||||
select_user_subscriptions_with_predicate!("WHERE id = ANY($1::bigint[])", ids_ref)
|
select_user_subscriptions_with_predicate!("WHERE us.id = ANY($1::bigint[])", ids_ref)
|
||||||
.fetch_all(exec)
|
.fetch_all(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(results.into_iter().map(|r| r.into()).collect())
|
Ok(results
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.try_into())
|
||||||
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_user(
|
pub async fn get_all_user(
|
||||||
@@ -82,22 +85,38 @@ impl UserSubscriptionItem {
|
|||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
|
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
|
||||||
let user_id = user_id.0;
|
let user_id = user_id.0;
|
||||||
let results = select_user_subscriptions_with_predicate!("WHERE user_id = $1", user_id)
|
let results = select_user_subscriptions_with_predicate!("WHERE us.user_id = $1", user_id)
|
||||||
.fetch_all(exec)
|
.fetch_all(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(results.into_iter().map(|r| r.into()).collect())
|
Ok(results
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.try_into())
|
||||||
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_all_expired(
|
pub async fn get_all_unprovision(
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
|
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let results = select_user_subscriptions_with_predicate!("WHERE expires < $1", now)
|
let results = select_user_subscriptions_with_predicate!(
|
||||||
.fetch_all(exec)
|
"
|
||||||
.await?;
|
INNER JOIN charges c
|
||||||
|
ON c.subscription_id = us.id
|
||||||
|
AND (
|
||||||
|
(c.status = 'cancelled' AND c.due < $1) OR
|
||||||
|
(c.status = 'failed' AND c.last_attempt < $1 - INTERVAL '2 days')
|
||||||
|
)
|
||||||
|
",
|
||||||
|
now
|
||||||
|
)
|
||||||
|
.fetch_all(exec)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(results.into_iter().map(|r| r.into()).collect())
|
Ok(results
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.try_into())
|
||||||
|
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert(
|
pub async fn upsert(
|
||||||
@@ -107,44 +126,25 @@ impl UserSubscriptionItem {
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO users_subscriptions (
|
INSERT INTO users_subscriptions (
|
||||||
id, user_id, price_id, interval, created, expires, last_charge, status
|
id, user_id, price_id, interval, created, status, metadata
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8
|
$1, $2, $3, $4, $5, $6, $7
|
||||||
)
|
)
|
||||||
ON CONFLICT (id)
|
ON CONFLICT (id)
|
||||||
DO UPDATE
|
DO UPDATE
|
||||||
SET interval = EXCLUDED.interval,
|
SET interval = EXCLUDED.interval,
|
||||||
expires = EXCLUDED.expires,
|
|
||||||
last_charge = EXCLUDED.last_charge,
|
|
||||||
status = EXCLUDED.status,
|
status = EXCLUDED.status,
|
||||||
price_id = EXCLUDED.price_id
|
price_id = EXCLUDED.price_id,
|
||||||
|
metadata = EXCLUDED.metadata
|
||||||
",
|
",
|
||||||
self.id.0,
|
self.id.0,
|
||||||
self.user_id.0,
|
self.user_id.0,
|
||||||
self.price_id.0,
|
self.price_id.0,
|
||||||
self.interval.as_str(),
|
self.interval.as_str(),
|
||||||
self.created,
|
self.created,
|
||||||
self.expires,
|
|
||||||
self.last_charge,
|
|
||||||
self.status.as_str(),
|
self.status.as_str(),
|
||||||
)
|
serde_json::to_value(&self.metadata)?,
|
||||||
.execute(&mut **transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove(
|
|
||||||
id: UserSubscriptionId,
|
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
||||||
) -> Result<(), DatabaseError> {
|
|
||||||
sqlx::query!(
|
|
||||||
"
|
|
||||||
DELETE FROM users_subscriptions
|
|
||||||
WHERE id = $1
|
|
||||||
",
|
|
||||||
id.0 as i64
|
|
||||||
)
|
)
|
||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub async fn delete_file_version(
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
) -> Result<DeleteFileData, FileHostingError> {
|
) -> Result<DeleteFileData, FileHostingError> {
|
||||||
let response = reqwest::Client::new()
|
let response = reqwest::Client::new()
|
||||||
.post(&format!(
|
.post(format!(
|
||||||
"{}/b2api/v2/b2_delete_file_version",
|
"{}/b2api/v2/b2_delete_file_version",
|
||||||
authorization_data.api_url
|
authorization_data.api_url
|
||||||
))
|
))
|
||||||
|
|||||||
29
src/lib.rs
29
src/lib.rs
@@ -259,15 +259,24 @@ pub fn app_setup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stripe_client = stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
let stripe_client = stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
||||||
// {
|
{
|
||||||
// let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
// let redis_ref = redis_pool.clone();
|
let redis_ref = redis_pool.clone();
|
||||||
// let stripe_client_ref = stripe_client.clone();
|
let stripe_client_ref = stripe_client.clone();
|
||||||
//
|
|
||||||
// actix_rt::spawn(async move {
|
actix_rt::spawn(async move {
|
||||||
// routes::internal::billing::task(stripe_client_ref, pool_ref, redis_ref).await;
|
routes::internal::billing::task(stripe_client_ref, pool_ref, redis_ref).await;
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let pool_ref = pool.clone();
|
||||||
|
let redis_ref = redis_pool.clone();
|
||||||
|
|
||||||
|
actix_rt::spawn(async move {
|
||||||
|
routes::internal::billing::subscription_task(pool_ref, redis_ref).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let ip_salt = Pepper {
|
let ip_salt = Pepper {
|
||||||
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
||||||
@@ -456,5 +465,7 @@ pub fn check_env_vars() -> bool {
|
|||||||
|
|
||||||
failed |= check_var::<u64>("ADITUDE_API_KEY");
|
failed |= check_var::<u64>("ADITUDE_API_KEY");
|
||||||
|
|
||||||
|
failed |= check_var::<String>("PYRO_API_KEY");
|
||||||
|
|
||||||
failed
|
failed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub struct Product {
|
|||||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
pub enum ProductMetadata {
|
pub enum ProductMetadata {
|
||||||
Midas,
|
Midas,
|
||||||
|
Pyro { ram: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
@@ -55,6 +56,13 @@ pub enum PriceDuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PriceDuration {
|
impl PriceDuration {
|
||||||
|
pub fn duration(&self) -> chrono::Duration {
|
||||||
|
match self {
|
||||||
|
PriceDuration::Monthly => chrono::Duration::days(30),
|
||||||
|
PriceDuration::Yearly => chrono::Duration::days(365),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_string(string: &str) -> PriceDuration {
|
pub fn from_string(string: &str) -> PriceDuration {
|
||||||
match string {
|
match string {
|
||||||
"monthly" => PriceDuration::Monthly,
|
"monthly" => PriceDuration::Monthly,
|
||||||
@@ -84,8 +92,7 @@ pub struct UserSubscription {
|
|||||||
pub interval: PriceDuration,
|
pub interval: PriceDuration,
|
||||||
pub status: SubscriptionStatus,
|
pub status: SubscriptionStatus,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
pub expires: DateTime<Utc>,
|
pub metadata: Option<SubscriptionMetadata>,
|
||||||
pub last_charge: Option<DateTime<Utc>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::database::models::user_subscription_item::UserSubscriptionItem>
|
impl From<crate::database::models::user_subscription_item::UserSubscriptionItem>
|
||||||
@@ -99,38 +106,119 @@ impl From<crate::database::models::user_subscription_item::UserSubscriptionItem>
|
|||||||
interval: x.interval,
|
interval: x.interval,
|
||||||
status: x.status,
|
status: x.status,
|
||||||
created: x.created,
|
created: x.created,
|
||||||
expires: x.expires,
|
metadata: x.metadata,
|
||||||
last_charge: x.last_charge,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum SubscriptionStatus {
|
pub enum SubscriptionStatus {
|
||||||
Active,
|
Provisioned,
|
||||||
PaymentProcessing,
|
Unprovisioned,
|
||||||
PaymentFailed,
|
|
||||||
Cancelled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubscriptionStatus {
|
impl SubscriptionStatus {
|
||||||
pub fn from_string(string: &str) -> SubscriptionStatus {
|
pub fn from_string(string: &str) -> SubscriptionStatus {
|
||||||
match string {
|
match string {
|
||||||
"active" => SubscriptionStatus::Active,
|
"provisioned" => SubscriptionStatus::Provisioned,
|
||||||
"payment-processing" => SubscriptionStatus::PaymentProcessing,
|
"unprovisioned" => SubscriptionStatus::Unprovisioned,
|
||||||
"payment-failed" => SubscriptionStatus::PaymentFailed,
|
_ => SubscriptionStatus::Provisioned,
|
||||||
"cancelled" => SubscriptionStatus::Cancelled,
|
|
||||||
_ => SubscriptionStatus::Cancelled,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
SubscriptionStatus::Active => "active",
|
SubscriptionStatus::Provisioned => "provisioned",
|
||||||
SubscriptionStatus::PaymentProcessing => "payment-processing",
|
SubscriptionStatus::Unprovisioned => "unprovisioned",
|
||||||
SubscriptionStatus::PaymentFailed => "payment-failed",
|
}
|
||||||
SubscriptionStatus::Cancelled => "cancelled",
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
|
pub enum SubscriptionMetadata {
|
||||||
|
Pyro { id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(from = "Base62Id")]
|
||||||
|
#[serde(into = "Base62Id")]
|
||||||
|
pub struct ChargeId(pub u64);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Charge {
|
||||||
|
pub id: ChargeId,
|
||||||
|
pub user_id: UserId,
|
||||||
|
pub price_id: ProductPriceId,
|
||||||
|
pub amount: i64,
|
||||||
|
pub currency_code: String,
|
||||||
|
pub status: ChargeStatus,
|
||||||
|
pub due: DateTime<Utc>,
|
||||||
|
pub last_attempt: Option<DateTime<Utc>>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub type_: ChargeType,
|
||||||
|
pub subscription_id: Option<UserSubscriptionId>,
|
||||||
|
pub subscription_interval: Option<PriceDuration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
|
pub enum ChargeType {
|
||||||
|
OneTime,
|
||||||
|
Subscription,
|
||||||
|
Proration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChargeType {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ChargeType::OneTime => "one-time",
|
||||||
|
ChargeType::Subscription { .. } => "subscription",
|
||||||
|
ChargeType::Proration { .. } => "proration",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_string(string: &str) -> ChargeType {
|
||||||
|
match string {
|
||||||
|
"one-time" => ChargeType::OneTime,
|
||||||
|
"subscription" => ChargeType::Subscription,
|
||||||
|
"proration" => ChargeType::Proration,
|
||||||
|
_ => ChargeType::OneTime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum ChargeStatus {
|
||||||
|
// Open charges are for the next billing interval
|
||||||
|
Open,
|
||||||
|
Processing,
|
||||||
|
Succeeded,
|
||||||
|
Failed,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChargeStatus {
|
||||||
|
pub fn from_string(string: &str) -> ChargeStatus {
|
||||||
|
match string {
|
||||||
|
"processing" => ChargeStatus::Processing,
|
||||||
|
"succeeded" => ChargeStatus::Succeeded,
|
||||||
|
"failed" => ChargeStatus::Failed,
|
||||||
|
"open" => ChargeStatus::Open,
|
||||||
|
"cancelled" => ChargeStatus::Cancelled,
|
||||||
|
_ => ChargeStatus::Failed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ChargeStatus::Processing => "processing",
|
||||||
|
ChargeStatus::Succeeded => "succeeded",
|
||||||
|
ChargeStatus::Failed => "failed",
|
||||||
|
ChargeStatus::Open => "open",
|
||||||
|
ChargeStatus::Cancelled => "cancelled",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ pub use super::teams::TeamId;
|
|||||||
pub use super::threads::ThreadId;
|
pub use super::threads::ThreadId;
|
||||||
pub use super::threads::ThreadMessageId;
|
pub use super::threads::ThreadMessageId;
|
||||||
pub use super::users::UserId;
|
pub use super::users::UserId;
|
||||||
pub use crate::models::billing::UserSubscriptionId;
|
pub use crate::models::billing::{ChargeId, ProductId, ProductPriceId, UserSubscriptionId};
|
||||||
pub use crate::models::v3::billing::{ProductId, ProductPriceId};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Generates a random 64 bit integer that is exactly `n` characters
|
/// Generates a random 64 bit integer that is exactly `n` characters
|
||||||
@@ -137,6 +136,7 @@ base62_id_impl!(PayoutId, PayoutId);
|
|||||||
base62_id_impl!(ProductId, ProductId);
|
base62_id_impl!(ProductId, ProductId);
|
||||||
base62_id_impl!(ProductPriceId, ProductPriceId);
|
base62_id_impl!(ProductPriceId, ProductPriceId);
|
||||||
base62_id_impl!(UserSubscriptionId, UserSubscriptionId);
|
base62_id_impl!(UserSubscriptionId, UserSubscriptionId);
|
||||||
|
base62_id_impl!(ChargeId, ChargeId);
|
||||||
|
|
||||||
pub mod base62_impl {
|
pub mod base62_impl {
|
||||||
use serde::de::{self, Deserializer, Visitor};
|
use serde::de::{self, Deserializer, Visitor};
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ impl PayoutsQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let credential: PaypalCredential = client
|
let credential: PaypalCredential = client
|
||||||
.post(&format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
|
.post(format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Accept-Language", "en_US")
|
.header("Accept-Language", "en_US")
|
||||||
.header("Authorization", formatted_key)
|
.header("Authorization", formatted_key)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -510,7 +510,7 @@ impl AuthProvider {
|
|||||||
map.insert("grant_type", "authorization_code");
|
map.insert("grant_type", "authorization_code");
|
||||||
|
|
||||||
let token: AccessToken = reqwest::Client::new()
|
let token: AccessToken = reqwest::Client::new()
|
||||||
.post(&format!("{api_url}oauth2/token"))
|
.post(format!("{api_url}oauth2/token"))
|
||||||
.header(reqwest::header::ACCEPT, "application/json")
|
.header(reqwest::header::ACCEPT, "application/json")
|
||||||
.header(
|
.header(
|
||||||
AUTHORIZATION,
|
AUTHORIZATION,
|
||||||
@@ -766,7 +766,7 @@ impl AuthProvider {
|
|||||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
||||||
|
|
||||||
let paypal_user: PayPalUser = reqwest::Client::new()
|
let paypal_user: PayPalUser = reqwest::Client::new()
|
||||||
.get(&format!(
|
.get(format!(
|
||||||
"{api_url}identity/openidconnect/userinfo?schema=openid"
|
"{api_url}identity/openidconnect/userinfo?schema=openid"
|
||||||
))
|
))
|
||||||
.header(reqwest::header::USER_AGENT, "Modrinth")
|
.header(reqwest::header::USER_AGENT, "Modrinth")
|
||||||
@@ -1393,7 +1393,7 @@ pub async fn sign_up_beehiiv(email: &str) -> Result<(), AuthenticationError> {
|
|||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
client
|
client
|
||||||
.post(&format!(
|
.post(format!(
|
||||||
"https://api.beehiiv.com/v2/publications/{id}/subscriptions"
|
"https://api.beehiiv.com/v2/publications/{id}/subscriptions"
|
||||||
))
|
))
|
||||||
.header(AUTHORIZATION, format!("Bearer {}", api_key))
|
.header(AUTHORIZATION, format!("Bearer {}", api_key))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub async fn export(
|
|||||||
&req,
|
&req,
|
||||||
&**pool,
|
&**pool,
|
||||||
&redis,
|
&redis,
|
||||||
&*session_queue,
|
&session_queue,
|
||||||
Some(&[Scopes::SESSION_ACCESS]),
|
Some(&[Scopes::SESSION_ACCESS]),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
@@ -34,19 +34,19 @@ pub async fn export(
|
|||||||
crate::database::models::Collection::get_many(&collection_ids, &**pool, &redis)
|
crate::database::models::Collection::get_many(&collection_ids, &**pool, &redis)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::collections::Collection::from(x))
|
.map(crate::models::collections::Collection::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let follows = crate::database::models::User::get_follows(user_id, &**pool)
|
let follows = crate::database::models::User::get_follows(user_id, &**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::ids::ProjectId::from(x))
|
.map(crate::models::ids::ProjectId::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let projects = crate::database::models::User::get_projects(user_id, &**pool, &redis)
|
let projects = crate::database::models::User::get_projects(user_id, &**pool, &redis)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::ids::ProjectId::from(x))
|
.map(crate::models::ids::ProjectId::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let org_ids = crate::database::models::User::get_organizations(user_id, &**pool).await?;
|
let org_ids = crate::database::models::User::get_organizations(user_id, &**pool).await?;
|
||||||
@@ -64,7 +64,7 @@ pub async fn export(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::notifications::Notification::from(x))
|
.map(crate::models::notifications::Notification::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let oauth_clients =
|
let oauth_clients =
|
||||||
@@ -73,7 +73,7 @@ pub async fn export(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::oauth_clients::OAuthClient::from(x))
|
.map(crate::models::oauth_clients::OAuthClient::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let oauth_authorizations = crate::database::models::oauth_client_authorization_item::OAuthClientAuthorization::get_all_for_user(
|
let oauth_authorizations = crate::database::models::oauth_client_authorization_item::OAuthClientAuthorization::get_all_for_user(
|
||||||
@@ -81,7 +81,7 @@ pub async fn export(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::oauth_clients::OAuthClientAuthorization::from(x))
|
.map(crate::models::oauth_clients::OAuthClientAuthorization::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let pat_ids = crate::database::models::pat_item::PersonalAccessToken::get_user_pats(
|
let pat_ids = crate::database::models::pat_item::PersonalAccessToken::get_user_pats(
|
||||||
@@ -102,7 +102,7 @@ pub async fn export(
|
|||||||
let payouts = crate::database::models::payout_item::Payout::get_many(&payout_ids, &**pool)
|
let payouts = crate::database::models::payout_item::Payout::get_many(&payout_ids, &**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::payouts::Payout::from(x))
|
.map(crate::models::payouts::Payout::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let report_ids =
|
let report_ids =
|
||||||
@@ -110,7 +110,7 @@ pub async fn export(
|
|||||||
let reports = crate::database::models::report_item::Report::get_many(&report_ids, &**pool)
|
let reports = crate::database::models::report_item::Report::get_many(&report_ids, &**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::reports::Report::from(x))
|
.map(crate::models::reports::Report::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let message_ids = sqlx::query!(
|
let message_ids = sqlx::query!(
|
||||||
@@ -146,7 +146,7 @@ pub async fn export(
|
|||||||
crate::database::models::image_item::Image::get_many(&uploaded_images_ids, &**pool, &redis)
|
crate::database::models::image_item::Image::get_many(&uploaded_images_ids, &**pool, &redis)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::images::Image::from(x))
|
.map(crate::models::images::Image::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let subscriptions =
|
let subscriptions =
|
||||||
@@ -155,7 +155,7 @@ pub async fn export(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| crate::models::billing::UserSubscription::from(x))
|
.map(crate::models::billing::UserSubscription::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||||
|
|||||||
@@ -106,14 +106,11 @@ pub async fn version_create(
|
|||||||
|
|
||||||
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply singleplayer, etc.
|
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply singleplayer, etc.
|
||||||
let loaders = match v3::tags::loader_list(client.clone(), redis.clone()).await {
|
let loaders = match v3::tags::loader_list(client.clone(), redis.clone()).await {
|
||||||
Ok(loader_response) => match v2_reroute::extract_ok_json::<
|
Ok(loader_response) => {
|
||||||
Vec<v3::tags::LoaderData>,
|
(v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(loader_response)
|
||||||
>(loader_response)
|
.await)
|
||||||
.await
|
.unwrap_or_default()
|
||||||
{
|
}
|
||||||
Ok(loaders) => loaders,
|
|
||||||
Err(_) => vec![],
|
|
||||||
},
|
|
||||||
Err(_) => vec![],
|
Err(_) => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ pub async fn create_payout(
|
|||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let balance = get_user_balance(user.id.into(), &**pool).await?;
|
let balance = get_user_balance(user.id, &pool).await?;
|
||||||
if balance.available < body.amount || body.amount < Decimal::ZERO {
|
if balance.available < body.amount || body.amount < Decimal::ZERO {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You do not have enough funds to make this payout!".to_string(),
|
"You do not have enough funds to make this payout!".to_string(),
|
||||||
@@ -734,7 +734,7 @@ pub async fn get_balance(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
let balance = get_user_balance(user.id.into(), &**pool).await?;
|
let balance = get_user_balance(user.id.into(), &pool).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(balance))
|
Ok(HttpResponse::Ok().json(balance))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,94 +579,6 @@ pub async fn test_bulk_edit_links() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn delete_project_with_report() {
|
|
||||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
|
||||||
let api = &test_env.api;
|
|
||||||
let alpha_project_id: &str = &test_env.dummy.project_alpha.project_id;
|
|
||||||
let beta_project_id: &str = &test_env.dummy.project_beta.project_id;
|
|
||||||
|
|
||||||
// Create a report for the project
|
|
||||||
let resp = api
|
|
||||||
.create_report(
|
|
||||||
"copyright",
|
|
||||||
alpha_project_id,
|
|
||||||
CommonItemType::Project,
|
|
||||||
"Hey! This is my project, copied without permission!",
|
|
||||||
ENEMY_USER_PAT, // Enemy makes a report
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_status!(&resp, StatusCode::OK);
|
|
||||||
let value = test::read_body_json::<serde_json::Value, _>(resp).await;
|
|
||||||
let alpha_report_id = value["id"].as_str().unwrap();
|
|
||||||
|
|
||||||
// Confirm existence
|
|
||||||
let resp = api
|
|
||||||
.get_report(
|
|
||||||
alpha_report_id,
|
|
||||||
ENEMY_USER_PAT, // Enemy makes a report
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_status!(&resp, StatusCode::OK);
|
|
||||||
|
|
||||||
// Do the same for beta
|
|
||||||
let resp = api
|
|
||||||
.create_report(
|
|
||||||
"copyright",
|
|
||||||
beta_project_id,
|
|
||||||
CommonItemType::Project,
|
|
||||||
"Hey! This is my project, copied without permission!",
|
|
||||||
ENEMY_USER_PAT, // Enemy makes a report
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_status!(&resp, StatusCode::OK);
|
|
||||||
let value = test::read_body_json::<serde_json::Value, _>(resp).await;
|
|
||||||
let beta_report_id = value["id"].as_str().unwrap();
|
|
||||||
|
|
||||||
// Delete the project
|
|
||||||
let resp = api.remove_project(alpha_project_id, USER_USER_PAT).await;
|
|
||||||
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
||||||
|
|
||||||
// Confirm that the project is gone from the cache
|
|
||||||
let mut redis_pool = test_env.db.redis_pool.connect().await.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
redis_pool
|
|
||||||
.get(PROJECTS_SLUGS_NAMESPACE, "demo")
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.and_then(|x| x.parse::<i64>().ok()),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
redis_pool
|
|
||||||
.get(PROJECTS_SLUGS_NAMESPACE, alpha_project_id)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.and_then(|x| x.parse::<i64>().ok()),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
|
|
||||||
// Report for alpha no longer exists
|
|
||||||
let resp = api
|
|
||||||
.get_report(
|
|
||||||
alpha_report_id,
|
|
||||||
ENEMY_USER_PAT, // Enemy makes a report
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_status!(&resp, StatusCode::NOT_FOUND);
|
|
||||||
|
|
||||||
// Confirm that report for beta still exists
|
|
||||||
let resp = api
|
|
||||||
.get_report(
|
|
||||||
beta_report_id,
|
|
||||||
ENEMY_USER_PAT, // Enemy makes a report
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_status!(&resp, StatusCode::OK);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn permissions_patch_project_v3() {
|
async fn permissions_patch_project_v3() {
|
||||||
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV3>| async move {
|
with_test_environment(Some(8), |test_env: TestEnvironment<ApiV3>| async move {
|
||||||
|
|||||||
Reference in New Issue
Block a user