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.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-codec"
|
name = "actix-codec"
|
||||||
@ -614,9 +614,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stripe"
|
name = "async-stripe"
|
||||||
version = "0.37.3"
|
version = "0.39.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2f14b5943a52cf051bbbbb68538e93a69d1e291934174121e769f4b181113f5"
|
checksum = "58d670cf4d47a1b8ffef54286a5625382e360a34ee76902fd93ad8c7032a0c30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -7467,6 +7467,18 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -10727,9 +10739,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaserde"
|
name = "yaserde"
|
||||||
version = "0.8.0"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bf52af554a50b866aaad63d7eabd6fca298db3dfe49afd50b7ba5a33dfa0582"
|
checksum = "8bfa0d2b420fd005aa9b6f99f9584ebd964e6865d7ca787304cc1a3366c39231"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"xml-rs",
|
"xml-rs",
|
||||||
@ -10737,15 +10749,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaserde_derive"
|
name = "yaserde_derive"
|
||||||
version = "0.8.0"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ab8bd5c76eebb8380b26833d30abddbdd885b00dd06178412e0d51d5bfc221f"
|
checksum = "1f785831c0e09e0f1a83f917054fd59c088f6561db5b2a42c1c3e1687329325f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.4.1",
|
"heck 0.5.0",
|
||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"serde",
|
||||||
|
"serde_tokenstream",
|
||||||
|
"syn 2.0.79",
|
||||||
"xml-rs",
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -71,7 +71,7 @@ const password = ref('')
|
|||||||
const confirmPassword = ref('')
|
const confirmPassword = ref('')
|
||||||
const subscribe = ref(true)
|
const subscribe = ref(true)
|
||||||
|
|
||||||
async function signInOauth(provider) {
|
async function signInOauth() {
|
||||||
const creds = await login().catch(handleSevereError)
|
const creds = await login().catch(handleSevereError)
|
||||||
|
|
||||||
if (creds && creds.type === 'two_factor_required') {
|
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
|
# will have compiled files and executables
|
||||||
/target/
|
/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",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -55,8 +55,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"name": "subscription_interval",
|
"name": "subscription_interval?",
|
||||||
"type_info": "Text"
|
"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": {
|
"parameters": {
|
||||||
@ -75,8 +95,12 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
true
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "86ee460c74f0052a4945ab4df9829b3b077930d8e9e09dca76fde8983413adc6"
|
"hash": "109b6307ff4b06297ddd45ac2385e6a445ee4a4d4f447816dfcd059892c8b68b"
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -17,6 +17,11 @@
|
|||||||
"ordinal": 2,
|
"ordinal": 2,
|
||||||
"name": "raw_image_url",
|
"name": "raw_image_url",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "mod_id",
|
||||||
|
"type_info": "Int8"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -25,10 +30,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
|
false,
|
||||||
false,
|
false,
|
||||||
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",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -55,8 +55,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"name": "subscription_interval",
|
"name": "subscription_interval?",
|
||||||
"type_info": "Text"
|
"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": {
|
"parameters": {
|
||||||
@ -75,8 +95,12 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
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",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -55,8 +55,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"name": "subscription_interval",
|
"name": "subscription_interval?",
|
||||||
"type_info": "Text"
|
"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": {
|
"parameters": {
|
||||||
@ -75,8 +95,12 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
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",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -55,13 +55,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"name": "subscription_interval",
|
"name": "subscription_interval?",
|
||||||
"type_info": "Text"
|
"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": {
|
"parameters": {
|
||||||
"Left": [
|
"Left": [
|
||||||
"Timestamptz"
|
"Int8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
@ -75,8 +95,12 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
true,
|
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 = { version = "1.0", features = ["derive"] }
|
||||||
serde_with = "3.0.0"
|
serde_with = "3.0.0"
|
||||||
chrono = { version = "0.4.26", features = ["serde"] }
|
chrono = { version = "0.4.26", features = ["serde"] }
|
||||||
yaserde = "0.8.0"
|
yaserde = "0.12.0"
|
||||||
yaserde_derive = "0.8.0"
|
yaserde_derive = "0.12.0"
|
||||||
xml-rs = "0.8.15"
|
xml-rs = "0.8.15"
|
||||||
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
@ -120,7 +120,7 @@ rust_iso3166 = "0.1.11"
|
|||||||
|
|
||||||
jemallocator = { version = "0.5.4", optional = true }
|
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"
|
rusty-money = "0.4.1"
|
||||||
json-patch = "*"
|
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,
|
pub name: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Success<'a> {
|
impl Success<'_> {
|
||||||
pub fn render(self) -> HttpResponse {
|
pub fn render(self) -> HttpResponse {
|
||||||
let html = include_str!("success.html");
|
let html = include_str!("success.html");
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use crate::database::models::{
|
use crate::database::models::{
|
||||||
ChargeId, DatabaseError, ProductPriceId, UserId, UserSubscriptionId,
|
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 chrono::{DateTime, Utc};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
@ -18,6 +20,14 @@ pub struct ChargeItem {
|
|||||||
pub type_: ChargeType,
|
pub type_: ChargeType,
|
||||||
pub subscription_id: Option<UserSubscriptionId>,
|
pub subscription_id: Option<UserSubscriptionId>,
|
||||||
pub subscription_interval: Option<PriceDuration>,
|
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 {
|
struct ChargeResult {
|
||||||
@ -32,6 +42,10 @@ struct ChargeResult {
|
|||||||
charge_type: String,
|
charge_type: String,
|
||||||
subscription_id: Option<i64>,
|
subscription_id: Option<i64>,
|
||||||
subscription_interval: Option<String>,
|
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 {
|
impl TryFrom<ChargeResult> for ChargeItem {
|
||||||
@ -52,6 +66,10 @@ impl TryFrom<ChargeResult> for ChargeItem {
|
|||||||
subscription_interval: r
|
subscription_interval: r
|
||||||
.subscription_interval
|
.subscription_interval
|
||||||
.map(|x| PriceDuration::from_string(&x)),
|
.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!(
|
sqlx::query_as!(
|
||||||
ChargeResult,
|
ChargeResult,
|
||||||
r#"
|
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
|
FROM charges
|
||||||
"#
|
"#
|
||||||
+ $predicate,
|
+ $predicate,
|
||||||
@ -77,15 +103,19 @@ impl ChargeItem {
|
|||||||
) -> Result<ChargeId, DatabaseError> {
|
) -> Result<ChargeId, DatabaseError> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO charges (id, user_id, price_id, amount, currency_code, charge_type, status, due, last_attempt, subscription_id, subscription_interval)
|
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)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||||
ON CONFLICT (id)
|
ON CONFLICT (id)
|
||||||
DO UPDATE
|
DO UPDATE
|
||||||
SET status = EXCLUDED.status,
|
SET status = EXCLUDED.status,
|
||||||
last_attempt = EXCLUDED.last_attempt,
|
last_attempt = EXCLUDED.last_attempt,
|
||||||
due = EXCLUDED.due,
|
due = EXCLUDED.due,
|
||||||
subscription_id = EXCLUDED.subscription_id,
|
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.id.0,
|
||||||
self.user_id.0,
|
self.user_id.0,
|
||||||
@ -98,6 +128,10 @@ impl ChargeItem {
|
|||||||
self.last_attempt,
|
self.last_attempt,
|
||||||
self.subscription_id.map(|x| x.0),
|
self.subscription_id.map(|x| x.0),
|
||||||
self.subscription_interval.map(|x| x.as_str()),
|
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)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -135,6 +169,24 @@ impl ChargeItem {
|
|||||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
.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(
|
pub async fn get_open_subscription(
|
||||||
user_subscription_id: UserSubscriptionId,
|
user_subscription_id: UserSubscriptionId,
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
@ -153,9 +205,18 @@ impl ChargeItem {
|
|||||||
pub async fn get_chargeable(
|
pub async fn get_chargeable(
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||||
let now = Utc::now();
|
let charge_type = ChargeType::Subscription.as_str();
|
||||||
|
let res = select_charges_with_predicate!(
|
||||||
let res = select_charges_with_predicate!("WHERE (status = 'open' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now)
|
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)
|
.fetch_all(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -168,10 +229,18 @@ impl ChargeItem {
|
|||||||
pub async fn get_unprovision(
|
pub async fn get_unprovision(
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
|
||||||
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
) -> Result<Vec<ChargeItem>, DatabaseError> {
|
||||||
let now = Utc::now();
|
let charge_type = ChargeType::Subscription.as_str();
|
||||||
|
let res = select_charges_with_predicate!(
|
||||||
let res =
|
r#"
|
||||||
select_charges_with_predicate!("WHERE (status = 'cancelled' AND due < $1) OR (status = 'failed' AND last_attempt < $1 - INTERVAL '2 days')", now)
|
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)
|
.fetch_all(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@ -104,6 +104,29 @@ impl UserSubscriptionItem {
|
|||||||
.collect::<Result<Vec<_>, serde_json::Error>>()?)
|
.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(
|
pub async fn upsert(
|
||||||
&self,
|
&self,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
|||||||
@ -161,7 +161,7 @@ pub struct Charge {
|
|||||||
pub id: ChargeId,
|
pub id: ChargeId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub price_id: ProductPriceId,
|
pub price_id: ProductPriceId,
|
||||||
pub amount: i64,
|
pub amount: u64,
|
||||||
pub currency_code: String,
|
pub currency_code: String,
|
||||||
pub status: ChargeStatus,
|
pub status: ChargeStatus,
|
||||||
pub due: DateTime<Utc>,
|
pub due: DateTime<Utc>,
|
||||||
@ -170,14 +170,16 @@ pub struct Charge {
|
|||||||
pub type_: ChargeType,
|
pub type_: ChargeType,
|
||||||
pub subscription_id: Option<UserSubscriptionId>,
|
pub subscription_id: Option<UserSubscriptionId>,
|
||||||
pub subscription_interval: Option<PriceDuration>,
|
pub subscription_interval: Option<PriceDuration>,
|
||||||
|
pub platform: PaymentPlatform,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
pub enum ChargeType {
|
pub enum ChargeType {
|
||||||
OneTime,
|
OneTime,
|
||||||
Subscription,
|
Subscription,
|
||||||
Proration,
|
Proration,
|
||||||
|
Refund,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChargeType {
|
impl ChargeType {
|
||||||
@ -186,6 +188,7 @@ impl ChargeType {
|
|||||||
ChargeType::OneTime => "one-time",
|
ChargeType::OneTime => "one-time",
|
||||||
ChargeType::Subscription { .. } => "subscription",
|
ChargeType::Subscription { .. } => "subscription",
|
||||||
ChargeType::Proration { .. } => "proration",
|
ChargeType::Proration { .. } => "proration",
|
||||||
|
ChargeType::Refund => "refund",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,12 +197,13 @@ impl ChargeType {
|
|||||||
"one-time" => ChargeType::OneTime,
|
"one-time" => ChargeType::OneTime,
|
||||||
"subscription" => ChargeType::Subscription,
|
"subscription" => ChargeType::Subscription,
|
||||||
"proration" => ChargeType::Proration,
|
"proration" => ChargeType::Proration,
|
||||||
|
"refund" => ChargeType::Refund,
|
||||||
_ => ChargeType::OneTime,
|
_ => ChargeType::OneTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone, Debug)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum ChargeStatus {
|
pub enum ChargeStatus {
|
||||||
// Open charges are for the next billing interval
|
// 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;
|
struct Base62Visitor;
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for Base62Visitor {
|
impl Visitor<'_> for Base62Visitor {
|
||||||
type Value = Base62Id;
|
type Value = Base62Id;
|
||||||
|
|
||||||
fn expecting(
|
fn expecting(
|
||||||
|
|||||||
@ -69,11 +69,11 @@ pub enum PackFileHash {
|
|||||||
|
|
||||||
impl From<String> for PackFileHash {
|
impl From<String> for PackFileHash {
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
return match s.as_str() {
|
match s.as_str() {
|
||||||
"sha1" => PackFileHash::Sha1,
|
"sha1" => PackFileHash::Sha1,
|
||||||
"sha512" => PackFileHash::Sha512,
|
"sha512" => PackFileHash::Sha512,
|
||||||
_ => PackFileHash::Unknown(s),
|
_ => PackFileHash::Unknown(s),
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -818,11 +818,13 @@ pub async fn process_payout(
|
|||||||
|
|
||||||
// Modrinth's share of ad revenue
|
// Modrinth's share of ad revenue
|
||||||
let modrinth_cut = Decimal::from(1) / Decimal::from(4);
|
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);
|
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
|
let net_revenue = aditude_amount
|
||||||
- (clean_io_fee * Decimal::from(aditude_impressions)
|
- ((clean_io_fee + gam_fee) * Decimal::from(aditude_impressions)
|
||||||
/ Decimal::from(1000));
|
/ Decimal::from(1000));
|
||||||
|
|
||||||
let payout = net_revenue * (Decimal::from(1) - modrinth_cut);
|
let payout = net_revenue * (Decimal::from(1) - modrinth_cut);
|
||||||
|
|||||||
@ -6,9 +6,9 @@ use crate::database::models::{
|
|||||||
};
|
};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::billing::{
|
use crate::models::billing::{
|
||||||
Charge, ChargeStatus, ChargeType, Price, PriceDuration, Product,
|
Charge, ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||||
ProductMetadata, ProductPrice, SubscriptionMetadata, SubscriptionStatus,
|
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
||||||
UserSubscription,
|
SubscriptionStatus, UserSubscription,
|
||||||
};
|
};
|
||||||
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
use crate::models::ids::base62_impl::{parse_base62, to_base62};
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
@ -26,11 +26,11 @@ use sqlx::{PgPool, Postgres, Transaction};
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use stripe::{
|
use stripe::{
|
||||||
CreateCustomer, CreatePaymentIntent, CreateSetupIntent,
|
CreateCustomer, CreatePaymentIntent, CreateRefund, CreateSetupIntent,
|
||||||
CreateSetupIntentAutomaticPaymentMethods,
|
CreateSetupIntentAutomaticPaymentMethods,
|
||||||
CreateSetupIntentAutomaticPaymentMethodsAllowRedirects, Currency,
|
CreateSetupIntentAutomaticPaymentMethodsAllowRedirects, Currency,
|
||||||
CustomerId, CustomerInvoiceSettings, CustomerPaymentMethodRetrieval,
|
CustomerId, CustomerInvoiceSettings, CustomerPaymentMethodRetrieval,
|
||||||
EventObject, EventType, PaymentIntentOffSession,
|
EventObject, EventType, PaymentIntentId, PaymentIntentOffSession,
|
||||||
PaymentIntentSetupFutureUsage, PaymentMethodId, SetupIntent,
|
PaymentIntentSetupFutureUsage, PaymentMethodId, SetupIntent,
|
||||||
UpdateCustomer, Webhook,
|
UpdateCustomer, Webhook,
|
||||||
};
|
};
|
||||||
@ -47,8 +47,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(edit_payment_method)
|
.service(edit_payment_method)
|
||||||
.service(remove_payment_method)
|
.service(remove_payment_method)
|
||||||
.service(charges)
|
.service(charges)
|
||||||
|
.service(active_servers)
|
||||||
.service(initiate_payment)
|
.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))
|
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)]
|
#[derive(Deserialize)]
|
||||||
pub struct SubscriptionEdit {
|
pub struct SubscriptionEdit {
|
||||||
pub interval: Option<PriceDuration>,
|
pub interval: Option<PriceDuration>,
|
||||||
@ -264,78 +429,94 @@ pub async fn edit_subscription(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// TODO: Add downgrading plans
|
// Plan downgrade, update future charge
|
||||||
if proration <= 0 {
|
if current_amount > amount {
|
||||||
return Err(ApiError::InvalidInput(
|
open_charge.price_id = product_price.id;
|
||||||
"You may not downgrade plans!".to_string(),
|
open_charge.amount = amount as i64;
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let charge_id = generate_charge_id(&mut transaction).await?;
|
None
|
||||||
let charge = ChargeItem {
|
} else {
|
||||||
id: charge_id,
|
// For small transactions (under 30 cents), we make a loss on the proration due to fees
|
||||||
user_id: user.id.into(),
|
if proration < 30 {
|
||||||
price_id: product_price.id,
|
return Err(ApiError::InvalidInput(
|
||||||
amount: proration as i64,
|
"Proration is too small!".to_string(),
|
||||||
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),
|
|
||||||
};
|
|
||||||
|
|
||||||
let customer_id = get_or_create_customer(
|
let charge_id = generate_charge_id(&mut transaction).await?;
|
||||||
user.id,
|
let mut charge = ChargeItem {
|
||||||
user.stripe_customer_id.as_deref(),
|
id: charge_id,
|
||||||
user.email.as_deref(),
|
user_id: user.id.into(),
|
||||||
&stripe_client,
|
price_id: product_price.id,
|
||||||
&pool,
|
amount: proration as i64,
|
||||||
&redis,
|
currency_code: current_price.currency_code.clone(),
|
||||||
)
|
status: ChargeStatus::Processing,
|
||||||
.await?;
|
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 =
|
let customer_id = get_or_create_customer(
|
||||||
Currency::from_str(¤t_price.currency_code.to_lowercase())
|
user.id,
|
||||||
.map_err(|_| {
|
user.stripe_customer_id.as_deref(),
|
||||||
ApiError::InvalidInput(
|
user.email.as_deref(),
|
||||||
"Invalid currency code".to_string(),
|
&stripe_client,
|
||||||
)
|
&pool,
|
||||||
})?;
|
&redis,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut intent =
|
let currency = Currency::from_str(
|
||||||
CreatePaymentIntent::new(proration as i64, currency);
|
¤t_price.currency_code.to_lowercase(),
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
ApiError::InvalidInput("Invalid currency code".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut metadata = HashMap::new();
|
let mut intent =
|
||||||
metadata
|
CreatePaymentIntent::new(proration as i64, currency);
|
||||||
.insert("modrinth_user_id".to_string(), to_base62(user.id.0));
|
|
||||||
|
|
||||||
intent.customer = Some(customer_id);
|
let mut metadata = HashMap::new();
|
||||||
intent.metadata = Some(metadata);
|
metadata.insert(
|
||||||
intent.receipt_email = user.email.as_deref();
|
"modrinth_user_id".to_string(),
|
||||||
intent.setup_future_usage =
|
to_base62(user.id.0),
|
||||||
Some(PaymentIntentSetupFutureUsage::OffSession);
|
);
|
||||||
|
|
||||||
if let Some(payment_method) = &edit_subscription.payment_method {
|
intent.customer = Some(customer_id);
|
||||||
let payment_method_id =
|
intent.metadata = Some(metadata);
|
||||||
if let Ok(id) = PaymentMethodId::from_str(payment_method) {
|
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
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Invalid payment method id".to_string(),
|
"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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -423,7 +604,7 @@ pub async fn charges(
|
|||||||
id: x.id.into(),
|
id: x.id.into(),
|
||||||
user_id: x.user_id.into(),
|
user_id: x.user_id.into(),
|
||||||
price_id: x.price_id.into(),
|
price_id: x.price_id.into(),
|
||||||
amount: x.amount,
|
amount: x.amount as u64,
|
||||||
currency_code: x.currency_code,
|
currency_code: x.currency_code,
|
||||||
status: x.status,
|
status: x.status,
|
||||||
due: x.due,
|
due: x.due,
|
||||||
@ -431,6 +612,7 @@ pub async fn charges(
|
|||||||
type_: x.type_,
|
type_: x.type_,
|
||||||
subscription_id: x.subscription_id.map(|x| x.into()),
|
subscription_id: x.subscription_id.map(|x| x.into()),
|
||||||
subscription_interval: x.subscription_interval,
|
subscription_interval: x.subscription_interval,
|
||||||
|
platform: x.payment_platform,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.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)]
|
#[derive(Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum PaymentRequestType {
|
pub enum PaymentRequestType {
|
||||||
@ -1130,6 +1355,7 @@ pub async fn stripe_webhook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_payment_intent_metadata(
|
async fn get_payment_intent_metadata(
|
||||||
|
payment_intent_id: PaymentIntentId,
|
||||||
metadata: HashMap<String, String>,
|
metadata: HashMap<String, String>,
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
@ -1203,6 +1429,8 @@ pub async fn stripe_webhook(
|
|||||||
|
|
||||||
charge.status = charge_status;
|
charge.status = charge_status;
|
||||||
charge.last_attempt = Some(Utc::now());
|
charge.last_attempt = Some(Utc::now());
|
||||||
|
charge.payment_platform_id =
|
||||||
|
Some(payment_intent_id.to_string());
|
||||||
charge.upsert(transaction).await?;
|
charge.upsert(transaction).await?;
|
||||||
|
|
||||||
if let Some(subscription_id) = charge.subscription_id {
|
if let Some(subscription_id) = charge.subscription_id {
|
||||||
@ -1229,6 +1457,11 @@ pub async fn stripe_webhook(
|
|||||||
ChargeType::Proration => {
|
ChargeType::Proration => {
|
||||||
subscription.price_id = charge.price_id;
|
subscription.price_id = charge.price_id;
|
||||||
}
|
}
|
||||||
|
ChargeType::Refund => {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Invalid charge type: Refund".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription.upsert(transaction).await?;
|
subscription.upsert(transaction).await?;
|
||||||
@ -1332,6 +1565,12 @@ pub async fn stripe_webhook(
|
|||||||
subscription_interval: subscription
|
subscription_interval: subscription
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|x| x.interval),
|
.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 {
|
if charge_status != ChargeStatus::Failed {
|
||||||
@ -1364,6 +1603,7 @@ pub async fn stripe_webhook(
|
|||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let mut metadata = get_payment_intent_metadata(
|
let mut metadata = get_payment_intent_metadata(
|
||||||
|
payment_intent.id,
|
||||||
payment_intent.metadata,
|
payment_intent.metadata,
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
@ -1372,6 +1612,27 @@ pub async fn stripe_webhook(
|
|||||||
)
|
)
|
||||||
.await?;
|
.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
|
// Provision subscription
|
||||||
match metadata.product_item.metadata {
|
match metadata.product_item.metadata {
|
||||||
ProductMetadata::Midas => {
|
ProductMetadata::Midas => {
|
||||||
@ -1415,7 +1676,20 @@ pub async fn stripe_webhook(
|
|||||||
.await?
|
.await?
|
||||||
.error_for_status()?;
|
.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 {
|
} else {
|
||||||
let (server_name, source) = if let Some(
|
let (server_name, source) = if let Some(
|
||||||
PaymentRequestMetadata::Pyro {
|
PaymentRequestMetadata::Pyro {
|
||||||
@ -1471,6 +1745,10 @@ pub async fn stripe_webhook(
|
|||||||
"storage_mb": storage,
|
"storage_mb": storage,
|
||||||
},
|
},
|
||||||
"source": source,
|
"source": source,
|
||||||
|
"payment_interval": metadata.charge_item.subscription_interval.map(|x| match x {
|
||||||
|
PriceDuration::Monthly => 1,
|
||||||
|
PriceDuration::Yearly => 3,
|
||||||
|
})
|
||||||
}))
|
}))
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
@ -1546,6 +1824,10 @@ pub async fn stripe_webhook(
|
|||||||
subscription_interval: Some(
|
subscription_interval: Some(
|
||||||
subscription.interval,
|
subscription.interval,
|
||||||
),
|
),
|
||||||
|
payment_platform: PaymentPlatform::Stripe,
|
||||||
|
payment_platform_id: None,
|
||||||
|
parent_charge_id: None,
|
||||||
|
net: None,
|
||||||
}
|
}
|
||||||
.upsert(&mut transaction)
|
.upsert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -1569,6 +1851,7 @@ pub async fn stripe_webhook(
|
|||||||
{
|
{
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
get_payment_intent_metadata(
|
get_payment_intent_metadata(
|
||||||
|
payment_intent.id,
|
||||||
payment_intent.metadata,
|
payment_intent.metadata,
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
@ -1586,6 +1869,7 @@ pub async fn stripe_webhook(
|
|||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let metadata = get_payment_intent_metadata(
|
let metadata = get_payment_intent_metadata(
|
||||||
|
payment_intent.id,
|
||||||
payment_intent.metadata,
|
payment_intent.metadata,
|
||||||
&pool,
|
&pool,
|
||||||
&redis,
|
&redis,
|
||||||
@ -1596,7 +1880,7 @@ pub async fn stripe_webhook(
|
|||||||
|
|
||||||
if let Some(email) = metadata.user_item.email {
|
if let Some(email) = metadata.user_item.email {
|
||||||
let money = rusty_money::Money::from_minor(
|
let money = rusty_money::Money::from_minor(
|
||||||
metadata.charge_item.amount,
|
metadata.charge_item.amount as i64,
|
||||||
rusty_money::iso::find(
|
rusty_money::iso::find(
|
||||||
&metadata.charge_item.currency_code,
|
&metadata.charge_item.currency_code,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -150,7 +150,12 @@ pub async fn ws_init(
|
|||||||
{
|
{
|
||||||
let (status, _) = pair.value_mut();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
cfg.service(version_file);
|
cfg.service(version_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These were modified in v3 and should be tested
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, YaSerialize)]
|
#[derive(Default, Debug, Clone, YaSerialize)]
|
||||||
#[yaserde(root = "metadata", rename = "metadata")]
|
#[yaserde(rename = "metadata")]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
#[yaserde(rename = "groupId")]
|
#[yaserde(rename = "groupId")]
|
||||||
group_id: String,
|
group_id: String,
|
||||||
@ -51,11 +49,11 @@ pub struct Versions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, YaSerialize)]
|
#[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 {
|
pub struct MavenPom {
|
||||||
#[yaserde(rename = "xsi:schemaLocation", attribute)]
|
#[yaserde(rename = "xsi:schemaLocation", attribute = true)]
|
||||||
schema_location: String,
|
schema_location: String,
|
||||||
#[yaserde(rename = "xmlns:xsi", attribute)]
|
#[yaserde(rename = "xmlns:xsi", attribute = true)]
|
||||||
xsi: String,
|
xsi: String,
|
||||||
#[yaserde(rename = "modelVersion")]
|
#[yaserde(rename = "modelVersion")]
|
||||||
model_version: String,
|
model_version: String,
|
||||||
|
|||||||
@ -861,7 +861,6 @@ pub struct GalleryEditQuery {
|
|||||||
pub async fn edit_gallery_item(
|
pub async fn edit_gallery_item(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
web::Query(item): web::Query<GalleryEditQuery>,
|
web::Query(item): web::Query<GalleryEditQuery>,
|
||||||
info: web::Path<(String,)>,
|
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
@ -876,7 +875,6 @@ pub async fn edit_gallery_item(
|
|||||||
description: item.description,
|
description: item.description,
|
||||||
ordering: item.ordering,
|
ordering: item.ordering,
|
||||||
}),
|
}),
|
||||||
info,
|
|
||||||
pool,
|
pool,
|
||||||
redis,
|
redis,
|
||||||
session_queue,
|
session_queue,
|
||||||
@ -894,7 +892,6 @@ pub struct GalleryDeleteQuery {
|
|||||||
pub async fn delete_gallery_item(
|
pub async fn delete_gallery_item(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||||
info: web::Path<(String,)>,
|
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
@ -904,7 +901,6 @@ pub async fn delete_gallery_item(
|
|||||||
v3::projects::delete_gallery_item(
|
v3::projects::delete_gallery_item(
|
||||||
req,
|
req,
|
||||||
web::Query(v3::projects::GalleryDeleteQuery { url: item.url }),
|
web::Query(v3::projects::GalleryDeleteQuery { url: item.url }),
|
||||||
info,
|
|
||||||
pool,
|
pool,
|
||||||
redis,
|
redis,
|
||||||
file_host,
|
file_host,
|
||||||
|
|||||||
@ -1788,7 +1788,6 @@ pub struct GalleryEditQuery {
|
|||||||
pub async fn edit_gallery_item(
|
pub async fn edit_gallery_item(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
web::Query(item): web::Query<GalleryEditQuery>,
|
web::Query(item): web::Query<GalleryEditQuery>,
|
||||||
info: web::Path<(String,)>,
|
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
@ -1802,19 +1801,38 @@ pub async fn edit_gallery_item(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
let string = info.into_inner().0;
|
|
||||||
|
|
||||||
item.validate().map_err(|err| {
|
item.validate().map_err(|err| {
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
ApiError::Validation(validation_errors_to_string(err, None))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
let result = sqlx::query!(
|
||||||
.await?
|
"
|
||||||
.ok_or_else(|| {
|
SELECT id, mod_id FROM mods_gallery
|
||||||
ApiError::InvalidInput(
|
WHERE image_url = $1
|
||||||
"The specified project does not exist!".to_string(),
|
",
|
||||||
)
|
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() {
|
if !user.role.is_mod() {
|
||||||
let (team_member, organization_team_member) =
|
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?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
@ -1887,7 +1887,7 @@ pub async fn edit_gallery_item(
|
|||||||
SET featured = $2
|
SET featured = $2
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id,
|
result.id,
|
||||||
featured
|
featured
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@ -1900,7 +1900,7 @@ pub async fn edit_gallery_item(
|
|||||||
SET name = $2
|
SET name = $2
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id,
|
result.id,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@ -1913,7 +1913,7 @@ pub async fn edit_gallery_item(
|
|||||||
SET description = $2
|
SET description = $2
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id,
|
result.id,
|
||||||
description
|
description
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@ -1926,7 +1926,7 @@ pub async fn edit_gallery_item(
|
|||||||
SET ordering = $2
|
SET ordering = $2
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id,
|
result.id,
|
||||||
ordering
|
ordering
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@ -1954,7 +1954,6 @@ pub struct GalleryDeleteQuery {
|
|||||||
pub async fn delete_gallery_item(
|
pub async fn delete_gallery_item(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||||
info: web::Path<(String,)>,
|
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
@ -1969,15 +1968,34 @@ pub async fn delete_gallery_item(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
let string = info.into_inner().0;
|
|
||||||
|
|
||||||
let project_item = db_models::Project::get(&string, &**pool, &redis)
|
let item = sqlx::query!(
|
||||||
.await?
|
"
|
||||||
.ok_or_else(|| {
|
SELECT id, image_url, raw_image_url, mod_id FROM mods_gallery
|
||||||
ApiError::InvalidInput(
|
WHERE image_url = $1
|
||||||
"The specified project does not exist!".to_string(),
|
",
|
||||||
)
|
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() {
|
if !user.role.is_mod() {
|
||||||
let (team_member, organization_team_member) =
|
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(
|
delete_old_images(
|
||||||
Some(item.image_url),
|
Some(item.image_url),
|
||||||
|
|||||||
@ -158,8 +158,8 @@ impl TemporaryDatabase {
|
|||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let needs_update = !dummy_data_update
|
let needs_update = dummy_data_update
|
||||||
.is_some_and(|d| d == DUMMY_DATA_UPDATE);
|
.is_none_or(|d| d != DUMMY_DATA_UPDATE);
|
||||||
if needs_update {
|
if needs_update {
|
||||||
println!("Dummy data updated, so template DB tables will be dropped and re-created");
|
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
|
// 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 {
|
impl From<String> for PackFileHash {
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
return match s.as_str() {
|
match s.as_str() {
|
||||||
"sha1" => PackFileHash::Sha1,
|
"sha1" => PackFileHash::Sha1,
|
||||||
"sha512" => PackFileHash::Sha512,
|
"sha512" => PackFileHash::Sha512,
|
||||||
_ => PackFileHash::Unknown(s),
|
_ => 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)
|
/// Sets generated profile attributes to the pack ones (using profile::edit)
|
||||||
/// This includes the pack name, icon, game version, loader version, and loader
|
/// This includes the pack name, icon, game version, loader version, and loader
|
||||||
|
|
||||||
pub async fn set_profile_information(
|
pub async fn set_profile_information(
|
||||||
profile_path: String,
|
profile_path: String,
|
||||||
description: &CreatePackDescription,
|
description: &CreatePackDescription,
|
||||||
|
|||||||
@ -25,7 +25,6 @@ use super::install_from::{
|
|||||||
/// Wrapper around install_pack_files that generates a pack creation description, and
|
/// 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)
|
/// 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)
|
/// Install a modpack from a mrpack file (a modrinth .zip format)
|
||||||
|
|
||||||
pub async fn install_zipped_mrpack(
|
pub async fn install_zipped_mrpack(
|
||||||
location: CreatePackLocation,
|
location: CreatePackLocation,
|
||||||
profile_path: String,
|
profile_path: String,
|
||||||
@ -68,7 +67,6 @@ pub async fn install_zipped_mrpack(
|
|||||||
|
|
||||||
/// Install all pack files from a description
|
/// Install all pack files from a description
|
||||||
/// Does not remove the profile if it fails
|
/// Does not remove the profile if it fails
|
||||||
|
|
||||||
pub async fn install_zipped_mrpack_files(
|
pub async fn install_zipped_mrpack_files(
|
||||||
create_pack: CreatePack,
|
create_pack: CreatePack,
|
||||||
ignore_lock: bool,
|
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)
|
/// 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
|
/// 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.
|
/// The app will wait for this loading bar to finish before exiting, as it is considered safe.
|
||||||
|
|
||||||
pub async fn init_loading(
|
pub async fn init_loading(
|
||||||
bar_type: LoadingBarType,
|
bar_type: LoadingBarType,
|
||||||
total: f64,
|
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,
|
/// 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.
|
/// meaning that the app won't ask to wait for it to finish before exiting.
|
||||||
|
|
||||||
pub async fn init_loading_unsafe(
|
pub async fn init_loading_unsafe(
|
||||||
bar_type: LoadingBarType,
|
bar_type: LoadingBarType,
|
||||||
total: f64,
|
total: f64,
|
||||||
|
|||||||
@ -38,5 +38,6 @@
|
|||||||
"fix": {
|
"fix": {
|
||||||
"cache": false
|
"cache": false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"ui": "tui"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user