Refunds + Upgrading/Downgrading plans (#2983)
* Refunds + Upgrading/Downgrading plans * Servers list route * Finish, lint * add GAM fee to payouts * Sync payment intent id with stripe * fix lint, update migrations * Remove tauri generated files * Register refund route * fix refund bugs
This commit is contained in:
parent
2cfb637451
commit
2987f507fe
32
Cargo.lock
generated
32
Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
@ -614,9 +614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-stripe"
|
||||
version = "0.37.3"
|
||||
version = "0.39.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2f14b5943a52cf051bbbbb68538e93a69d1e291934174121e769f4b181113f5"
|
||||
checksum = "58d670cf4d47a1b8ffef54286a5625382e360a34ee76902fd93ad8c7032a0c30"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
@ -7467,6 +7467,18 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_tokenstream"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@ -10727,9 +10739,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yaserde"
|
||||
version = "0.8.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bf52af554a50b866aaad63d7eabd6fca298db3dfe49afd50b7ba5a33dfa0582"
|
||||
checksum = "8bfa0d2b420fd005aa9b6f99f9584ebd964e6865d7ca787304cc1a3366c39231"
|
||||
dependencies = [
|
||||
"log",
|
||||
"xml-rs",
|
||||
@ -10737,15 +10749,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yaserde_derive"
|
||||
version = "0.8.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab8bd5c76eebb8380b26833d30abddbdd885b00dd06178412e0d51d5bfc221f"
|
||||
checksum = "1f785831c0e09e0f1a83f917054fd59c088f6561db5b2a42c1c3e1687329325f"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.79",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
|
||||
@ -71,7 +71,7 @@ const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const subscribe = ref(true)
|
||||
|
||||
async function signInOauth(provider) {
|
||||
async function signInOauth() {
|
||||
const creds = await login().catch(handleSevereError)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
|
||||
2
apps/app/.gitignore
vendored
2
apps/app/.gitignore
vendored
@ -2,3 +2,5 @@
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by tauri, metadata generated at compile time
|
||||
/gen/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
28
apps/labrinth/.sqlx/query-043f8c91cc63fefaf56b4d59f1b46addf63c85056d9bc47f1f7a4eba86b914cd.json
generated
Normal file
28
apps/labrinth/.sqlx/query-043f8c91cc63fefaf56b4d59f1b46addf63c85056d9bc47f1f7a4eba86b914cd.json
generated
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id, mod_id FROM mods_gallery\n WHERE image_url = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "043f8c91cc63fefaf56b4d59f1b46addf63c85056d9bc47f1f7a4eba86b914cd"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE user_id = $1 ORDER BY due DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -55,8 +55,28 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "subscription_interval",
|
||||
"name": "subscription_interval?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -75,8 +95,12 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "86ee460c74f0052a4945ab4df9829b3b077930d8e9e09dca76fde8983413adc6"
|
||||
"hash": "109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id, image_url, raw_image_url FROM mods_gallery\n WHERE image_url = $1\n ",
|
||||
"query": "\n SELECT id, image_url, raw_image_url, mod_id FROM mods_gallery\n WHERE image_url = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -17,6 +17,11 @@
|
||||
"ordinal": 2,
|
||||
"name": "raw_image_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "mod_id",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -25,10 +30,11 @@
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "87a0f2f991d749876d90b8e4ab73727f638a019b64e6cb1d891b333c2f09099c"
|
||||
"hash": "1cedeb3367e780314d99b4c069c5a98383277e0db6240394c9a36bbf5fd5d597"
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
58
apps/labrinth/.sqlx/query-3c128a131baef8c8b18dd85a02aeca9658b604b3f3eab53fbac5fa0d95560a1c.json
generated
Normal file
58
apps/labrinth/.sqlx/query-3c128a131baef8c8b18dd85a02aeca9658b604b3f3eab53fbac5fa0d95560a1c.json
generated
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"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 products_prices pp ON us.price_id = pp.id\n INNER JOIN products p ON p.metadata @> '{\"type\": \"pyro\"}'\n WHERE $1::text IS NULL OR us.status = $1::text\n ",
|
||||
"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": "interval",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "status",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "metadata",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3c128a131baef8c8b18dd85a02aeca9658b604b3f3eab53fbac5fa0d95560a1c"
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE parent_charge_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -55,8 +55,28 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "subscription_interval",
|
||||
"name": "subscription_interval?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -75,8 +95,12 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "457493bd11254ba1c9f81c47f15e8154053ae4e90e319d34a940fb73e33a69d4"
|
||||
"hash": "6bcbb0c584804c492ccee49ba0449a8a1cd88fa5d85d4cd6533f65d4c8021634"
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT id FROM mods_gallery\n WHERE image_url = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7c61fee015231f0a97c25d24f2c6be24821e39e330ab82344ad3b985d0d2aaea"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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')",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -55,8 +55,28 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "subscription_interval",
|
||||
"name": "subscription_interval?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -75,8 +95,12 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e68e27fcb3e85233be06e7435aaeb6b27d8dbe2ddaf211ba37a026eab3bb6926"
|
||||
"hash": "7fb6f7e0b2b993d4b89146fdd916dbd7efe31a42127f15c9617caa00d60b7bcc"
|
||||
}
|
||||
106
apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json
generated
Normal file
106
apps/labrinth/.sqlx/query-8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1.json
generated
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n \n WHERE\n charge_type = $1 AND\n (\n (status = 'open' AND due < NOW()) OR\n (status = 'failed' AND last_attempt < NOW() - INTERVAL '2 days')\n )\n ",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8f51f552d1c63fa818cbdba581691e97f693a187ed05224a44adeee68c6809d1"
|
||||
}
|
||||
28
apps/labrinth/.sqlx/query-933606e1ee3cd9a33e57eaf507ee8b7f966e8d3de5aaafadfe7ae30c12c925d2.json
generated
Normal file
28
apps/labrinth/.sqlx/query-933606e1ee3cd9a33e57eaf507ee8b7f966e8d3de5aaafadfe7ae30c12c925d2.json
generated
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"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, payment_platform, payment_platform_id, parent_charge_id, net)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)\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 payment_platform = EXCLUDED.payment_platform,\n payment_platform_id = EXCLUDED.payment_platform_id,\n parent_charge_id = EXCLUDED.parent_charge_id,\n net = EXCLUDED.net\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Varchar",
|
||||
"Timestamptz",
|
||||
"Timestamptz",
|
||||
"Int8",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "933606e1ee3cd9a33e57eaf507ee8b7f966e8d3de5aaafadfe7ae30c12c925d2"
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"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 = 'cancelled' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -55,13 +55,33 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "subscription_interval",
|
||||
"name": "subscription_interval?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz"
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
@ -75,8 +95,12 @@
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a87c913916adf9177f8f38369975d5fc644d989293ccb42c1e06ec54dc2571f8"
|
||||
"hash": "99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2"
|
||||
}
|
||||
106
apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json
generated
Normal file
106
apps/labrinth/.sqlx/query-bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b.json
generated
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n \n WHERE\n charge_type = $1 AND\n (\n (status = 'cancelled' AND due < NOW()) OR\n (status = 'failed' AND last_attempt < NOW() - INTERVAL '2 days')\n )\n ",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "payment_platform",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "payment_platform_id?",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "parent_charge_id?",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "net?",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bfcbcadda1e323d56b6a21fc060c56bff2f38a54cf65dd1cc21f209240c7091b"
|
||||
}
|
||||
@ -40,8 +40,8 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_with = "3.0.0"
|
||||
chrono = { version = "0.4.26", features = ["serde"] }
|
||||
yaserde = "0.8.0"
|
||||
yaserde_derive = "0.8.0"
|
||||
yaserde = "0.12.0"
|
||||
yaserde_derive = "0.12.0"
|
||||
xml-rs = "0.8.15"
|
||||
|
||||
rand = "0.8.5"
|
||||
@ -120,7 +120,7 @@ rust_iso3166 = "0.1.11"
|
||||
|
||||
jemallocator = { version = "0.5.4", optional = true }
|
||||
|
||||
async-stripe = { version = "0.37.3", features = ["runtime-tokio-hyper-rustls"] }
|
||||
async-stripe = { version = "0.39.1", features = ["runtime-tokio-hyper-rustls"] }
|
||||
rusty-money = "0.4.1"
|
||||
json-patch = "*"
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
ALTER TABLE charges
|
||||
ADD COLUMN payment_platform TEXT NOT NULL DEFAULT 'stripe',
|
||||
ADD COLUMN payment_platform_id TEXT NULL,
|
||||
ADD COLUMN parent_charge_id BIGINT REFERENCES charges(id) NULL,
|
||||
ADD COLUMN net BIGINT NULL;
|
||||
@ -8,7 +8,7 @@ pub struct Success<'a> {
|
||||
pub name: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Success<'a> {
|
||||
impl Success<'_> {
|
||||
pub fn render(self) -> HttpResponse {
|
||||
let html = include_str!("success.html");
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
use crate::database::models::{
|
||||
ChargeId, DatabaseError, ProductPriceId, UserId, UserSubscriptionId,
|
||||
};
|
||||
use crate::models::billing::{ChargeStatus, ChargeType, PriceDuration};
|
||||
use crate::models::billing::{
|
||||
ChargeStatus, ChargeType, PaymentPlatform, PriceDuration,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
@ -18,6 +20,14 @@ pub struct ChargeItem {
|
||||
pub type_: ChargeType,
|
||||
pub subscription_id: Option<UserSubscriptionId>,
|
||||
pub subscription_interval: Option<PriceDuration>,
|
||||
|
||||
pub payment_platform: PaymentPlatform,
|
||||
pub payment_platform_id: Option<String>,
|
||||
|
||||
pub parent_charge_id: Option<ChargeId>,
|
||||
|
||||
// Net is always in USD
|
||||
pub net: Option<i64>,
|
||||
}
|
||||
|
||||
struct ChargeResult {
|
||||
@ -32,6 +42,10 @@ struct ChargeResult {
|
||||
charge_type: String,
|
||||
subscription_id: Option<i64>,
|
||||
subscription_interval: Option<String>,
|
||||
payment_platform: String,
|
||||
payment_platform_id: Option<String>,
|
||||
parent_charge_id: Option<i64>,
|
||||
net: Option<i64>,
|
||||
}
|
||||
|
||||
impl TryFrom<ChargeResult> for ChargeItem {
|
||||
@ -52,6 +66,10 @@ impl TryFrom<ChargeResult> for ChargeItem {
|
||||
subscription_interval: r
|
||||
.subscription_interval
|
||||
.map(|x| PriceDuration::from_string(&x)),
|
||||
payment_platform: PaymentPlatform::from_string(&r.payment_platform),
|
||||
payment_platform_id: r.payment_platform_id,
|
||||
parent_charge_id: r.parent_charge_id.map(ChargeId),
|
||||
net: r.net,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -61,7 +79,15 @@ macro_rules! select_charges_with_predicate {
|
||||
sqlx::query_as!(
|
||||
ChargeResult,
|
||||
r#"
|
||||
SELECT id, user_id, price_id, amount, currency_code, status, due, last_attempt, charge_type, subscription_id, subscription_interval
|
||||
SELECT
|
||||
id, user_id, price_id, amount, currency_code, status, due, last_attempt,
|
||||
charge_type, subscription_id,
|
||||
-- Workaround for https://github.com/launchbadge/sqlx/issues/3336
|
||||
subscription_interval AS "subscription_interval?",
|
||||
payment_platform,
|
||||
payment_platform_id AS "payment_platform_id?",
|
||||
parent_charge_id AS "parent_charge_id?",
|
||||
net AS "net?"
|
||||
FROM charges
|
||||
"#
|
||||
+ $predicate,
|
||||
@ -77,15 +103,19 @@ impl ChargeItem {
|
||||
) -> 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)
|
||||
INSERT INTO charges (id, user_id, price_id, amount, currency_code, charge_type, status, due, last_attempt, subscription_id, subscription_interval, payment_platform, payment_platform_id, parent_charge_id, net)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
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
|
||||
subscription_interval = EXCLUDED.subscription_interval,
|
||||
payment_platform = EXCLUDED.payment_platform,
|
||||
payment_platform_id = EXCLUDED.payment_platform_id,
|
||||
parent_charge_id = EXCLUDED.parent_charge_id,
|
||||
net = EXCLUDED.net
|
||||
"#,
|
||||
self.id.0,
|
||||
self.user_id.0,
|
||||
@ -98,6 +128,10 @@ impl ChargeItem {
|
||||
self.last_attempt,
|
||||
self.subscription_id.map(|x| x.0),
|
||||
self.subscription_interval.map(|x| x.as_str()),
|
||||
self.payment_platform.as_str(),
|
||||
self.payment_platform_id.as_deref(),
|
||||
self.parent_charge_id.map(|x| x.0),
|
||||
self.net,
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
@ -135,6 +169,24 @@ impl ChargeItem {
|
||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||
}
|
||||
|
||||
pub async fn get_children(
|
||||
charge_id: ChargeId,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||
let charge_id = charge_id.0;
|
||||
let res = select_charges_with_predicate!(
|
||||
"WHERE parent_charge_id = $1",
|
||||
charge_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>,
|
||||
@ -153,9 +205,18 @@ impl ChargeItem {
|
||||
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)
|
||||
let charge_type = ChargeType::Subscription.as_str();
|
||||
let res = select_charges_with_predicate!(
|
||||
r#"
|
||||
WHERE
|
||||
charge_type = $1 AND
|
||||
(
|
||||
(status = 'open' AND due < NOW()) OR
|
||||
(status = 'failed' AND last_attempt < NOW() - INTERVAL '2 days')
|
||||
)
|
||||
"#,
|
||||
charge_type
|
||||
)
|
||||
.fetch_all(exec)
|
||||
.await?;
|
||||
|
||||
@ -168,10 +229,18 @@ impl ChargeItem {
|
||||
pub async fn get_unprovision(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||
let now = Utc::now();
|
||||
|
||||
let res =
|
||||
select_charges_with_predicate!("WHERE (status = 'cancelled' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now)
|
||||
let charge_type = ChargeType::Subscription.as_str();
|
||||
let res = select_charges_with_predicate!(
|
||||
r#"
|
||||
WHERE
|
||||
charge_type = $1 AND
|
||||
(
|
||||
(status = 'cancelled' AND due < NOW()) OR
|
||||
(status = 'failed' AND last_attempt < NOW() - INTERVAL '2 days')
|
||||
)
|
||||
"#,
|
||||
charge_type
|
||||
)
|
||||
.fetch_all(exec)
|
||||
.await?;
|
||||
|
||||
|
||||
@ -104,6 +104,29 @@ impl UserSubscriptionItem {
|
||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||
}
|
||||
|
||||
pub async fn get_all_servers(
|
||||
status: Option<SubscriptionStatus>,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||
) -> Result<Vec<UserSubscriptionItem>, DatabaseError> {
|
||||
let status = status.map(|x| x.as_str());
|
||||
|
||||
let results = select_user_subscriptions_with_predicate!(
|
||||
r#"
|
||||
INNER JOIN products_prices pp ON us.price_id = pp.id
|
||||
INNER JOIN products p ON p.metadata @> '{"type": "pyro"}'
|
||||
WHERE $1::text IS NULL OR us.status = $1::text
|
||||
"#,
|
||||
status
|
||||
)
|
||||
.fetch_all(exec)
|
||||
.await?;
|
||||
|
||||
Ok(results
|
||||
.into_iter()
|
||||
.map(|r| r.try_into())
|
||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
||||
}
|
||||
|
||||
pub async fn upsert(
|
||||
&self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
|
||||
@ -161,7 +161,7 @@ pub struct Charge {
|
||||
pub id: ChargeId,
|
||||
pub user_id: UserId,
|
||||
pub price_id: ProductPriceId,
|
||||
pub amount: i64,
|
||||
pub amount: u64,
|
||||
pub currency_code: String,
|
||||
pub status: ChargeStatus,
|
||||
pub due: DateTime<Utc>,
|
||||
@ -170,14 +170,16 @@ pub struct Charge {
|
||||
pub type_: ChargeType,
|
||||
pub subscription_id: Option<UserSubscriptionId>,
|
||||
pub subscription_interval: Option<PriceDuration>,
|
||||
pub platform: PaymentPlatform,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||
pub enum ChargeType {
|
||||
OneTime,
|
||||
Subscription,
|
||||
Proration,
|
||||
Refund,
|
||||
}
|
||||
|
||||
impl ChargeType {
|
||||
@ -186,6 +188,7 @@ impl ChargeType {
|
||||
ChargeType::OneTime => "one-time",
|
||||
ChargeType::Subscription { .. } => "subscription",
|
||||
ChargeType::Proration { .. } => "proration",
|
||||
ChargeType::Refund => "refund",
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,12 +197,13 @@ impl ChargeType {
|
||||
"one-time" => ChargeType::OneTime,
|
||||
"subscription" => ChargeType::Subscription,
|
||||
"proration" => ChargeType::Proration,
|
||||
"refund" => ChargeType::Refund,
|
||||
_ => ChargeType::OneTime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ChargeStatus {
|
||||
// Open charges are for the next billing interval
|
||||
@ -232,3 +236,23 @@ impl ChargeStatus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PaymentPlatform {
|
||||
Stripe,
|
||||
}
|
||||
|
||||
impl PaymentPlatform {
|
||||
pub fn from_string(string: &str) -> PaymentPlatform {
|
||||
match string {
|
||||
"stripe" => PaymentPlatform::Stripe,
|
||||
_ => PaymentPlatform::Stripe,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
PaymentPlatform::Stripe => "stripe",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ pub mod base62_impl {
|
||||
{
|
||||
struct Base62Visitor;
|
||||
|
||||
impl<'de> Visitor<'de> for Base62Visitor {
|
||||
impl Visitor<'_> for Base62Visitor {
|
||||
type Value = Base62Id;
|
||||
|
||||
fn expecting(
|
||||
|
||||
@ -69,11 +69,11 @@ pub enum PackFileHash {
|
||||
|
||||
impl From<String> for PackFileHash {
|
||||
fn from(s: String) -> Self {
|
||||
return match s.as_str() {
|
||||
match s.as_str() {
|
||||
"sha1" => PackFileHash::Sha1,
|
||||
"sha512" => PackFileHash::Sha512,
|
||||
_ => PackFileHash::Unknown(s),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -818,11 +818,13 @@ pub async fn process_payout(
|
||||
|
||||
// Modrinth's share of ad revenue
|
||||
let modrinth_cut = Decimal::from(1) / Decimal::from(4);
|
||||
// Clean.io fee (ad antimalware). Per 1000 impressions.
|
||||
// Clean.io fee (ad antimalware). Per 1000 impressions. 0.008 CPM
|
||||
let clean_io_fee = Decimal::from(8) / Decimal::from(1000);
|
||||
// Google Ad Manager fee. Per 1000 impressions. 0.015400 CPM
|
||||
let gam_fee = Decimal::from(154) / Decimal::from(10000);
|
||||
|
||||
let net_revenue = aditude_amount
|
||||
- (clean_io_fee * Decimal::from(aditude_impressions)
|
||||
- ((clean_io_fee + gam_fee) * Decimal::from(aditude_impressions)
|
||||
/ Decimal::from(1000));
|
||||
|
||||
let payout = net_revenue * (Decimal::from(1) - modrinth_cut);
|
||||
|
||||
@ -6,9 +6,9 @@ use crate::database::models::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::billing::{
|
||||
Charge, ChargeStatus, ChargeType, Price, PriceDuration, Product,
|
||||
ProductMetadata, ProductPrice, SubscriptionMetadata, SubscriptionStatus,
|
||||
UserSubscription,
|
||||
Charge, ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
||||
SubscriptionStatus, UserSubscription,
|
||||
};
|
||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||
use crate::models::pats::Scopes;
|
||||
@ -26,11 +26,11 @@ use sqlx::{PgPool, Postgres, Transaction};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str::FromStr;
|
||||
use stripe::{
|
||||
CreateCustomer, CreatePaymentIntent, CreateSetupIntent,
|
||||
CreateCustomer, CreatePaymentIntent, CreateRefund, CreateSetupIntent,
|
||||
CreateSetupIntentAutomaticPaymentMethods,
|
||||
CreateSetupIntentAutomaticPaymentMethodsAllowRedirects, Currency,
|
||||
CustomerId, CustomerInvoiceSettings, CustomerPaymentMethodRetrieval,
|
||||
EventObject, EventType, PaymentIntentOffSession,
|
||||
EventObject, EventType, PaymentIntentId, PaymentIntentOffSession,
|
||||
PaymentIntentSetupFutureUsage, PaymentMethodId, SetupIntent,
|
||||
UpdateCustomer, Webhook,
|
||||
};
|
||||
@ -47,8 +47,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.service(edit_payment_method)
|
||||
.service(remove_payment_method)
|
||||
.service(charges)
|
||||
.service(active_servers)
|
||||
.service(initiate_payment)
|
||||
.service(stripe_webhook),
|
||||
.service(stripe_webhook)
|
||||
.service(refund_charge),
|
||||
);
|
||||
}
|
||||
|
||||
@ -111,6 +113,169 @@ pub async fn subscriptions(
|
||||
Ok(HttpResponse::Ok().json(subscriptions))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ChargeRefundAmount {
|
||||
Full,
|
||||
Partial { amount: u64 },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ChargeRefund {
|
||||
#[serde(flatten)]
|
||||
pub amount: ChargeRefundAmount,
|
||||
pub unprovision: Option<bool>,
|
||||
}
|
||||
|
||||
#[post("charge/{id}/refund")]
|
||||
pub async fn refund_charge(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
info: web::Path<(crate::models::ids::ChargeId,)>,
|
||||
body: web::Json<ChargeRefund>,
|
||||
stripe_client: web::Data<stripe::Client>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::SESSION_ACCESS]),
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let (id,) = info.into_inner();
|
||||
|
||||
if !user.role.is_admin() {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to refund a subscription!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(charge) = ChargeItem::get(id.into(), &**pool).await? {
|
||||
let refunds = ChargeItem::get_children(id.into(), &**pool).await?;
|
||||
let refunds = -refunds
|
||||
.into_iter()
|
||||
.filter_map(|x| match x.status {
|
||||
ChargeStatus::Open
|
||||
| ChargeStatus::Processing
|
||||
| ChargeStatus::Succeeded => Some(x.amount),
|
||||
ChargeStatus::Failed | ChargeStatus::Cancelled => None,
|
||||
})
|
||||
.sum::<i64>();
|
||||
|
||||
let refundable = charge.amount - refunds;
|
||||
|
||||
let refund_amount = match body.0.amount {
|
||||
ChargeRefundAmount::Full => refundable,
|
||||
ChargeRefundAmount::Partial { amount } => amount as i64,
|
||||
};
|
||||
|
||||
if charge.status != ChargeStatus::Succeeded {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"This charge cannot be refunded!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if (refundable - refund_amount) < 0 || refund_amount == 0 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You cannot refund more than the amount of the charge!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let (id, net) = match charge.payment_platform {
|
||||
PaymentPlatform::Stripe => {
|
||||
if let Some(payment_platform_id) = charge
|
||||
.payment_platform_id
|
||||
.and_then(|x| stripe::PaymentIntentId::from_str(&x).ok())
|
||||
{
|
||||
let mut metadata = HashMap::new();
|
||||
|
||||
metadata.insert(
|
||||
"modrinth_user_id".to_string(),
|
||||
to_base62(user.id.0),
|
||||
);
|
||||
metadata.insert(
|
||||
"modrinth_charge_id".to_string(),
|
||||
to_base62(charge.id.0 as u64),
|
||||
);
|
||||
|
||||
let refund = stripe::Refund::create(
|
||||
&stripe_client,
|
||||
CreateRefund {
|
||||
amount: Some(refund_amount),
|
||||
metadata: Some(metadata),
|
||||
payment_intent: Some(payment_platform_id),
|
||||
|
||||
expand: &["balance_transaction"],
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
(
|
||||
refund.id.to_string(),
|
||||
refund
|
||||
.balance_transaction
|
||||
.and_then(|x| x.into_object())
|
||||
.map(|x| x.net),
|
||||
)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Charge does not have attached payment id!".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let charge_id = generate_charge_id(&mut transaction).await?;
|
||||
ChargeItem {
|
||||
id: charge_id,
|
||||
user_id: charge.user_id,
|
||||
price_id: charge.price_id,
|
||||
amount: -refund_amount,
|
||||
currency_code: charge.currency_code,
|
||||
status: ChargeStatus::Succeeded,
|
||||
due: Utc::now(),
|
||||
last_attempt: None,
|
||||
type_: ChargeType::Refund,
|
||||
subscription_id: charge.subscription_id,
|
||||
subscription_interval: charge.subscription_interval,
|
||||
payment_platform: charge.payment_platform,
|
||||
payment_platform_id: Some(id),
|
||||
parent_charge_id: Some(charge.id),
|
||||
net,
|
||||
}
|
||||
.upsert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
if body.0.unprovision.unwrap_or(false) {
|
||||
if let Some(subscription_id) = charge.subscription_id {
|
||||
let open_charge =
|
||||
ChargeItem::get_open_subscription(subscription_id, &**pool)
|
||||
.await?;
|
||||
if let Some(mut open_charge) = open_charge {
|
||||
open_charge.status = ChargeStatus::Cancelled;
|
||||
open_charge.due = Utc::now();
|
||||
|
||||
open_charge.upsert(&mut transaction).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SubscriptionEdit {
|
||||
pub interval: Option<PriceDuration>,
|
||||
@ -264,78 +429,94 @@ pub async fn edit_subscription(
|
||||
)
|
||||
})?;
|
||||
|
||||
// TODO: Add downgrading plans
|
||||
if proration <= 0 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You may not downgrade plans!".to_string(),
|
||||
));
|
||||
}
|
||||
// Plan downgrade, update future charge
|
||||
if current_amount > amount {
|
||||
open_charge.price_id = product_price.id;
|
||||
open_charge.amount = amount as i64;
|
||||
|
||||
let charge_id = generate_charge_id(&mut transaction).await?;
|
||||
let charge = ChargeItem {
|
||||
id: charge_id,
|
||||
user_id: user.id.into(),
|
||||
price_id: product_price.id,
|
||||
amount: proration as i64,
|
||||
currency_code: current_price.currency_code.clone(),
|
||||
status: ChargeStatus::Processing,
|
||||
due: Utc::now(),
|
||||
last_attempt: None,
|
||||
type_: ChargeType::Proration,
|
||||
subscription_id: Some(subscription.id),
|
||||
subscription_interval: Some(duration),
|
||||
};
|
||||
None
|
||||
} else {
|
||||
// For small transactions (under 30 cents), we make a loss on the proration due to fees
|
||||
if proration < 30 {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Proration is too small!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let customer_id = get_or_create_customer(
|
||||
user.id,
|
||||
user.stripe_customer_id.as_deref(),
|
||||
user.email.as_deref(),
|
||||
&stripe_client,
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
let charge_id = generate_charge_id(&mut transaction).await?;
|
||||
let mut charge = ChargeItem {
|
||||
id: charge_id,
|
||||
user_id: user.id.into(),
|
||||
price_id: product_price.id,
|
||||
amount: proration as i64,
|
||||
currency_code: current_price.currency_code.clone(),
|
||||
status: ChargeStatus::Processing,
|
||||
due: Utc::now(),
|
||||
last_attempt: None,
|
||||
type_: ChargeType::Proration,
|
||||
subscription_id: Some(subscription.id),
|
||||
subscription_interval: Some(duration),
|
||||
payment_platform: PaymentPlatform::Stripe,
|
||||
payment_platform_id: None,
|
||||
parent_charge_id: None,
|
||||
net: None,
|
||||
};
|
||||
|
||||
let currency =
|
||||
Currency::from_str(¤t_price.currency_code.to_lowercase())
|
||||
.map_err(|_| {
|
||||
ApiError::InvalidInput(
|
||||
"Invalid currency code".to_string(),
|
||||
)
|
||||
})?;
|
||||
let customer_id = get_or_create_customer(
|
||||
user.id,
|
||||
user.stripe_customer_id.as_deref(),
|
||||
user.email.as_deref(),
|
||||
&stripe_client,
|
||||
&pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut intent =
|
||||
CreatePaymentIntent::new(proration as i64, currency);
|
||||
let currency = Currency::from_str(
|
||||
¤t_price.currency_code.to_lowercase(),
|
||||
)
|
||||
.map_err(|_| {
|
||||
ApiError::InvalidInput("Invalid currency code".to_string())
|
||||
})?;
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
metadata
|
||||
.insert("modrinth_user_id".to_string(), to_base62(user.id.0));
|
||||
let mut intent =
|
||||
CreatePaymentIntent::new(proration as i64, currency);
|
||||
|
||||
intent.customer = Some(customer_id);
|
||||
intent.metadata = Some(metadata);
|
||||
intent.receipt_email = user.email.as_deref();
|
||||
intent.setup_future_usage =
|
||||
Some(PaymentIntentSetupFutureUsage::OffSession);
|
||||
let mut metadata = HashMap::new();
|
||||
metadata.insert(
|
||||
"modrinth_user_id".to_string(),
|
||||
to_base62(user.id.0),
|
||||
);
|
||||
|
||||
if let Some(payment_method) = &edit_subscription.payment_method {
|
||||
let payment_method_id =
|
||||
if let Ok(id) = PaymentMethodId::from_str(payment_method) {
|
||||
intent.customer = Some(customer_id);
|
||||
intent.metadata = Some(metadata);
|
||||
intent.receipt_email = user.email.as_deref();
|
||||
intent.setup_future_usage =
|
||||
Some(PaymentIntentSetupFutureUsage::OffSession);
|
||||
|
||||
if let Some(payment_method) = &edit_subscription.payment_method
|
||||
{
|
||||
let payment_method_id = if let Ok(id) =
|
||||
PaymentMethodId::from_str(payment_method)
|
||||
{
|
||||
id
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Invalid payment method id".to_string(),
|
||||
));
|
||||
};
|
||||
intent.payment_method = Some(payment_method_id);
|
||||
intent.payment_method = Some(payment_method_id);
|
||||
}
|
||||
|
||||
let intent =
|
||||
stripe::PaymentIntent::create(&stripe_client, intent)
|
||||
.await?;
|
||||
|
||||
charge.payment_platform_id = Some(intent.id.to_string());
|
||||
charge.upsert(&mut transaction).await?;
|
||||
|
||||
Some((proration, 0, intent))
|
||||
}
|
||||
|
||||
charge.upsert(&mut transaction).await?;
|
||||
|
||||
Some((
|
||||
proration,
|
||||
0,
|
||||
stripe::PaymentIntent::create(&stripe_client, intent).await?,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -423,7 +604,7 @@ pub async fn charges(
|
||||
id: x.id.into(),
|
||||
user_id: x.user_id.into(),
|
||||
price_id: x.price_id.into(),
|
||||
amount: x.amount,
|
||||
amount: x.amount as u64,
|
||||
currency_code: x.currency_code,
|
||||
status: x.status,
|
||||
due: x.due,
|
||||
@ -431,6 +612,7 @@ pub async fn charges(
|
||||
type_: x.type_,
|
||||
subscription_id: x.subscription_id.map(|x| x.into()),
|
||||
subscription_interval: x.subscription_interval,
|
||||
platform: x.payment_platform,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
@ -685,6 +867,49 @@ pub async fn payment_methods(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ActiveServersQuery {
|
||||
pub subscription_status: Option<SubscriptionStatus>,
|
||||
}
|
||||
|
||||
#[get("active_servers")]
|
||||
pub async fn active_servers(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<ActiveServersQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let master_key = dotenvy::var("PYRO_API_KEY")?;
|
||||
|
||||
if !req
|
||||
.head()
|
||||
.headers()
|
||||
.get("X-Master-Key")
|
||||
.map_or(false, |it| it.as_bytes() == master_key.as_bytes())
|
||||
{
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"Invalid master key".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let servers =
|
||||
user_subscription_item::UserSubscriptionItem::get_all_servers(
|
||||
query.subscription_status,
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let server_ids = servers
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
x.metadata.map(|x| match x {
|
||||
SubscriptionMetadata::Pyro { id } => id,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(server_ids))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum PaymentRequestType {
|
||||
@ -1130,6 +1355,7 @@ pub async fn stripe_webhook(
|
||||
}
|
||||
|
||||
async fn get_payment_intent_metadata(
|
||||
payment_intent_id: PaymentIntentId,
|
||||
metadata: HashMap<String, String>,
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
@ -1203,6 +1429,8 @@ pub async fn stripe_webhook(
|
||||
|
||||
charge.status = charge_status;
|
||||
charge.last_attempt = Some(Utc::now());
|
||||
charge.payment_platform_id =
|
||||
Some(payment_intent_id.to_string());
|
||||
charge.upsert(transaction).await?;
|
||||
|
||||
if let Some(subscription_id) = charge.subscription_id {
|
||||
@ -1229,6 +1457,11 @@ pub async fn stripe_webhook(
|
||||
ChargeType::Proration => {
|
||||
subscription.price_id = charge.price_id;
|
||||
}
|
||||
ChargeType::Refund => {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Invalid charge type: Refund".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
subscription.upsert(transaction).await?;
|
||||
@ -1332,6 +1565,12 @@ pub async fn stripe_webhook(
|
||||
subscription_interval: subscription
|
||||
.as_ref()
|
||||
.map(|x| x.interval),
|
||||
payment_platform: PaymentPlatform::Stripe,
|
||||
payment_platform_id: Some(
|
||||
payment_intent_id.to_string(),
|
||||
),
|
||||
parent_charge_id: None,
|
||||
net: None,
|
||||
};
|
||||
|
||||
if charge_status != ChargeStatus::Failed {
|
||||
@ -1364,6 +1603,7 @@ pub async fn stripe_webhook(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let mut metadata = get_payment_intent_metadata(
|
||||
payment_intent.id,
|
||||
payment_intent.metadata,
|
||||
&pool,
|
||||
&redis,
|
||||
@ -1372,6 +1612,27 @@ pub async fn stripe_webhook(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(latest_charge) = payment_intent.latest_charge {
|
||||
let charge = stripe::Charge::retrieve(
|
||||
&stripe_client,
|
||||
&latest_charge.id(),
|
||||
&["balance_transaction"],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(balance_transaction) = charge
|
||||
.balance_transaction
|
||||
.and_then(|x| x.into_object())
|
||||
{
|
||||
metadata.charge_item.net =
|
||||
Some(balance_transaction.net);
|
||||
metadata
|
||||
.charge_item
|
||||
.upsert(&mut transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// Provision subscription
|
||||
match metadata.product_item.metadata {
|
||||
ProductMetadata::Midas => {
|
||||
@ -1415,7 +1676,20 @@ pub async fn stripe_webhook(
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
// TODO: Send plan upgrade request for proration
|
||||
client.post(format!(
|
||||
"https://archon.pyro.host/modrinth/v0/servers/{}/reallocate",
|
||||
id
|
||||
))
|
||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
||||
.json(&serde_json::json!({
|
||||
"memory_mb": ram,
|
||||
"cpu": cpu,
|
||||
"swap_mb": swap,
|
||||
"storage_mb": storage,
|
||||
}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
} else {
|
||||
let (server_name, source) = if let Some(
|
||||
PaymentRequestMetadata::Pyro {
|
||||
@ -1471,6 +1745,10 @@ pub async fn stripe_webhook(
|
||||
"storage_mb": storage,
|
||||
},
|
||||
"source": source,
|
||||
"payment_interval": metadata.charge_item.subscription_interval.map(|x| match x {
|
||||
PriceDuration::Monthly => 1,
|
||||
PriceDuration::Yearly => 3,
|
||||
})
|
||||
}))
|
||||
.send()
|
||||
.await?
|
||||
@ -1546,6 +1824,10 @@ pub async fn stripe_webhook(
|
||||
subscription_interval: Some(
|
||||
subscription.interval,
|
||||
),
|
||||
payment_platform: PaymentPlatform::Stripe,
|
||||
payment_platform_id: None,
|
||||
parent_charge_id: None,
|
||||
net: None,
|
||||
}
|
||||
.upsert(&mut transaction)
|
||||
.await?;
|
||||
@ -1569,6 +1851,7 @@ pub async fn stripe_webhook(
|
||||
{
|
||||
let mut transaction = pool.begin().await?;
|
||||
get_payment_intent_metadata(
|
||||
payment_intent.id,
|
||||
payment_intent.metadata,
|
||||
&pool,
|
||||
&redis,
|
||||
@ -1586,6 +1869,7 @@ pub async fn stripe_webhook(
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let metadata = get_payment_intent_metadata(
|
||||
payment_intent.id,
|
||||
payment_intent.metadata,
|
||||
&pool,
|
||||
&redis,
|
||||
@ -1596,7 +1880,7 @@ pub async fn stripe_webhook(
|
||||
|
||||
if let Some(email) = metadata.user_item.email {
|
||||
let money = rusty_money::Money::from_minor(
|
||||
metadata.charge_item.amount,
|
||||
metadata.charge_item.amount as i64,
|
||||
rusty_money::iso::find(
|
||||
&metadata.charge_item.currency_code,
|
||||
)
|
||||
|
||||
@ -150,7 +150,12 @@ pub async fn ws_init(
|
||||
{
|
||||
let (status, _) = pair.value_mut();
|
||||
|
||||
if status.profile_name.as_ref().map(|x| x.len() > 64).unwrap_or(false) {
|
||||
if status
|
||||
.profile_name
|
||||
.as_ref()
|
||||
.map(|x| x.len() > 64)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@ -21,10 +21,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(version_file);
|
||||
}
|
||||
|
||||
// TODO: These were modified in v3 and should be tested
|
||||
|
||||
#[derive(Default, Debug, Clone, YaSerialize)]
|
||||
#[yaserde(root = "metadata", rename = "metadata")]
|
||||
#[yaserde(rename = "metadata")]
|
||||
pub struct Metadata {
|
||||
#[yaserde(rename = "groupId")]
|
||||
group_id: String,
|
||||
@ -51,11 +49,11 @@ pub struct Versions {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, YaSerialize)]
|
||||
#[yaserde(rename = "project", namespace = "http://maven.apache.org/POM/4.0.0")]
|
||||
#[yaserde(rename = "project", namespaces = { "" = "http://maven.apache.org/POM/4.0.0" })]
|
||||
pub struct MavenPom {
|
||||
#[yaserde(rename = "xsi:schemaLocation", attribute)]
|
||||
#[yaserde(rename = "xsi:schemaLocation", attribute = true)]
|
||||
schema_location: String,
|
||||
#[yaserde(rename = "xmlns:xsi", attribute)]
|
||||
#[yaserde(rename = "xmlns:xsi", attribute = true)]
|
||||
xsi: String,
|
||||
#[yaserde(rename = "modelVersion")]
|
||||
model_version: String,
|
||||
|
||||
@ -861,7 +861,6 @@ pub struct GalleryEditQuery {
|
||||
pub async fn edit_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryEditQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@ -876,7 +875,6 @@ pub async fn edit_gallery_item(
|
||||
description: item.description,
|
||||
ordering: item.ordering,
|
||||
}),
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
session_queue,
|
||||
@ -894,7 +892,6 @@ pub struct GalleryDeleteQuery {
|
||||
pub async fn delete_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
@ -904,7 +901,6 @@ pub async fn delete_gallery_item(
|
||||
v3::projects::delete_gallery_item(
|
||||
req,
|
||||
web::Query(v3::projects::GalleryDeleteQuery { url: item.url }),
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
file_host,
|
||||
|
||||
@ -1788,7 +1788,6 @@ pub struct GalleryEditQuery {
|
||||
pub async fn edit_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryEditQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@ -1802,19 +1801,38 @@ pub async fn edit_gallery_item(
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
item.validate().map_err(|err| {
|
||||
ApiError::Validation(validation_errors_to_string(err, None))
|
||||
})?;
|
||||
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT id, mod_id FROM mods_gallery
|
||||
WHERE image_url = $1
|
||||
",
|
||||
item.url
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Gallery item at URL {} is not part of the project's gallery.",
|
||||
item.url
|
||||
))
|
||||
})?;
|
||||
|
||||
let project_item = db_models::Project::get_id(
|
||||
database::models::ProjectId(result.mod_id),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let (team_member, organization_team_member) =
|
||||
@ -1845,24 +1863,6 @@ pub async fn edit_gallery_item(
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let id = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods_gallery
|
||||
WHERE image_url = $1
|
||||
",
|
||||
item.url
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Gallery item at URL {} is not part of the project's gallery.",
|
||||
item.url
|
||||
))
|
||||
})?
|
||||
.id;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@ -1887,7 +1887,7 @@ pub async fn edit_gallery_item(
|
||||
SET featured = $2
|
||||
WHERE id = $1
|
||||
",
|
||||
id,
|
||||
result.id,
|
||||
featured
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
@ -1900,7 +1900,7 @@ pub async fn edit_gallery_item(
|
||||
SET name = $2
|
||||
WHERE id = $1
|
||||
",
|
||||
id,
|
||||
result.id,
|
||||
name
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
@ -1913,7 +1913,7 @@ pub async fn edit_gallery_item(
|
||||
SET description = $2
|
||||
WHERE id = $1
|
||||
",
|
||||
id,
|
||||
result.id,
|
||||
description
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
@ -1926,7 +1926,7 @@ pub async fn edit_gallery_item(
|
||||
SET ordering = $2
|
||||
WHERE id = $1
|
||||
",
|
||||
id,
|
||||
result.id,
|
||||
ordering
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
@ -1954,7 +1954,6 @@ pub struct GalleryDeleteQuery {
|
||||
pub async fn delete_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
@ -1969,15 +1968,34 @@ pub async fn delete_gallery_item(
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
let item = sqlx::query!(
|
||||
"
|
||||
SELECT id, image_url, raw_image_url, mod_id FROM mods_gallery
|
||||
WHERE image_url = $1
|
||||
",
|
||||
item.url
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Gallery item at URL {} is not part of the project's gallery.",
|
||||
item.url
|
||||
))
|
||||
})?;
|
||||
|
||||
let project_item = db_models::Project::get_id(
|
||||
database::models::ProjectId(item.mod_id),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(
|
||||
"The specified project does not exist!".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let (team_member, organization_team_member) =
|
||||
@ -2009,23 +2027,6 @@ pub async fn delete_gallery_item(
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let item = sqlx::query!(
|
||||
"
|
||||
SELECT id, image_url, raw_image_url FROM mods_gallery
|
||||
WHERE image_url = $1
|
||||
",
|
||||
item.url
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Gallery item at URL {} is not part of the project's gallery.",
|
||||
item.url
|
||||
))
|
||||
})?;
|
||||
|
||||
delete_old_images(
|
||||
Some(item.image_url),
|
||||
|
||||
@ -158,8 +158,8 @@ impl TemporaryDatabase {
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let needs_update = !dummy_data_update
|
||||
.is_some_and(|d| d == DUMMY_DATA_UPDATE);
|
||||
let needs_update = dummy_data_update
|
||||
.is_none_or(|d| d != DUMMY_DATA_UPDATE);
|
||||
if needs_update {
|
||||
println!("Dummy data updated, so template DB tables will be dropped and re-created");
|
||||
// Drop all tables in the database so they can be re-created and later filled with updated dummy data
|
||||
|
||||
@ -44,11 +44,11 @@ pub enum PackFileHash {
|
||||
|
||||
impl From<String> for PackFileHash {
|
||||
fn from(s: String) -> Self {
|
||||
return match s.as_str() {
|
||||
match s.as_str() {
|
||||
"sha1" => PackFileHash::Sha1,
|
||||
"sha512" => PackFileHash::Sha512,
|
||||
_ => PackFileHash::Unknown(s),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +324,6 @@ pub async fn generate_pack_from_file(
|
||||
|
||||
/// Sets generated profile attributes to the pack ones (using profile::edit)
|
||||
/// This includes the pack name, icon, game version, loader version, and loader
|
||||
|
||||
pub async fn set_profile_information(
|
||||
profile_path: String,
|
||||
description: &CreatePackDescription,
|
||||
|
||||
@ -25,7 +25,6 @@ use super::install_from::{
|
||||
/// Wrapper around install_pack_files that generates a pack creation description, and
|
||||
/// attempts to install the pack files. If it fails, it will remove the profile (fail safely)
|
||||
/// Install a modpack from a mrpack file (a modrinth .zip format)
|
||||
|
||||
pub async fn install_zipped_mrpack(
|
||||
location: CreatePackLocation,
|
||||
profile_path: String,
|
||||
@ -68,7 +67,6 @@ pub async fn install_zipped_mrpack(
|
||||
|
||||
/// Install all pack files from a description
|
||||
/// Does not remove the profile if it fails
|
||||
|
||||
pub async fn install_zipped_mrpack_files(
|
||||
create_pack: CreatePack,
|
||||
ignore_lock: bool,
|
||||
|
||||
@ -44,7 +44,6 @@ const CLI_PROGRESS_BAR_TOTAL: u64 = 1000;
|
||||
/// total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity)
|
||||
/// title is the title of the loading bar
|
||||
/// The app will wait for this loading bar to finish before exiting, as it is considered safe.
|
||||
|
||||
pub async fn init_loading(
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
@ -56,7 +55,6 @@ pub async fn init_loading(
|
||||
|
||||
/// An unsafe loading bar can be created without adding it to the SafeProcesses list,
|
||||
/// meaning that the app won't ask to wait for it to finish before exiting.
|
||||
|
||||
pub async fn init_loading_unsafe(
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
|
||||
@ -38,5 +38,6 @@
|
||||
"fix": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"ui": "tui"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user