From dfa43f3c5a4ed2344ed650bd570566ff74bcc5ac Mon Sep 17 00:00:00 2001 From: Jackson Kruger Date: Fri, 6 Oct 2023 17:58:02 -0500 Subject: [PATCH 1/4] Add /metrics endpoint for Prometheus (#724) --- Cargo.lock | 28 ++++++++++++++++++++++++++++ Cargo.toml | 21 +++++++++++++++++---- src/main.rs | 7 +++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 030e2fc7..77159808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,6 +284,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "actix-web-prom" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23f332a652836b8f3a6876103c70c9ed436d0e69fa779ab5d7f57b1d5c8d488" +dependencies = [ + "actix-web", + "futures-core", + "pin-project-lite", + "prometheus", + "regex", +] + [[package]] name = "actix-ws" version = "0.2.5" @@ -2234,6 +2247,7 @@ dependencies = [ "actix-multipart", "actix-rt", "actix-web", + "actix-web-prom", "actix-ws", "argon2", "async-trait", @@ -3139,6 +3153,20 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.1", + "thiserror", +] + [[package]] name = "ptr_meta" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 8e464ad5..885904b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ actix-multipart = "0.6.0" actix-cors = "0.6.4" actix-ws = "0.2.5" actix-files = "0.6.2" +actix-web-prom = "0.7.0" tokio = { version = "1.29.1", features = ["sync"] } tokio-stream = "0.1.14" @@ -37,7 +38,7 @@ hyper-tls = "0.5.0" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } serde_with = "3.0.0" -chrono = { version = "0.4.26", features = ["serde"]} +chrono = { version = "0.4.26", features = ["serde"] } yaserde = "0.8.0" yaserde_derive = "0.8.0" xml-rs = "0.8.15" @@ -72,9 +73,21 @@ log = "0.4.19" env_logger = "0.10.0" thiserror = "1.0.41" -sqlx = { version = "0.6.3", features = ["offline", "runtime-tokio-rustls", "postgres", "chrono", "macros", "migrate", "decimal", "json"] } -rust_decimal = { version = "1.30.0", features = ["serde-with-float", "serde-with-str"] } -redis = { version = "0.23.0", features = ["tokio-comp", "ahash", "r2d2"]} +sqlx = { version = "0.6.3", features = [ + "offline", + "runtime-tokio-rustls", + "postgres", + "chrono", + "macros", + "migrate", + "decimal", + "json", +] } +rust_decimal = { version = "1.30.0", features = [ + "serde-with-float", + "serde-with-str", +] } +redis = { version = "0.23.0", features = ["tokio-comp", "ahash", "r2d2"] } deadpool-redis = "0.12.0" clickhouse = { version = "0.11.2", features = ["uuid", "time"] } uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] } diff --git a/src/main.rs b/src/main.rs index e0d0e0ff..67093c0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use actix_web::{App, HttpServer}; +use actix_web_prom::PrometheusMetricsBuilder; use env_logger::Env; use labrinth::database::redis::RedisPool; use labrinth::file_hosting::S3Host; @@ -88,6 +89,11 @@ async fn main() -> std::io::Result<()> { let store = MemoryStore::new(); + let prometheus = PrometheusMetricsBuilder::new("labrinth") + .endpoint("/metrics") + .build() + .expect("Failed to create prometheus metrics middleware"); + info!("Starting Actix HTTP server!"); let labrinth_config = labrinth::app_setup( @@ -101,6 +107,7 @@ async fn main() -> std::io::Result<()> { // Init App HttpServer::new(move || { App::new() + .wrap(prometheus.clone()) .wrap(actix_web::middleware::Compress::default()) .wrap( RateLimiter::new(MemoryStoreActor::from(store.clone()).start()) From d92272ffa0933b485a466e17aa7141ea56bef9f9 Mon Sep 17 00:00:00 2001 From: Jackson Kruger Date: Wed, 11 Oct 2023 13:32:58 -0500 Subject: [PATCH 2/4] Batch inserts [MOD-555] (#726) * Batch a bunch of inserts, but still more to do * Insert many for clickhouse (+ tests) * Batch the remaining ones except those requiring deduplication * Risky dedups * Bit o cleanup and formatting * cargo sqlx prepare * Add test around batch editing project categories * Add struct to satisfy clippy * Fix silly mistake that was caught by the tests! * Leave room for growth in dummy_data --- Cargo.lock | 12 + Cargo.toml | 2 + sqlx-data.json | 444 ++++++++---------- src/clickhouse/mod.rs | 6 +- src/database/models/collection_item.rs | 26 +- src/database/models/project_item.rs | 144 +++--- src/database/models/team_item.rs | 73 ++- src/database/models/thread_item.rs | 28 +- src/database/models/version_item.rs | 257 ++++++++--- src/queue/analytics.rs | 65 +-- src/queue/analytics/tests.rs | 128 ++++++ src/queue/payouts.rs | 31 +- src/routes/analytics.rs | 26 +- src/routes/v2/admin.rs | 64 ++- src/routes/v2/collections.rs | 32 +- src/routes/v2/projects.rs | 595 +++++++++++-------------- src/routes/v2/version_creation.rs | 4 +- src/routes/v2/versions.rs | 39 +- tests/common/database.rs | 5 + tests/common/dummy_data.rs | 10 + tests/common/environment.rs | 13 + tests/files/dummy_data.sql | 22 +- tests/project.rs | 71 ++- 23 files changed, 1188 insertions(+), 909 deletions(-) create mode 100644 src/queue/analytics/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 77159808..40237625 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1284,6 +1284,17 @@ dependencies = [ "uuid 1.4.0", ] +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_builder" version = "0.12.0" @@ -2260,6 +2271,7 @@ dependencies = [ "color-thief", "dashmap", "deadpool-redis", + "derive-new", "dotenvy", "env_logger", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 885904b9..898a4ae4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,5 +106,7 @@ woothee = "0.13.0" lettre = "0.10.4" +derive-new = "0.5.9" + [dev-dependencies] actix-http = "3.4.0" diff --git a/sqlx-data.json b/sqlx-data.json index eabec5b7..9549939c 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -265,19 +265,6 @@ }, "query": "\n SELECT o.id, o.title, o.team_id, o.description, o.icon_url, o.color\n FROM organizations o\n WHERE o.id = ANY($1) OR o.title = ANY($2)\n GROUP BY o.id;\n " }, - "05baeb26d9856218e5c6f8856a96788b2a7ac3536ff9412a50552cef1d561a1e": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, FALSE)\n " - }, "061a3e43df9464263aaf1555a27c1f4b6a0f381282f4fa75cc13b1d354857578": { "describe": { "columns": [ @@ -595,24 +582,6 @@ }, "query": "\n SELECT SUM(pv.amount) amount\n FROM payouts_values pv\n WHERE pv.user_id = $1\n " }, - "0c2addb0d7a87fa558821ff8e943bbb751fb2bdc22d1a5368f61cc7827586840": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Varchar", - "Varchar", - "Bool", - "Int4", - "Varchar" - ] - } - }, - "query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n " - }, "0c6b7e51b0b9115d95b5dbb9bb88a3e266b78ae9375a90261503c2cccd5bdf1b": { "describe": { "columns": [], @@ -870,21 +839,6 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM threads WHERE id=$1)" }, - "196c8ac2228e199f23eaf980f7ea15b37f76e66bb81da1115a754aad0be756e4": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Numeric", - "Timestamptz" - ] - } - }, - "query": "\n INSERT INTO payouts_values (user_id, mod_id, amount, created)\n VALUES ($1, $2, $3, $4)\n " - }, "19c7498a01f51b8220245a53490916191a153fa1fe14404d39ab2980e3386058": { "describe": { "columns": [], @@ -935,6 +889,34 @@ }, "query": "\n UPDATE collections\n SET icon_url = NULL, color = NULL\n WHERE (id = $1)\n " }, + "1b66b5d566aa6a969bacbb7897af829a569e13a619db295d2e6abcdb89fcac17": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO collections_mods (collection_id, mod_id)\n SELECT * FROM UNNEST ($1::int8[], $2::int8[])\n ON CONFLICT DO NOTHING\n " + }, + "1c30a8a31b031f194f70dc2a3bac8e131513dd7e9d2c46432ca797f6422c6ecf": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8Array", + "NumericArray", + "TimestamptzArray" + ] + } + }, + "query": "\n INSERT INTO payouts_values (user_id, mod_id, amount, created)\n SELECT * FROM UNNEST ($1::bigint[], $2::bigint[], $3::numeric[], $4::timestamptz[])\n " + }, "1cefe4924d3c1f491739858ce844a22903d2dbe26f255219299f1833a10ce3d7": { "describe": { "columns": [ @@ -1201,6 +1183,24 @@ }, "query": "\n UPDATE team_members\n SET permissions = $1\n WHERE (team_id = $2 AND user_id = $3)\n " }, + "24ae57ca296554a29b414caca866cfe7ab956ea28450d40a564498c3d27b937f": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8Array", + "VarcharArray", + "VarcharArray", + "BoolArray", + "Int4Array", + "VarcharArray" + ] + } + }, + "query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::varchar[], $4::varchar[], $5::bool[], $6::integer[], $7::varchar[])\n " + }, "25131559cb73a088000ab6379a769233440ade6c7511542da410065190d203fc": { "describe": { "columns": [ @@ -1233,18 +1233,6 @@ }, "query": "\n DELETE FROM threads_members\n WHERE thread_id = $1\n " }, - "299b8ea6e7a0048fa389cc4432715dc2a09e227d2f08e91167a43372a7ac6e35": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = FALSE\n " - }, "29e171bd746ac5dc1fabae4c9f81c3d1df4e69c860b7d0f6a907377664199217": { "describe": { "columns": [ @@ -1265,19 +1253,6 @@ }, "query": "\n SELECT id FROM reports\n WHERE closed = FALSE\n ORDER BY created ASC\n LIMIT $1;\n " }, - "29e657d26f0fb24a766f5b5eb6a94d01d1616884d8ca10e91536e974d5b585a6": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Int8" - ] - } - }, - "query": "\n INSERT INTO loaders_versions (loader_id, version_id)\n VALUES ($1, $2)\n " - }, "29fcff0f1d36bd1a9e0c8c4005209308f0c5f383e4e52ed8c6b989994ead32df": { "describe": { "columns": [], @@ -1462,6 +1437,19 @@ }, "query": "\n SELECT id FROM users\n WHERE email = $1\n " }, + "3151420021b0c5a85f7c338e67be971915ff89073815e27fa6af5254db22dce8": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4Array", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO loaders_versions (loader_id, version_id)\n SELECT * FROM UNNEST($1::integer[], $2::bigint[])\n " + }, "320d73cd900a6e00f0e74b7a8c34a7658d16034b01a35558cb42fa9c16185eb5": { "describe": { "columns": [ @@ -1768,31 +1756,6 @@ }, "query": "\n UPDATE mods\n SET title = $1\n WHERE (id = $2)\n " }, - "3f2f05653552ce8c1be95ce0a922ab41f52f40f8ff6c91c6621481102c8f35e3": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Int8" - ] - } - }, - "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n VALUES ($1, $2)\n " - }, - "3fcfed18cbfb37866e0fa57a4e95efb326864f8219941d1b696add39ed333ad1": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = TRUE\n " - }, "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08": { "describe": { "columns": [], @@ -2009,6 +1972,20 @@ }, "query": "\n UPDATE uploaded_images\n SET thread_message_id = $1\n WHERE id = $2\n " }, + "473db826b691ae1131990ef0927cfe5b63d48829dd41edb7def22248d5668ac7": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int4Array", + "VarcharArray" + ] + } + }, + "query": "\n INSERT INTO mods_donations (\n joining_mod_id, joining_platform_id, url\n )\n SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[])\n " + }, "4778d2f5994fda2f978fa53e0840c1a9a2582ef0434a5ff7f21706f1dc4edcf4": { "describe": { "columns": [], @@ -2055,19 +2032,6 @@ }, "query": "\n SELECT d.dependency_id, COALESCE(vd.mod_id, 0) mod_id, d.mod_dependency_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n LEFT JOIN versions vd ON d.dependency_id = vd.id\n WHERE v.mod_id = $1\n " }, - "489913b3c32631fb329a3259cfe620d65053e2abf425a0d3f1bc01f1cdbdd73d": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - } - }, - "query": "\n INSERT INTO collections_mods (collection_id, mod_id)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n " - }, "49813a96f007216072d69468aae705d73d5b85dcdd64a22060009b12d947ed5a": { "describe": { "columns": [], @@ -2649,6 +2613,23 @@ }, "query": "SELECT id FROM users WHERE gitlab_id = $1" }, + "5d65f89c020ae032f26d742c37afe47876911eb3a16a6852299b98f2a8251fb4": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "VarcharArray", + "BoolArray", + "VarcharArray", + "VarcharArray", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO mods_gallery (\n mod_id, image_url, featured, title, description, ordering\n )\n SELECT * FROM UNNEST ($1::bigint[], $2::varchar[], $3::bool[], $4::varchar[], $5::varchar[], $6::bigint[])\n " + }, "5d7425cfa91e332bf7cc14aa5c300b997e941c49757606f6b906cb5e060d3179": { "describe": { "columns": [], @@ -2725,22 +2706,6 @@ }, "query": "\n UPDATE mods_gallery\n SET ordering = $2\n WHERE id = $1\n " }, - "5f94e9e767ec4be7f9136b991b4a29373dbe48feb2f61281e3212721095ed675": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Varchar", - "Int8", - "Int8", - "Varchar" - ] - } - }, - "query": "\n INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name)\n VALUES ($1, $2, $3, $4, $5)\n " - }, "60a251aea1efbc7d9357255e520f0ac13f3697fecb84b1e9edd5d9ea61fe0cb0": { "describe": { "columns": [ @@ -3187,19 +3152,6 @@ }, "query": "\n UPDATE mods\n SET loaders = (\n SELECT COALESCE(ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null), array[]::varchar[])\n FROM versions v\n INNER JOIN loaders_versions lv ON lv.version_id = v.id\n INNER JOIN loaders l on lv.loader_id = l.id\n WHERE v.mod_id = mods.id AND v.status != ALL($2)\n )\n WHERE id = $1\n " }, - "6c7aeb0db4a4fb3387c37b8d7aca6fdafaa637fd883a44416b56270aeebb7a01": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Int8" - ] - } - }, - "query": "\n INSERT INTO loaders_versions (loader_id, version_id)\n VALUES ($1, $2)\n " - }, "6c8b8a2f11c0b4e7a5973547fe1611a0fa4ef366d5c8a91d9fb9a1360ea04d46": { "describe": { "columns": [ @@ -3358,18 +3310,38 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM uploaded_images WHERE id=$1)" }, - "70b510956a40583eef8c57dcced71c67f525eee455ae8b09e9b2403668068751": { + "7075dc0343dab7c4dd4469b4af095232dcdd056a15d928a6d93556daf6fd327c": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8Array", + "Int8Array", + "VarcharArray", + "Int8Array", + "Int8Array", + "BoolArray", + "NumericArray", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering)\n SELECT * FROM UNNEST ($1::int8[], $2::int8[], $3::int8[], $4::varchar[], $5::int8[], $6::int8[], $7::bool[], $8::numeric[], $9::int8[])\n " + }, + "70c812c6a0d29465569169afde42c74a353a534aeedd5cdd81bceb2a7de6bc78": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ "Int8", - "Int8" + "Bool" ] } }, - "query": "\n INSERT INTO threads_members (\n thread_id, user_id\n )\n VALUES (\n $1, $2\n )\n " + "query": "\n DELETE FROM mods_categories\n WHERE joining_mod_id = $1 AND is_additional = $2\n " }, "71abd207410d123f9a50345ddcddee335fea0d0cc6f28762713ee01a36aee8a0": { "describe": { @@ -3743,19 +3715,6 @@ }, "query": "\n SELECT id FROM mods_gallery\n WHERE image_url = $1\n " }, - "7cb691738c28e0d1f28c84ba2dbcfa21a6dbd859bcf0f565f90cd7ce2ea5aa1c": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, FALSE)\n " - }, "7e030d43f3412e7df63c970f873d0a73dd2deb9857aa6f201ec5eec628eb336c": { "describe": { "columns": [], @@ -3807,35 +3766,34 @@ }, "query": "\n DELETE FROM historical_payouts\n WHERE user_id = $1\n " }, - "85463fa221147ee8d409fc92ed681fa27df683e7c80b8dd8616ae94dc1205c24": { + "8475c7cb94786576012b16d53a017cb250f0de99b76746d8725798daa3345c5e": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ - "Int8", - "Int8" + "Int8Array", + "VarcharArray", + "Int8Array", + "Int8Array", + "VarcharArray" ] } }, - "query": "\n UPDATE versions\n SET author_id = $1\n WHERE (author_id = $2)\n " + "query": "\n INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name)\n SELECT * FROM UNNEST ($1::bigint[], $2::varchar[], $3::bigint[], $4::bigint[], $5::varchar[])\n " }, - "85b40877c48fc4f23039c1b556007f92056a015f160fe1059b0d3b13615af0fb": { + "85463fa221147ee8d409fc92ed681fa27df683e7c80b8dd8616ae94dc1205c24": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ "Int8", - "Varchar", - "Bool", - "Varchar", - "Varchar", "Int8" ] } }, - "query": "\n INSERT INTO mods_gallery (\n mod_id, image_url, featured, title, description, ordering\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n " + "query": "\n UPDATE versions\n SET author_id = $1\n WHERE (author_id = $2)\n " }, "85c6de008681d9fc9dc51b17330bed09204010813111e66a7ca84bc0e603f537": { "describe": { @@ -4820,19 +4778,21 @@ }, "query": "\n SELECT m.id id, tm.user_id user_id, tm.payouts_split payouts_split\n FROM mods m\n INNER JOIN team_members tm on m.team_id = tm.team_id AND tm.accepted = TRUE\n WHERE m.id = ANY($1) AND m.monetization_status = $2\n " }, - "b903ac4e686ef85ba28d698c668da07860e7f276b261d8f2cebb74e73b094970": { + "b86145932b1f919fc82414c303ade80f62d4c1bc155f948359b5f6578c680244": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ - "Int8" + "Int8Array", + "Int4Array", + "BoolArray" ] } }, - "query": "\n DELETE FROM hashes\n WHERE EXISTS(\n SELECT 1 FROM files WHERE\n (files.version_id = $1) AND\n (hashes.file_id = files.id)\n )\n " + "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n SELECT * FROM UNNEST ($1::bigint[], $2::int[], $3::bool[])\n " }, - "b9399840dbbf807a03d69b7fcb3bd479ef20920ab1e3c91706a1c2c7089f48e7": { + "b903ac4e686ef85ba28d698c668da07860e7f276b261d8f2cebb74e73b094970": { "describe": { "columns": [], "nullable": [], @@ -4842,20 +4802,19 @@ ] } }, - "query": "\n INSERT INTO teams (id)\n VALUES ($1)\n " + "query": "\n DELETE FROM hashes\n WHERE EXISTS(\n SELECT 1 FROM files WHERE\n (files.version_id = $1) AND\n (hashes.file_id = files.id)\n )\n " }, - "b96ab39ab9624bfcdc8675107544307af9892504c4cbc40e4e7c40a1e4e83e14": { + "b9399840dbbf807a03d69b7fcb3bd479ef20920ab1e3c91706a1c2c7089f48e7": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ - "Int4", "Int8" ] } }, - "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n VALUES ($1, $2)\n " + "query": "\n INSERT INTO teams (id)\n VALUES ($1)\n " }, "b971cecafab7046c5952447fd78a6e45856841256d812ce9ae3c07f903c5cc62": { "describe": { @@ -5157,20 +5116,6 @@ }, "query": "\n UPDATE users\n SET payout_wallet = NULL, payout_wallet_type = NULL, payout_address = NULL\n WHERE (id = $1)\n " }, - "c545a74e902c5c63bca1057b76e94b9547ee21fadbc61964f45837915d5f4608": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4", - "Varchar" - ] - } - }, - "query": "\n INSERT INTO mods_donations (\n joining_mod_id, joining_platform_id, url\n )\n VALUES (\n $1, $2, $3\n )\n " - }, "c55d2132e3e6e92dd50457affab758623dca175dc27a2d3cd4aace9cfdecf789": { "describe": { "columns": [], @@ -5217,37 +5162,30 @@ }, "query": "\n UPDATE mods\n SET client_side = $1\n WHERE (id = $2)\n " }, - "c6060a389343c9f35aea5d931518b85ab7c71b6ba74eae7b4b51d881f1798c6e": { + "c8a27a122160a0896914c786deef9e8193eb240501d30d5ffb4129e2103efd3d": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ - "Int8", - "Int8", - "Int8", - "Varchar", - "Int8", - "Int8", - "Bool", - "Numeric", - "Int8" + "Text" ] } }, - "query": "\n INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n " + "query": "\n UPDATE versions\n SET status = requested_status\n WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL\n " }, - "c8a27a122160a0896914c786deef9e8193eb240501d30d5ffb4129e2103efd3d": { + "c8c0bf5d298810a7a30caf03d7437af757303fa9aa0f500b83476e65cec7f1e9": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ - "Text" + "Int8Array", + "Int8Array" ] } }, - "query": "\n UPDATE versions\n SET status = requested_status\n WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL\n " + "query": "\n INSERT INTO threads_members (\n thread_id, user_id\n )\n SELECT * FROM UNNEST ($1::int8[], $2::int8[])\n " }, "c8fde56e5d03eda085519b4407768de7ddf48cae18ce7138a97e8e8fba967e15": { "describe": { @@ -5376,20 +5314,6 @@ }, "query": "\n SELECT id, user_id, session, created, last_login, expires, refresh_expires, os, platform,\n city, country, ip, user_agent\n FROM sessions\n WHERE id = ANY($1) OR session = ANY($2)\n ORDER BY created DESC\n " }, - "cb57ae673f1a7e50cc319efddb9bdc82e2251596bcf85aea52e8def343e423b8": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Varchar", - "Bytea" - ] - } - }, - "query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n " - }, "cb82bb6e22690fd5fee18bbc2975503371814ef1cbf95f32c195bfe7542b2b20": { "describe": { "columns": [], @@ -5408,19 +5332,6 @@ }, "query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, organization_permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7\n )\n " }, - "ccce60dc60ca6c4ea1142ab6d0d81bdb1ee9ed97c992695324aec015e0e190bf": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - } - }, - "query": "\n INSERT INTO collections_mods (collection_id, mod_id)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING\n " - }, "ccd913bb2f3006ffe881ce2fc4ef1e721d18fe2eed6ac62627046c955129610c": { "describe": { "columns": [ @@ -5453,19 +5364,6 @@ }, "query": "\n DELETE FROM hashes\n WHERE file_id = $1\n " }, - "cdf20036b29b61da40bf990c9ab04c509297a4d65bc9b136c9fb20f1e97e1149": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, FALSE)\n " - }, "ce20a9c53249e255be7312819f505d935d3ab2ee3c21a6422e5b12155c159bd7": { "describe": { "columns": [ @@ -5660,6 +5558,20 @@ }, "query": "\n INSERT INTO notifications (\n id, user_id, body\n )\n VALUES (\n $1, $2, $3\n )\n " }, + "d2e826d4fa4e3e730cc84c97964c0c5fdd25cd49ddff8c593bd9b8a3b4d5ff1e": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "VarcharArray", + "ByteaArray" + ] + } + }, + "query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n SELECT * FROM UNNEST($1::bigint[], $2::varchar[], $3::bytea[])\n " + }, "d331ca8f22da418cf654985c822ce4466824beaa00dea64cde90dc651a03024b": { "describe": { "columns": [], @@ -5791,19 +5703,6 @@ }, "query": "\n SELECT id, team_id, role AS member_role, permissions, organization_permissions,\n accepted, payouts_split, role,\n ordering, user_id\n FROM team_members\n WHERE (team_id = ANY($1) AND user_id = $2 AND accepted = TRUE)\n ORDER BY ordering\n " }, - "d59a0ca4725d40232eae8bf5735787e1b76282c390d2a8d07fb34e237a0b2132": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " - }, "d6453e50041b5521fa9e919a9162e533bb9426f8c584d98474c6ad414db715c8": { "describe": { "columns": [ @@ -6505,6 +6404,19 @@ }, "query": "\n SELECT name FROM project_types pt\n INNER JOIN mods ON mods.project_type = pt.id\n WHERE mods.id = $1\n " }, + "efdaae627a24efdf522c913cfd3600d6331e30dffbba8c2d318e44e260ac5f59": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8Array", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO collections_mods (collection_id, mod_id)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[])\n ON CONFLICT DO NOTHING\n " + }, "f1525930830e17b5ee8feb796d9950dd3741131965f050840fa75423b5a54f01": { "describe": { "columns": [], @@ -6723,6 +6635,19 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 WHERE m.id = $1)" }, + "fa54ed32004b883daa44eeb413fc2e07b45883608afc6ac91ac6f74736a12256": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4Array", + "Int8Array" + ] + } + }, + "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n SELECT * FROM UNNEST($1::integer[], $2::bigint[])\n " + }, "fb955ca41b95120f66c98c0b528b1db10c4be4a55e9641bb104d772e390c9bb7": { "describe": { "columns": [ @@ -6743,19 +6668,6 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM notifications WHERE id=$1)" }, - "fcd15905507769ab7f9839d64d1be3ee3f61cd555aee57dace76f8e53e91d344": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " - }, "fce67ce3d0c27c64af85fb7d36661513bc5ea2e96fcf12f3a51c97999b01b83c": { "describe": { "columns": [ diff --git a/src/clickhouse/mod.rs b/src/clickhouse/mod.rs index 09722462..c46c0089 100644 --- a/src/clickhouse/mod.rs +++ b/src/clickhouse/mod.rs @@ -6,8 +6,12 @@ mod fetch; pub use fetch::*; pub async fn init_client() -> clickhouse::error::Result { - let database = dotenvy::var("CLICKHOUSE_DATABASE").unwrap(); + init_client_with_database(&dotenvy::var("CLICKHOUSE_DATABASE").unwrap()).await +} +pub async fn init_client_with_database( + database: &str, +) -> clickhouse::error::Result { let client = { let mut http_connector = HttpConnector::new(); http_connector.enforce_http(false); // allow https URLs diff --git a/src/database/models/collection_item.rs b/src/database/models/collection_item.rs index 12ff7838..a24f5dcb 100644 --- a/src/database/models/collection_item.rs +++ b/src/database/models/collection_item.rs @@ -81,19 +81,19 @@ impl Collection { .execute(&mut *transaction) .await?; - for project_id in self.projects.iter() { - sqlx::query!( - " - INSERT INTO collections_mods (collection_id, mod_id) - VALUES ($1, $2) - ON CONFLICT DO NOTHING - ", - self.id as CollectionId, - *project_id as ProjectId, - ) - .execute(&mut *transaction) - .await?; - } + let (collection_ids, project_ids): (Vec<_>, Vec<_>) = + self.projects.iter().map(|p| (self.id.0, p.0)).unzip(); + sqlx::query!( + " + INSERT INTO collections_mods (collection_id, mod_id) + SELECT * FROM UNNEST($1::bigint[], $2::bigint[]) + ON CONFLICT DO NOTHING + ", + &collection_ids[..], + &project_ids[..], + ) + .execute(&mut *transaction) + .await?; Ok(()) } diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index f841f934..f214c9ad 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -5,6 +5,7 @@ use crate::database::redis::RedisPool; use crate::models::ids::base62_impl::{parse_base62, to_base62}; use crate::models::projects::{MonetizationStatus, ProjectStatus}; use chrono::{DateTime, Utc}; +use itertools::Itertools; use serde::{Deserialize, Serialize}; pub const PROJECTS_NAMESPACE: &str = "projects"; @@ -20,23 +21,25 @@ pub struct DonationUrl { } impl DonationUrl { - pub async fn insert_project( - &self, + pub async fn insert_many_projects( + donation_urls: Vec, project_id: ProjectId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), sqlx::error::Error> { + let (project_ids, platform_ids, urls): (Vec<_>, Vec<_>, Vec<_>) = donation_urls + .into_iter() + .map(|url| (project_id.0, url.platform_id.0, url.url)) + .multiunzip(); sqlx::query!( " INSERT INTO mods_donations ( joining_mod_id, joining_platform_id, url ) - VALUES ( - $1, $2, $3 - ) + SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[]) ", - project_id as ProjectId, - self.platform_id as DonationPlatformId, - self.url, + &project_ids[..], + &platform_ids[..], + &urls[..], ) .execute(&mut *transaction) .await?; @@ -56,26 +59,76 @@ pub struct GalleryItem { } impl GalleryItem { - pub async fn insert( - &self, + pub async fn insert_many( + items: Vec, project_id: ProjectId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), sqlx::error::Error> { + let (project_ids, image_urls, featureds, titles, descriptions, orderings): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = items + .into_iter() + .map(|gi| { + ( + project_id.0, + gi.image_url, + gi.featured, + gi.title, + gi.description, + gi.ordering, + ) + }) + .multiunzip(); sqlx::query!( " INSERT INTO mods_gallery ( mod_id, image_url, featured, title, description, ordering ) - VALUES ( - $1, $2, $3, $4, $5, $6 - ) + SELECT * FROM UNNEST ($1::bigint[], $2::varchar[], $3::bool[], $4::varchar[], $5::varchar[], $6::bigint[]) ", - project_id as ProjectId, - self.image_url, - self.featured, - self.title, - self.description, - self.ordering + &project_ids[..], + &image_urls[..], + &featureds[..], + &titles[..] as &[Option], + &descriptions[..] as &[Option], + &orderings[..] + ) + .execute(&mut *transaction) + .await?; + + Ok(()) + } +} + +#[derive(derive_new::new)] +pub struct ModCategory { + project_id: ProjectId, + category_id: CategoryId, + is_additional: bool, +} + +impl ModCategory { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (project_ids, category_ids, is_additionals): (Vec<_>, Vec<_>, Vec<_>) = items + .into_iter() + .map(|mc| (mc.project_id.0, mc.category_id.0, mc.is_additional)) + .multiunzip(); + sqlx::query!( + " + INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) + SELECT * FROM UNNEST ($1::bigint[], $2::int[], $3::bool[]) + ", + &project_ids[..], + &category_ids[..], + &is_additionals[..] ) .execute(&mut *transaction) .await?; @@ -160,46 +213,35 @@ impl ProjectBuilder { }; project_struct.insert(&mut *transaction).await?; + let ProjectBuilder { + donation_urls, + gallery_items, + categories, + additional_categories, + .. + } = self; + for mut version in self.initial_versions { version.project_id = self.project_id; version.insert(&mut *transaction).await?; } - for donation in self.donation_urls { - donation - .insert_project(self.project_id, &mut *transaction) - .await?; - } - - for gallery in self.gallery_items { - gallery.insert(self.project_id, &mut *transaction).await?; - } - - for category in self.categories { - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, FALSE) - ", - self.project_id as ProjectId, - category as CategoryId, - ) - .execute(&mut *transaction) + DonationUrl::insert_many_projects(donation_urls, self.project_id, &mut *transaction) .await?; - } - for category in self.additional_categories { - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, TRUE) - ", - self.project_id as ProjectId, - category as CategoryId, + GalleryItem::insert_many(gallery_items, self.project_id, &mut *transaction).await?; + + let project_id = self.project_id; + let mod_categories = categories + .into_iter() + .map(|c| ModCategory::new(project_id, c, false)) + .chain( + additional_categories + .into_iter() + .map(|c| ModCategory::new(project_id, c, true)), ) - .execute(&mut *transaction) - .await?; - } + .collect_vec(); + ModCategory::insert_many(mod_categories, &mut *transaction).await?; Project::update_game_versions(self.project_id, &mut *transaction).await?; Project::update_loaders(self.project_id, &mut *transaction).await?; diff --git a/src/database/models/team_item.rs b/src/database/models/team_item.rs index 31d60b20..a6e7c783 100644 --- a/src/database/models/team_item.rs +++ b/src/database/models/team_item.rs @@ -41,26 +41,61 @@ impl TeamBuilder { .execute(&mut *transaction) .await?; - for member in self.members { - let team_member_id = generate_team_member_id(&mut *transaction).await?; - sqlx::query!( - " - INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ", - team_member_id as TeamMemberId, - team.id as TeamId, - member.user_id as UserId, - member.role, - member.permissions.bits() as i64, - member.organization_permissions.map(|p| p.bits() as i64), - member.accepted, - member.payouts_split, - member.ordering, - ) - .execute(&mut *transaction) - .await?; + let mut team_member_ids = Vec::new(); + for _ in self.members.iter() { + team_member_ids.push(generate_team_member_id(&mut *transaction).await?.0); } + let TeamBuilder { members } = self; + let ( + team_ids, + user_ids, + roles, + permissions, + organization_permissions, + accepteds, + payouts_splits, + orderings, + ): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = members + .into_iter() + .map(|m| { + ( + team.id.0, + m.user_id.0, + m.role, + m.permissions.bits() as i64, + m.organization_permissions.map(|p| p.bits() as i64), + m.accepted, + m.payouts_split, + m.ordering, + ) + }) + .multiunzip(); + sqlx::query!( + " + INSERT INTO team_members (id, team_id, user_id, role, permissions, organization_permissions, accepted, payouts_split, ordering) + SELECT * FROM UNNEST ($1::int8[], $2::int8[], $3::int8[], $4::varchar[], $5::int8[], $6::int8[], $7::bool[], $8::numeric[], $9::int8[]) + ", + &team_member_ids[..], + &team_ids[..], + &user_ids[..], + &roles[..], + &permissions[..], + &organization_permissions[..] as &[Option], + &accepteds[..], + &payouts_splits[..], + &orderings[..], + ) + .execute(&mut *transaction) + .await?; Ok(team_id) } diff --git a/src/database/models/thread_item.rs b/src/database/models/thread_item.rs index c81b2db4..b8582cee 100644 --- a/src/database/models/thread_item.rs +++ b/src/database/models/thread_item.rs @@ -90,22 +90,20 @@ impl ThreadBuilder { .execute(&mut *transaction) .await?; - for member in &self.members { - sqlx::query!( - " - INSERT INTO threads_members ( - thread_id, user_id - ) - VALUES ( - $1, $2 - ) - ", - thread_id as ThreadId, - *member as UserId, + let (thread_ids, members): (Vec<_>, Vec<_>) = + self.members.iter().map(|m| (thread_id.0, m.0)).unzip(); + sqlx::query!( + " + INSERT INTO threads_members ( + thread_id, user_id ) - .execute(&mut *transaction) - .await?; - } + SELECT * FROM UNNEST ($1::int8[], $2::int8[]) + ", + &thread_ids[..], + &members[..], + ) + .execute(&mut *transaction) + .await?; Ok(thread_id) } diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index f917b20d..451f22f4 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -39,12 +39,59 @@ pub struct DependencyBuilder { } impl DependencyBuilder { - pub async fn insert( - self, + pub async fn insert_many( + builders: Vec, version_id: VersionId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), DatabaseError> { - let project_id = if let Some(project_id) = self.project_id { + let mut project_ids = Vec::new(); + for dependency in builders.iter() { + project_ids.push( + dependency + .try_get_project_id(transaction) + .await? + .map(|id| id.0), + ); + } + + let (version_ids, dependency_types, dependency_ids, filenames): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = builders + .into_iter() + .map(|d| { + ( + version_id.0, + d.dependency_type, + d.version_id.map(|v| v.0), + d.file_name, + ) + }) + .multiunzip(); + sqlx::query!( + " + INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name) + SELECT * FROM UNNEST ($1::bigint[], $2::varchar[], $3::bigint[], $4::bigint[], $5::varchar[]) + ", + &version_ids[..], + &dependency_types[..], + &dependency_ids[..] as &[Option], + &project_ids[..] as &[Option], + &filenames[..] as &[Option], + ) + .execute(&mut *transaction) + .await?; + + Ok(()) + } + + async fn try_get_project_id( + &self, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result, DatabaseError> { + Ok(if let Some(project_id) = self.project_id { Some(project_id) } else if let Some(version_id) = self.version_id { sqlx::query!( @@ -58,23 +105,7 @@ impl DependencyBuilder { .map(|x| ProjectId(x.mod_id)) } else { None - }; - - sqlx::query!( - " - INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name) - VALUES ($1, $2, $3, $4, $5) - ", - version_id as VersionId, - self.dependency_type, - self.version_id.map(|x| x.0), - project_id.map(|x| x.0), - self.file_name, - ) - .execute(&mut *transaction) - .await?; - - Ok(()) + }) } } @@ -89,42 +120,70 @@ pub struct VersionFileBuilder { } impl VersionFileBuilder { - pub async fn insert( - self, + pub async fn insert_many( + version_files: Vec, version_id: VersionId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { let file_id = generate_file_id(&mut *transaction).await?; + let (file_ids, version_ids, urls, filenames, primary, sizes, file_types): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = version_files + .iter() + .map(|f| { + ( + file_id.0, + version_id.0, + f.url.clone(), + f.filename.clone(), + f.primary, + f.size as i32, + f.file_type.map(|x| x.to_string()), + ) + }) + .multiunzip(); sqlx::query!( " INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type) - VALUES ($1, $2, $3, $4, $5, $6, $7) + SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::varchar[], $4::varchar[], $5::bool[], $6::integer[], $7::varchar[]) ", - file_id as FileId, - version_id as VersionId, - self.url, - self.filename, - self.primary, - self.size as i32, - self.file_type.map(|x| x.as_str()), + &file_ids[..], + &version_ids[..], + &urls[..], + &filenames[..], + &primary[..], + &sizes[..], + &file_types[..] as &[Option], ) .execute(&mut *transaction) .await?; - for hash in self.hashes { - sqlx::query!( - " - INSERT INTO hashes (file_id, algorithm, hash) - VALUES ($1, $2, $3) - ", - file_id as FileId, - hash.algorithm, - hash.hash, - ) - .execute(&mut *transaction) - .await?; - } + let (file_ids, algorithms, hashes): (Vec<_>, Vec<_>, Vec<_>) = version_files + .into_iter() + .flat_map(|f| { + f.hashes + .into_iter() + .map(|h| (file_id.0, h.algorithm, h.hash)) + }) + .multiunzip(); + sqlx::query!( + " + INSERT INTO hashes (file_id, algorithm, hash) + SELECT * FROM UNNEST($1::bigint[], $2::varchar[], $3::bytea[]) + ", + &file_ids[..], + &algorithms[..], + &hashes[..], + ) + .execute(&mut *transaction) + .await?; Ok(file_id) } @@ -170,41 +229,91 @@ impl VersionBuilder { .execute(&mut *transaction) .await?; - for file in self.files { - file.insert(self.version_id, transaction).await?; - } + let VersionBuilder { + dependencies, + loaders, + game_versions, + files, + version_id, + .. + } = self; + VersionFileBuilder::insert_many(files, self.version_id, transaction).await?; + + DependencyBuilder::insert_many(dependencies, self.version_id, transaction).await?; + + let loader_versions = loaders + .iter() + .map(|l| LoaderVersion::new(*l, version_id)) + .collect_vec(); + LoaderVersion::insert_many(loader_versions, &mut *transaction).await?; + + let game_version_versions = game_versions + .iter() + .map(|v| VersionVersion::new(*v, version_id)) + .collect_vec(); + VersionVersion::insert_many(game_version_versions, &mut *transaction).await?; - for dependency in self.dependencies { - dependency.insert(self.version_id, transaction).await?; - } + Ok(self.version_id) + } +} - for loader in self.loaders.clone() { - sqlx::query!( - " - INSERT INTO loaders_versions (loader_id, version_id) - VALUES ($1, $2) - ", - loader as LoaderId, - self.version_id as VersionId, - ) - .execute(&mut *transaction) - .await?; - } +#[derive(derive_new::new)] +pub struct LoaderVersion { + pub loader_id: LoaderId, + pub version_id: VersionId, +} - for game_version in self.game_versions.clone() { - sqlx::query!( - " - INSERT INTO game_versions_versions (game_version_id, joining_version_id) - VALUES ($1, $2) - ", - game_version as GameVersionId, - self.version_id as VersionId, - ) - .execute(&mut *transaction) - .await?; - } +impl LoaderVersion { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (loader_ids, version_ids): (Vec<_>, Vec<_>) = items + .iter() + .map(|l| (l.loader_id.0, l.version_id.0)) + .unzip(); + sqlx::query!( + " + INSERT INTO loaders_versions (loader_id, version_id) + SELECT * FROM UNNEST($1::integer[], $2::bigint[]) + ", + &loader_ids[..], + &version_ids[..], + ) + .execute(&mut *transaction) + .await?; - Ok(self.version_id) + Ok(()) + } +} + +#[derive(derive_new::new)] +pub struct VersionVersion { + pub game_version_id: GameVersionId, + pub joining_version_id: VersionId, +} + +impl VersionVersion { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (game_version_ids, version_ids): (Vec<_>, Vec<_>) = items + .into_iter() + .map(|i| (i.game_version_id.0, i.joining_version_id.0)) + .unzip(); + sqlx::query!( + " + INSERT INTO game_versions_versions (game_version_id, joining_version_id) + SELECT * FROM UNNEST($1::integer[], $2::bigint[]) + ", + &game_version_ids[..], + &version_ids[..], + ) + .execute(&mut *transaction) + .await?; + + Ok(()) } } diff --git a/src/queue/analytics.rs b/src/queue/analytics.rs index 6ff153a4..78a63ebc 100644 --- a/src/queue/analytics.rs +++ b/src/queue/analytics.rs @@ -1,6 +1,13 @@ use crate::models::analytics::{Download, PageView, Playtime}; use dashmap::DashSet; +#[cfg(test)] +mod tests; + +const VIEWS_TABLENAME: &str = "views"; +const DOWNLOADS_TABLENAME: &str = "downloads"; +const PLAYTIME_TABLENAME: &str = "playtime"; + pub struct AnalyticsQueue { views_queue: DashSet, downloads_queue: DashSet, @@ -17,54 +24,50 @@ impl AnalyticsQueue { } } - pub async fn add_view(&self, page_view: PageView) { + pub fn add_view(&self, page_view: PageView) { self.views_queue.insert(page_view); } - pub async fn add_download(&self, download: Download) { + pub fn add_download(&self, download: Download) { self.downloads_queue.insert(download); } - pub async fn add_playtime(&self, playtime: Playtime) { + pub fn add_playtime(&self, playtime: Playtime) { self.playtime_queue.insert(playtime); } pub async fn index(&self, client: clickhouse::Client) -> Result<(), clickhouse::error::Error> { - let views_queue = self.views_queue.clone(); - self.views_queue.clear(); - - let downloads_queue = self.downloads_queue.clone(); - self.downloads_queue.clear(); - - let playtime_queue = self.playtime_queue.clone(); - self.playtime_queue.clear(); - - if !views_queue.is_empty() || !downloads_queue.is_empty() || !playtime_queue.is_empty() { - let mut views = client.insert("views")?; - - for view in views_queue { - views.write(&view).await?; - } + Self::index_queue(&client, &self.views_queue, VIEWS_TABLENAME).await?; + Self::index_queue(&client, &self.downloads_queue, DOWNLOADS_TABLENAME).await?; + Self::index_queue(&client, &self.playtime_queue, PLAYTIME_TABLENAME).await?; - views.end().await?; - - let mut downloads = client.insert("downloads")?; - - for download in downloads_queue { - downloads.write(&download).await?; - } + Ok(()) + } - downloads.end().await?; + async fn index_queue( + client: &clickhouse::Client, + queue: &DashSet, + table_name: &str, + ) -> Result<(), clickhouse::error::Error> + where + T: serde::Serialize + Eq + std::hash::Hash + Clone + clickhouse::Row, + { + if queue.is_empty() { + return Ok(()); + } - let mut playtimes = client.insert("playtime")?; + let current_queue = queue.clone(); + queue.clear(); - for playtime in playtime_queue { - playtimes.write(&playtime).await?; - } + let mut inserter = client.inserter(table_name)?; - playtimes.end().await?; + for row in current_queue { + inserter.write(&row).await?; + inserter.commit().await?; } + inserter.end().await?; + Ok(()) } } diff --git a/src/queue/analytics/tests.rs b/src/queue/analytics/tests.rs new file mode 100644 index 00000000..52b3d8e1 --- /dev/null +++ b/src/queue/analytics/tests.rs @@ -0,0 +1,128 @@ +use futures::Future; +use uuid::Uuid; + +use super::*; +use crate::clickhouse::init_client_with_database; +use std::net::Ipv6Addr; + +#[tokio::test] +async fn test_indexing() { + with_test_clickhouse_db(|clickhouse_client| async move { + let analytics = AnalyticsQueue::new(); + + analytics.add_download(get_default_download()); + analytics.add_playtime(get_default_playtime()); + analytics.add_view(get_default_views()); + + analytics.index(clickhouse_client.clone()).await.unwrap(); + assert_table_counts(&clickhouse_client, 1, 1, 1).await; + + analytics.index(clickhouse_client.clone()).await.unwrap(); + assert_table_counts(&clickhouse_client, 1, 1, 1).await; + }) + .await; +} + +#[tokio::test] +async fn can_insert_many_downloads() { + with_test_clickhouse_db(|clickhouse_client| async move { + let analytics = AnalyticsQueue::new(); + let n_downloads = 100_000; + + for _ in 0..n_downloads { + analytics.add_download(get_default_download()); + } + + analytics.index(clickhouse_client.clone()).await.unwrap(); + assert_table_count(DOWNLOADS_TABLENAME, &clickhouse_client, n_downloads).await; + }) + .await; +} + +async fn assert_table_counts( + client: &clickhouse::Client, + downloads: u64, + playtimes: u64, + views: u64, +) { + assert_table_count(DOWNLOADS_TABLENAME, client, downloads).await; + assert_table_count(PLAYTIME_TABLENAME, client, playtimes).await; + assert_table_count(VIEWS_TABLENAME, client, views).await; +} + +async fn assert_table_count(table_name: &str, client: &clickhouse::Client, expected_count: u64) { + let count = client + .query(&format!("SELECT COUNT(*) from {table_name}")) + .fetch_one::() + .await + .unwrap(); + assert_eq!(expected_count, count); +} + +async fn with_test_clickhouse_db(f: impl FnOnce(clickhouse::Client) -> Fut) +where + Fut: Future, +{ + let db_name = format!("test_{}", uuid::Uuid::new_v4().as_simple()); + println!("Clickhouse test db: {}", db_name); + let clickhouse_client = init_client_with_database(&db_name) + .await + .expect("A real clickhouse instance should be running locally"); + + f(clickhouse_client.clone()).await; + + clickhouse_client + .query(&format!("DROP DATABASE IF EXISTS {db_name}")) + .execute() + .await + .unwrap(); +} + +fn get_default_download() -> Download { + Download { + id: Uuid::new_v4(), + recorded: Default::default(), + domain: Default::default(), + site_path: Default::default(), + user_id: Default::default(), + project_id: Default::default(), + version_id: Default::default(), + ip: get_default_ipv6(), + country: Default::default(), + user_agent: Default::default(), + headers: Default::default(), + } +} + +fn get_default_playtime() -> Playtime { + Playtime { + id: Uuid::new_v4(), + recorded: Default::default(), + seconds: Default::default(), + user_id: Default::default(), + project_id: Default::default(), + version_id: Default::default(), + loader: Default::default(), + game_version: Default::default(), + parent: Default::default(), + } +} + +fn get_default_views() -> PageView { + PageView { + id: Uuid::new_v4(), + recorded: Default::default(), + domain: Default::default(), + site_path: Default::default(), + user_id: Default::default(), + project_id: Default::default(), + ip: get_default_ipv6(), + country: Default::default(), + user_agent: Default::default(), + headers: Default::default(), + } +} + +fn get_default_ipv6() -> Ipv6Addr { + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0) +} diff --git a/src/queue/payouts.rs b/src/queue/payouts.rs index 73924e9d..737fe31b 100644 --- a/src/queue/payouts.rs +++ b/src/queue/payouts.rs @@ -355,6 +355,8 @@ pub async fn process_payout( }; let mut clear_cache_users = Vec::new(); + let (mut insert_user_ids, mut insert_project_ids, mut insert_payouts, mut insert_starts) = + (Vec::new(), Vec::new(), Vec::new(), Vec::new()); for (id, project) in projects_map { if let Some(value) = &multipliers.values.get(&(id as u64)) { let project_multiplier: Decimal = @@ -367,18 +369,10 @@ pub async fn process_payout( let payout: Decimal = payout * project_multiplier * (split / sum_splits); if payout > Decimal::ZERO { - sqlx::query!( - " - INSERT INTO payouts_values (user_id, mod_id, amount, created) - VALUES ($1, $2, $3, $4) - ", - user_id, - id, - payout, - start - ) - .execute(&mut *transaction) - .await?; + insert_user_ids.push(user_id); + insert_project_ids.push(id); + insert_payouts.push(payout); + insert_starts.push(start); sqlx::query!( " @@ -399,6 +393,19 @@ pub async fn process_payout( } } + sqlx::query!( + " + INSERT INTO payouts_values (user_id, mod_id, amount, created) + SELECT * FROM UNNEST ($1::bigint[], $2::bigint[], $3::numeric[], $4::timestamptz[]) + ", + &insert_user_ids[..], + &insert_project_ids[..], + &insert_payouts[..], + &insert_starts[..] + ) + .execute(&mut *transaction) + .await?; + if !clear_cache_users.is_empty() { crate::database::models::User::clear_caches( &clear_cache_users diff --git a/src/routes/analytics.rs b/src/routes/analytics.rs index 5e06b4c5..932cc8b7 100644 --- a/src/routes/analytics.rs +++ b/src/routes/analytics.rs @@ -150,7 +150,7 @@ pub async fn page_view_ingest( view.user_id = user.id.0; } - analytics_queue.add_view(view).await; + analytics_queue.add_view(view); Ok(HttpResponse::NoContent().body("")) } @@ -202,19 +202,17 @@ pub async fn playtime_ingest( } if let Some(version) = versions.iter().find(|x| id == x.inner.id.into()) { - analytics_queue - .add_playtime(Playtime { - id: Default::default(), - recorded: Utc::now().timestamp_nanos() / 100_000, - seconds: playtime.seconds as u64, - user_id: user.id.0, - project_id: version.inner.project_id.0 as u64, - version_id: version.inner.id.0 as u64, - loader: playtime.loader, - game_version: playtime.game_version, - parent: playtime.parent.map(|x| x.0).unwrap_or(0), - }) - .await; + analytics_queue.add_playtime(Playtime { + id: Default::default(), + recorded: Utc::now().timestamp_nanos() / 100_000, + seconds: playtime.seconds as u64, + user_id: user.id.0, + project_id: version.inner.project_id.0 as u64, + version_id: version.inner.id.0 as u64, + loader: playtime.loader, + game_version: playtime.game_version, + parent: playtime.parent.map(|x| x.0).unwrap_or(0), + }); } } diff --git a/src/routes/v2/admin.rs b/src/routes/v2/admin.rs index be4db052..439e83ec 100644 --- a/src/routes/v2/admin.rs +++ b/src/routes/v2/admin.rs @@ -108,40 +108,36 @@ pub async fn count_download( let ip = crate::routes::analytics::convert_to_ip_v6(&download_body.ip) .unwrap_or_else(|_| Ipv4Addr::new(127, 0, 0, 1).to_ipv6_mapped()); - analytics_queue - .add_download(Download { - id: Uuid::new_v4(), - recorded: Utc::now().timestamp_nanos() / 100_000, - domain: url.host_str().unwrap_or_default().to_string(), - site_path: url.path().to_string(), - user_id: user - .and_then(|(scopes, x)| { - if scopes.contains(Scopes::PERFORM_ANALYTICS) { - Some(x.id.0 as u64) - } else { - None - } - }) - .unwrap_or(0), - project_id: project_id as u64, - version_id: version_id as u64, - ip, - country: maxmind.query(ip).await.unwrap_or_default(), - user_agent: download_body - .headers - .get("user-agent") - .cloned() - .unwrap_or_default(), - headers: download_body - .headers - .clone() - .into_iter() - .filter(|x| { - !crate::routes::analytics::FILTERED_HEADERS.contains(&&*x.0.to_lowercase()) - }) - .collect(), - }) - .await; + analytics_queue.add_download(Download { + id: Uuid::new_v4(), + recorded: Utc::now().timestamp_nanos() / 100_000, + domain: url.host_str().unwrap_or_default().to_string(), + site_path: url.path().to_string(), + user_id: user + .and_then(|(scopes, x)| { + if scopes.contains(Scopes::PERFORM_ANALYTICS) { + Some(x.id.0 as u64) + } else { + None + } + }) + .unwrap_or(0), + project_id: project_id as u64, + version_id: version_id as u64, + ip, + country: maxmind.query(ip).await.unwrap_or_default(), + user_agent: download_body + .headers + .get("user-agent") + .cloned() + .unwrap_or_default(), + headers: download_body + .headers + .clone() + .into_iter() + .filter(|x| !crate::routes::analytics::FILTERED_HEADERS.contains(&&*x.0.to_lowercase())) + .collect(), + }); Ok(HttpResponse::NoContent().body("")) } diff --git a/src/routes/v2/collections.rs b/src/routes/v2/collections.rs index 01372b0e..89778754 100644 --- a/src/routes/v2/collections.rs +++ b/src/routes/v2/collections.rs @@ -15,6 +15,7 @@ use crate::{database, models}; use actix_web::web::Data; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; use chrono::Utc; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use std::sync::Arc; @@ -301,6 +302,11 @@ pub async fn collection_edit( .execute(&mut *transaction) .await?; + let collection_item_ids = new_project_ids + .iter() + .map(|_| collection_item.id.0) + .collect_vec(); + let mut validated_project_ids = Vec::new(); for project_id in new_project_ids { let project = database::models::Project::get(project_id, &**pool, &redis) .await? @@ -309,20 +315,20 @@ pub async fn collection_edit( "The specified project {project_id} does not exist!" )) })?; - - // Insert- don't throw an error if it already exists - sqlx::query!( - " - INSERT INTO collections_mods (collection_id, mod_id) - VALUES ($1, $2) - ON CONFLICT DO NOTHING - ", - collection_item.id as database::models::ids::CollectionId, - project.inner.id as database::models::ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; + validated_project_ids.push(project.inner.id.0); } + // Insert- don't throw an error if it already exists + sqlx::query!( + " + INSERT INTO collections_mods (collection_id, mod_id) + SELECT * FROM UNNEST ($1::int8[], $2::int8[]) + ON CONFLICT DO NOTHING + ", + &collection_item_ids[..], + &validated_project_ids[..], + ) + .execute(&mut *transaction) + .await?; } database::models::Collection::clear_cache(collection_item.id, &redis).await?; diff --git a/src/routes/v2/projects.rs b/src/routes/v2/projects.rs index 50967487..1a0e4c69 100644 --- a/src/routes/v2/projects.rs +++ b/src/routes/v2/projects.rs @@ -2,6 +2,7 @@ use crate::auth::{filter_authorized_projects, get_user_from_headers, is_authoriz use crate::database; use crate::database::models::image_item; use crate::database::models::notification_item::NotificationBuilder; +use crate::database::models::project_item::{GalleryItem, ModCategory}; use crate::database::models::thread_item::ThreadMessageBuilder; use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; @@ -31,6 +32,9 @@ use sqlx::PgPool; use std::sync::Arc; use validator::Validate; +use database::models as db_models; +use db_models::ids as db_ids; + pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(project_search); cfg.service(projects_get); @@ -97,11 +101,11 @@ pub async fn random_projects_get( .collect::>(), ) .fetch_many(&**pool) - .try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) }) + .try_filter_map(|e| async { Ok(e.right().map(|m| db_ids::ProjectId(m.id))) }) .try_collect::>() .await?; - let projects_data = database::models::Project::get_many_ids(&project_ids, &**pool, &redis) + let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis) .await? .into_iter() .map(Project::from) @@ -124,7 +128,7 @@ pub async fn projects_get( session_queue: web::Data, ) -> Result { let ids = serde_json::from_str::>(&ids.ids)?; - let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?; + let projects_data = db_models::Project::get_many(&ids, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, @@ -152,7 +156,7 @@ pub async fn project_get( ) -> Result { let string = info.into_inner().0; - let project_data = database::models::Project::get(&string, &**pool, &redis).await?; + let project_data = db_models::Project::get(&string, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, &**pool, @@ -181,7 +185,7 @@ pub async fn project_get_check( ) -> Result { let slug = info.into_inner().0; - let project_data = database::models::Project::get(&slug, &**pool, &redis).await?; + let project_data = db_models::Project::get(&slug, &**pool, &redis).await?; if let Some(project) = project_data { Ok(HttpResponse::Ok().json(json! ({ @@ -208,7 +212,7 @@ pub async fn dependency_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, @@ -247,7 +251,7 @@ pub async fn dependency_list( let dep_version_ids = dependencies .iter() .filter_map(|x| x.0) - .collect::>(); + .collect::>(); let (projects_result, versions_result) = futures::future::try_join( database::Project::get_many_ids(&project_ids, &**pool, &redis), database::Version::get_many(&dep_version_ids, &**pool, &redis), @@ -399,13 +403,13 @@ pub async fn project_edit( .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; if let Some(project_item) = result { let id = project_item.inner.id; let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -436,7 +440,7 @@ pub async fn project_edit( WHERE (id = $2) ", title.trim(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -457,7 +461,7 @@ pub async fn project_edit( WHERE (id = $2) ", description, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -494,7 +498,7 @@ pub async fn project_edit( SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW() WHERE (id = $1) ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -505,7 +509,7 @@ pub async fn project_edit( SET show_in_mod_inbox = FALSE WHERE id = $1 ", - project_item.thread_id as database::models::ids::ThreadId, + project_item.thread_id as db_ids::ThreadId, ) .execute(&mut *transaction) .await?; @@ -518,7 +522,7 @@ pub async fn project_edit( SET approved = NOW() WHERE id = $1 AND approved IS NULL ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -542,7 +546,7 @@ pub async fn project_edit( SET webhook_sent = TRUE WHERE id = $1 ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -580,12 +584,10 @@ pub async fn project_edit( FROM team_members tm WHERE tm.team_id = $1 AND tm.accepted ", - project_item.inner.team_id as database::models::ids::TeamId + project_item.inner.team_id as db_ids::TeamId ) .fetch_many(&mut *transaction) - .try_filter_map(|e| async { - Ok(e.right().map(|c| database::models::UserId(c.id))) - }) + .try_filter_map(|e| async { Ok(e.right().map(|c| db_models::UserId(c.id))) }) .try_collect::>() .await?; @@ -618,7 +620,7 @@ pub async fn project_edit( WHERE (id = $2) ", status.as_str(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -652,7 +654,7 @@ pub async fn project_edit( WHERE (id = $2) ", requested_status.map(|x| x.as_str()), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -665,7 +667,7 @@ pub async fn project_edit( DELETE FROM mods_categories WHERE joining_mod_id = $1 AND is_additional = FALSE ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -677,7 +679,7 @@ pub async fn project_edit( DELETE FROM mods_categories WHERE joining_mod_id = $1 AND is_additional = TRUE ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -685,67 +687,25 @@ pub async fn project_edit( } if let Some(categories) = &new_project.categories { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the categories of this project!" - .to_string(), - )); - } - - for category in categories { - let category_id = - database::models::categories::Category::get_id(category, &mut *transaction) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, FALSE) - ", - id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } + edit_project_categories( + categories, + &perms, + id as db_ids::ProjectId, + false, + &mut transaction, + ) + .await?; } if let Some(categories) = &new_project.additional_categories { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the additional categories of this project!" - .to_string(), - )); - } - - for category in categories { - let category_id = - database::models::categories::Category::get_id(category, &mut *transaction) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, TRUE) - ", - id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } + edit_project_categories( + categories, + &perms, + id as db_ids::ProjectId, + true, + &mut transaction, + ) + .await?; } if let Some(issues_url) = &new_project.issues_url { @@ -763,7 +723,7 @@ pub async fn project_edit( WHERE (id = $2) ", issues_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -784,7 +744,7 @@ pub async fn project_edit( WHERE (id = $2) ", source_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -805,7 +765,7 @@ pub async fn project_edit( WHERE (id = $2) ", wiki_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -826,7 +786,7 @@ pub async fn project_edit( WHERE (id = $2) ", license_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -847,7 +807,7 @@ pub async fn project_edit( WHERE (id = $2) ", discord_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -905,7 +865,7 @@ pub async fn project_edit( WHERE (id = $2) ", Some(slug), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -919,12 +879,10 @@ pub async fn project_edit( )); } - let side_type_id = database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = + db_models::categories::SideType::get_id(new_side.as_str(), &mut *transaction) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -932,8 +890,8 @@ pub async fn project_edit( SET client_side = $1 WHERE (id = $2) ", - side_type_id as database::models::SideTypeId, - id as database::models::ids::ProjectId, + side_type_id as db_models::SideTypeId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -947,12 +905,10 @@ pub async fn project_edit( )); } - let side_type_id = database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = + db_models::categories::SideType::get_id(new_side.as_str(), &mut *transaction) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -960,8 +916,8 @@ pub async fn project_edit( SET server_side = $1 WHERE (id = $2) ", - side_type_id as database::models::SideTypeId, - id as database::models::ids::ProjectId, + side_type_id as db_models::SideTypeId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -992,7 +948,7 @@ pub async fn project_edit( WHERE (id = $2) ", license, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1010,13 +966,13 @@ pub async fn project_edit( DELETE FROM mods_donations WHERE joining_mod_id = $1 ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; for donation in donations { - let platform_id = database::models::categories::DonationPlatform::get_id( + let platform_id = db_models::categories::DonationPlatform::get_id( &donation.id, &mut *transaction, ) @@ -1033,8 +989,8 @@ pub async fn project_edit( INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) VALUES ($1, $2, $3) ", - id as database::models::ids::ProjectId, - platform_id as database::models::ids::DonationPlatformId, + id as db_ids::ProjectId, + platform_id as db_ids::DonationPlatformId, donation.url ) .execute(&mut *transaction) @@ -1059,7 +1015,7 @@ pub async fn project_edit( WHERE (id = $2) ", moderation_message.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1083,7 +1039,7 @@ pub async fn project_edit( WHERE (id = $2) ", moderation_message_body.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1104,7 +1060,7 @@ pub async fn project_edit( WHERE (id = $2) ", body, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1136,7 +1092,7 @@ pub async fn project_edit( WHERE (id = $2) ", monetization_status.as_str(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1154,7 +1110,7 @@ pub async fn project_edit( }; img::delete_unused_images(context, checkable_strings, &mut transaction, &redis).await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1174,6 +1130,13 @@ pub async fn project_edit( } } +#[derive(derive_new::new)] +pub struct CategoryChanges<'a> { + pub categories: &'a Option>, + pub add_categories: &'a Option>, + pub remove_categories: &'a Option>, +} + #[derive(Deserialize, Validate)] pub struct BulkEditProject { #[validate(length(max = 3))] @@ -1260,14 +1223,12 @@ pub async fn projects_edit( .validate() .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; - let project_ids: Vec = - serde_json::from_str::>(&ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect(); + let project_ids: Vec = serde_json::from_str::>(&ids.ids)? + .into_iter() + .map(|x| x.into()) + .collect(); - let projects_data = - database::models::Project::get_many_ids(&project_ids, &**pool, &redis).await?; + let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis).await?; if let Some(id) = project_ids .iter() @@ -1282,31 +1243,27 @@ pub async fn projects_edit( let team_ids = projects_data .iter() .map(|x| x.inner.team_id) - .collect::>(); + .collect::>(); let team_members = - database::models::TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?; + db_models::TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?; let organization_ids = projects_data .iter() .filter_map(|x| x.inner.organization_id) - .collect::>(); + .collect::>(); let organizations = - database::models::Organization::get_many_ids(&organization_ids, &**pool, &redis).await?; + db_models::Organization::get_many_ids(&organization_ids, &**pool, &redis).await?; let organization_team_ids = organizations .iter() .map(|x| x.team_id) - .collect::>(); - let organization_team_members = database::models::TeamMember::get_from_team_full_many( - &organization_team_ids, - &**pool, - &redis, - ) - .await?; + .collect::>(); + let organization_team_members = + db_models::TeamMember::get_from_team_full_many(&organization_team_ids, &**pool, &redis) + .await?; - let categories = database::models::categories::Category::list(&**pool, &redis).await?; - let donation_platforms = - database::models::categories::DonationPlatform::list(&**pool, &redis).await?; + let categories = db_models::categories::Category::list(&**pool, &redis).await?; + let donation_platforms = db_models::categories::DonationPlatform::list(&**pool, &redis).await?; let mut transaction = pool.begin().await?; @@ -1356,126 +1313,35 @@ pub async fn projects_edit( }; } - let mut set_categories = if let Some(categories) = bulk_edit_project.categories.clone() { - categories - } else { - project.categories.clone() - }; - - if let Some(delete_categories) = &bulk_edit_project.remove_categories { - for category in delete_categories { - if let Some(pos) = set_categories.iter().position(|x| x == category) { - set_categories.remove(pos); - } - } - } - - if let Some(add_categories) = &bulk_edit_project.add_categories { - for category in add_categories { - if set_categories.len() < 3 { - set_categories.push(category.clone()); - } else { - break; - } - } - } - - if set_categories != project.categories { - sqlx::query!( - " - DELETE FROM mods_categories - WHERE joining_mod_id = $1 AND is_additional = FALSE - ", - project.inner.id as database::models::ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - - for category in set_categories { - let category_id = categories - .iter() - .find(|x| x.category == category) - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })? - .id; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, FALSE) - ", - project.inner.id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } - } - - let mut set_additional_categories = - if let Some(categories) = bulk_edit_project.additional_categories.clone() { - categories - } else { - project.additional_categories.clone() - }; - - if let Some(delete_categories) = &bulk_edit_project.remove_additional_categories { - for category in delete_categories { - if let Some(pos) = set_additional_categories.iter().position(|x| x == category) { - set_additional_categories.remove(pos); - } - } - } - - if let Some(add_categories) = &bulk_edit_project.add_additional_categories { - for category in add_categories { - if set_additional_categories.len() < 256 { - set_additional_categories.push(category.clone()); - } else { - break; - } - } - } - - if set_additional_categories != project.additional_categories { - sqlx::query!( - " - DELETE FROM mods_categories - WHERE joining_mod_id = $1 AND is_additional = TRUE - ", - project.inner.id as database::models::ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - - for category in set_additional_categories { - let category_id = categories - .iter() - .find(|x| x.category == category) - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })? - .id; + bulk_edit_project_categories( + &categories, + &project.categories, + project.inner.id as db_ids::ProjectId, + CategoryChanges::new( + &bulk_edit_project.categories, + &bulk_edit_project.add_categories, + &bulk_edit_project.remove_categories, + ), + 3, + false, + &mut transaction, + ) + .await?; - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, TRUE) - ", - project.inner.id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } - } + bulk_edit_project_categories( + &categories, + &project.additional_categories, + project.inner.id as db_ids::ProjectId, + CategoryChanges::new( + &bulk_edit_project.additional_categories, + &bulk_edit_project.add_additional_categories, + &bulk_edit_project.remove_additional_categories, + ), + 256, + true, + &mut transaction, + ) + .await?; let project_donations: Vec = project .donation_urls @@ -1514,7 +1380,7 @@ pub async fn projects_edit( DELETE FROM mods_donations WHERE joining_mod_id = $1 ", - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1536,8 +1402,8 @@ pub async fn projects_edit( INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) VALUES ($1, $2, $3) ", - project.inner.id as database::models::ids::ProjectId, - platform_id as database::models::ids::DonationPlatformId, + project.inner.id as db_ids::ProjectId, + platform_id as db_ids::DonationPlatformId, donation.url ) .execute(&mut *transaction) @@ -1553,7 +1419,7 @@ pub async fn projects_edit( WHERE (id = $2) ", issues_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1567,7 +1433,7 @@ pub async fn projects_edit( WHERE (id = $2) ", source_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1581,7 +1447,7 @@ pub async fn projects_edit( WHERE (id = $2) ", wiki_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1595,14 +1461,13 @@ pub async fn projects_edit( WHERE (id = $2) ", discord_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; } - database::models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis) - .await?; + db_models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis).await?; } transaction.commit().await?; @@ -1610,6 +1475,96 @@ pub async fn projects_edit( Ok(HttpResponse::NoContent().body("")) } +pub async fn bulk_edit_project_categories( + all_db_categories: &[db_models::categories::Category], + project_categories: &Vec, + project_id: db_ids::ProjectId, + bulk_changes: CategoryChanges<'_>, + max_num_categories: usize, + is_additional: bool, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result<(), ApiError> { + let mut set_categories = if let Some(categories) = bulk_changes.categories.clone() { + categories + } else { + project_categories.clone() + }; + + if let Some(delete_categories) = &bulk_changes.remove_categories { + for category in delete_categories { + if let Some(pos) = set_categories.iter().position(|x| x == category) { + set_categories.remove(pos); + } + } + } + + if let Some(add_categories) = &bulk_changes.add_categories { + for category in add_categories { + if set_categories.len() < max_num_categories { + set_categories.push(category.clone()); + } else { + break; + } + } + } + + if &set_categories != project_categories { + sqlx::query!( + " + DELETE FROM mods_categories + WHERE joining_mod_id = $1 AND is_additional = $2 + ", + project_id as db_ids::ProjectId, + is_additional + ) + .execute(&mut *transaction) + .await?; + + let mut mod_categories = Vec::new(); + for category in set_categories { + let category_id = all_db_categories + .iter() + .find(|x| x.category == category) + .ok_or_else(|| { + ApiError::InvalidInput(format!("Category {} does not exist.", category.clone())) + })? + .id; + mod_categories.push(ModCategory::new(project_id, category_id, is_additional)); + } + ModCategory::insert_many(mod_categories, &mut *transaction).await?; + } + + Ok(()) +} + +pub async fn edit_project_categories( + categories: &Vec, + perms: &ProjectPermissions, + project_id: db_ids::ProjectId, + additional: bool, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result<(), ApiError> { + if !perms.contains(ProjectPermissions::EDIT_DETAILS) { + let additional_str = if additional { "additional " } else { "" }; + return Err(ApiError::CustomAuthentication(format!( + "You do not have the permissions to edit the {additional_str}categories of this project!" + ))); + } + + let mut mod_categories = Vec::new(); + for category in categories { + let category_id = db_models::categories::Category::get_id(category, &mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!("Category {} does not exist.", category.clone())) + })?; + mod_categories.push(ModCategory::new(project_id, category_id, additional)); + } + ModCategory::insert_many(mod_categories, &mut *transaction).await?; + + Ok(()) +} + #[derive(Deserialize)] pub struct SchedulingData { pub time: DateTime, @@ -1648,11 +1603,11 @@ pub async fn project_schedule( } let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; if let Some(project_item) = result { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1692,12 +1647,12 @@ pub async fn project_schedule( ", ProjectStatus::Scheduled.as_str(), scheduling_data.time, - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&**pool) .await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1741,7 +1696,7 @@ pub async fn project_icon_edit( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1749,7 +1704,7 @@ pub async fn project_icon_edit( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1810,12 +1765,12 @@ pub async fn project_icon_edit( ", format!("{}/{}", cdn_url, upload_data.file_name), color.map(|x| x as i32), - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1854,7 +1809,7 @@ pub async fn delete_project_icon( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1862,7 +1817,7 @@ pub async fn delete_project_icon( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1906,18 +1861,13 @@ pub async fn delete_project_icon( SET icon_url = NULL, color = NULL WHERE (id = $1) ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -1963,7 +1913,7 @@ pub async fn add_gallery_item( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1977,7 +1927,7 @@ pub async fn add_gallery_item( if !user.role.is_admin() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2040,25 +1990,24 @@ pub async fn add_gallery_item( SET featured = $2 WHERE mod_id = $1 ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, false, ) .execute(&mut *transaction) .await?; } - database::models::project_item::GalleryItem { + let gallery_item = vec![db_models::project_item::GalleryItem { image_url: file_url, featured: item.featured, title: item.title, description: item.description, created: Utc::now(), ordering: item.ordering.unwrap_or(0), - } - .insert(project_item.inner.id, &mut transaction) - .await?; + }]; + GalleryItem::insert_many(gallery_item, project_item.inner.id, &mut transaction).await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -2122,7 +2071,7 @@ pub async fn edit_gallery_item( item.validate() .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2130,7 +2079,7 @@ pub async fn edit_gallery_item( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2185,7 +2134,7 @@ pub async fn edit_gallery_item( SET featured = $2 WHERE mod_id = $1 ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, false, ) .execute(&mut *transaction) @@ -2244,13 +2193,8 @@ pub async fn edit_gallery_item( .await?; } - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -2283,7 +2227,7 @@ pub async fn delete_gallery_item( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2291,7 +2235,7 @@ pub async fn delete_gallery_item( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2356,13 +2300,8 @@ pub async fn delete_gallery_item( .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -2389,7 +2328,7 @@ pub async fn project_delete( .1; let string = info.into_inner().0; - let project = database::models::Project::get(&string, &**pool, &redis) + let project = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2397,7 +2336,7 @@ pub async fn project_delete( if !user.role.is_admin() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project.inner, user.id.into(), &**pool, @@ -2429,8 +2368,7 @@ pub async fn project_delete( let context = ImageContext::Project { project_id: Some(project.inner.id.into()), }; - let uploaded_images = - database::models::Image::get_many_contexted(context, &mut transaction).await?; + let uploaded_images = db_models::Image::get_many_contexted(context, &mut transaction).await?; for image in uploaded_images { image_item::Image::remove(image.id, &mut transaction, &redis).await?; } @@ -2440,13 +2378,12 @@ pub async fn project_delete( DELETE FROM collections_mods WHERE mod_id = $1 ", - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - let result = - database::models::Project::remove(project.inner.id, &mut transaction, &redis).await?; + let result = db_models::Project::remove(project.inner.id, &mut transaction, &redis).await?; transaction.commit().await?; @@ -2478,14 +2415,14 @@ pub async fn project_follow( .1; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis) + let result = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) })?; - let user_id: database::models::ids::UserId = user.id.into(); - let project_id: database::models::ids::ProjectId = result.inner.id; + let user_id: db_ids::UserId = user.id.into(); + let project_id: db_ids::ProjectId = result.inner.id; if !is_authorized(&result.inner, &Some(user), &pool).await? { return Ok(HttpResponse::NotFound().body("")); @@ -2495,8 +2432,8 @@ pub async fn project_follow( " SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .fetch_one(&**pool) .await? @@ -2512,7 +2449,7 @@ pub async fn project_follow( SET follows = follows + 1 WHERE id = $1 ", - project_id as database::models::ids::ProjectId, + project_id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -2522,8 +2459,8 @@ pub async fn project_follow( INSERT INTO mod_follows (follower_id, mod_id) VALUES ($1, $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .execute(&mut *transaction) .await?; @@ -2557,21 +2494,21 @@ pub async fn project_unfollow( .1; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis) + let result = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) })?; - let user_id: database::models::ids::UserId = user.id.into(); + let user_id: db_ids::UserId = user.id.into(); let project_id = result.inner.id; let following = sqlx::query!( " SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .fetch_one(&**pool) .await? @@ -2587,7 +2524,7 @@ pub async fn project_unfollow( SET follows = follows - 1 WHERE id = $1 ", - project_id as database::models::ids::ProjectId, + project_id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -2597,8 +2534,8 @@ pub async fn project_unfollow( DELETE FROM mod_follows WHERE follower_id = $1 AND mod_id = $2 ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .execute(&mut *transaction) .await?; diff --git a/src/routes/v2/version_creation.rs b/src/routes/v2/version_creation.rs index 80fc895d..9c7b8611 100644 --- a/src/routes/v2/version_creation.rs +++ b/src/routes/v2/version_creation.rs @@ -725,9 +725,7 @@ async fn upload_file_to_version_inner( "At least one file must be specified".to_string(), )); } else { - for file_builder in file_builders { - file_builder.insert(version_id, &mut *transaction).await?; - } + VersionFileBuilder::insert_many(file_builders, version_id, &mut *transaction).await?; } // Clear version cache diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index cfaa9da4..218706e9 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -3,6 +3,7 @@ use crate::auth::{ filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version, }; use crate::database; +use crate::database::models::version_item::{DependencyBuilder, LoaderVersion, VersionVersion}; use crate::database::models::{image_item, Organization}; use crate::database::redis::RedisPool; use crate::models; @@ -450,11 +451,12 @@ pub async fn version_edit( }) .collect::>(); - for dependency in builders { - dependency - .insert(version_item.inner.id, &mut transaction) - .await?; - } + DependencyBuilder::insert_many( + builders, + version_item.inner.id, + &mut transaction, + ) + .await?; } } } @@ -469,6 +471,7 @@ pub async fn version_edit( .execute(&mut *transaction) .await?; + let mut version_versions = Vec::new(); for game_version in game_versions { let game_version_id = database::models::categories::GameVersion::get_id( &game_version.0, @@ -481,17 +484,9 @@ pub async fn version_edit( ) })?; - sqlx::query!( - " - INSERT INTO game_versions_versions (game_version_id, joining_version_id) - VALUES ($1, $2) - ", - game_version_id as database::models::ids::GameVersionId, - id as database::models::ids::VersionId, - ) - .execute(&mut *transaction) - .await?; + version_versions.push(VersionVersion::new(game_version_id, id)); } + VersionVersion::insert_many(version_versions, &mut transaction).await?; database::models::Project::update_game_versions( version_item.inner.project_id, @@ -510,6 +505,7 @@ pub async fn version_edit( .execute(&mut *transaction) .await?; + let mut loader_versions = Vec::new(); for loader in loaders { let loader_id = database::models::categories::Loader::get_id(&loader.0, &mut *transaction) @@ -519,18 +515,9 @@ pub async fn version_edit( "No database entry for loader provided.".to_string(), ) })?; - - sqlx::query!( - " - INSERT INTO loaders_versions (loader_id, version_id) - VALUES ($1, $2) - ", - loader_id as database::models::ids::LoaderId, - id as database::models::ids::VersionId, - ) - .execute(&mut *transaction) - .await?; + loader_versions.push(LoaderVersion::new(loader_id, id)); } + LoaderVersion::insert_many(loader_versions, &mut transaction).await?; database::models::Project::update_loaders( version_item.inner.project_id, diff --git a/tests/common/database.rs b/tests/common/database.rs index 63535125..483a44d9 100644 --- a/tests/common/database.rs +++ b/tests/common/database.rs @@ -29,6 +29,7 @@ pub const USER_USER_PAT: &str = "mrp_patuser"; pub const FRIEND_USER_PAT: &str = "mrp_patfriend"; pub const ENEMY_USER_PAT: &str = "mrp_patenemy"; +#[derive(Clone)] pub struct TemporaryDatabase { pub pool: PgPool, pub redis_pool: RedisPool, @@ -75,10 +76,14 @@ impl TemporaryDatabase { .await .expect("Connection to temporary database failed"); + println!("Running migrations on temporary database"); + // Performs migrations let migrations = sqlx::migrate!("./migrations"); migrations.run(&pool).await.expect("Migrations failed"); + println!("Migrations complete"); + // Gets new Redis pool let redis_pool = RedisPool::new(Some(temp_database_name.clone())); diff --git a/tests/common/dummy_data.rs b/tests/common/dummy_data.rs index d3cd9667..35cdc97d 100644 --- a/tests/common/dummy_data.rs +++ b/tests/common/dummy_data.rs @@ -13,6 +13,16 @@ use super::{ environment::TestEnvironment, }; +pub const DUMMY_CATEGORIES: &'static [&str] = &[ + "combat", + "decoration", + "economy", + "food", + "magic", + "mobs", + "optimization", +]; + pub struct DummyData { pub alpha_team_id: String, pub beta_team_id: String, diff --git a/tests/common/environment.rs b/tests/common/environment.rs index bcf5c686..cba05836 100644 --- a/tests/common/environment.rs +++ b/tests/common/environment.rs @@ -3,6 +3,19 @@ use super::{database::TemporaryDatabase, dummy_data}; use crate::common::setup; use actix_web::{dev::ServiceResponse, test, App}; +use futures::Future; + +pub async fn with_test_environment(f: impl FnOnce(TestEnvironment) -> Fut) +where + Fut: Future, +{ + let test_env = TestEnvironment::build_with_dummy().await; + let db = test_env.db.clone(); + + f(test_env).await; + + db.cleanup().await; +} // A complete test environment, with a test actix app and a database. // Must be called in an #[actix_rt::test] context. It also simulates a diff --git a/tests/files/dummy_data.sql b/tests/files/dummy_data.sql index 59391f48..0583f8af 100644 --- a/tests/files/dummy_data.sql +++ b/tests/files/dummy_data.sql @@ -27,10 +27,20 @@ INSERT INTO loaders (id, loader) VALUES (1, 'fabric'); INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id) VALUES (1,1); INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id) VALUES (1,2); -INSERT INTO categories (id, category, project_type) VALUES (1, 'combat', 1); -INSERT INTO categories (id, category, project_type) VALUES (2, 'decoration', 1); -INSERT INTO categories (id, category, project_type) VALUES (3, 'economy', 1); +INSERT INTO categories (id, category, project_type) VALUES + (1, 'combat', 1), + (2, 'decoration', 1), + (3, 'economy', 1), + (4, 'food', 1), + (5, 'magic', 1), + (6, 'mobs', 1), + (7, 'optimization', 1); -INSERT INTO categories (id, category, project_type) VALUES (4, 'combat', 2); -INSERT INTO categories (id, category, project_type) VALUES (5, 'decoration', 2); -INSERT INTO categories (id, category, project_type) VALUES (6, 'economy', 2); \ No newline at end of file +INSERT INTO categories (id, category, project_type) VALUES + (101, 'combat', 2), + (102, 'decoration', 2), + (103, 'economy', 2), + (104, 'food', 2), + (105, 'magic', 2), + (106, 'mobs', 2), + (107, 'optimization', 2); \ No newline at end of file diff --git a/tests/project.rs b/tests/project.rs index 215bcb66..c28804f9 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -1,10 +1,14 @@ +use actix_http::StatusCode; +use actix_web::dev::ServiceResponse; use actix_web::test; +use common::environment::with_test_environment; use labrinth::database::models::project_item::{PROJECTS_NAMESPACE, PROJECTS_SLUGS_NAMESPACE}; use labrinth::models::ids::base62_impl::parse_base62; use serde_json::json; use crate::common::database::*; +use crate::common::dummy_data::DUMMY_CATEGORIES; use crate::common::{actix::AppendsMultipart, environment::TestEnvironment}; // importing common module. @@ -403,7 +407,7 @@ pub async fn test_patch_project() { "title": "New successful title", "description": "New successful description", "body": "New successful body", - "categories": ["combat"], + "categories": [DUMMY_CATEGORIES[0]], "license_id": "MIT", "issues_url": "https://github.com", "discord_url": "https://discord.gg", @@ -441,7 +445,7 @@ pub async fn test_patch_project() { assert_eq!(body["title"], json!("New successful title")); assert_eq!(body["description"], json!("New successful description")); assert_eq!(body["body"], json!("New successful body")); - assert_eq!(body["categories"], json!(["combat"])); + assert_eq!(body["categories"], json!([DUMMY_CATEGORIES[0]])); assert_eq!(body["license"]["id"], json!("MIT")); assert_eq!(body["issues_url"], json!("https://github.com")); assert_eq!(body["discord_url"], json!("https://discord.gg")); @@ -457,5 +461,68 @@ pub async fn test_patch_project() { test_env.cleanup().await; } +#[actix_rt::test] +pub async fn test_bulk_edit_categories() { + with_test_environment(|test_env| async move { + let alpha_project_id = &test_env.dummy.as_ref().unwrap().alpha_project_id; + let beta_project_id = &test_env.dummy.as_ref().unwrap().beta_project_id; + + let req = test::TestRequest::patch() + .uri(&format!( + "/v2/projects?ids={}", + urlencoding::encode(&format!("[\"{alpha_project_id}\",\"{beta_project_id}\"]")) + )) + .append_header(("Authorization", ADMIN_USER_PAT)) + .set_json(json!({ + "categories": [DUMMY_CATEGORIES[0], DUMMY_CATEGORIES[3]], + "add_categories": [DUMMY_CATEGORIES[1], DUMMY_CATEGORIES[2]], + "remove_categories": [DUMMY_CATEGORIES[3]], + "additional_categories": [DUMMY_CATEGORIES[4], DUMMY_CATEGORIES[6]], + "add_additional_categories": [DUMMY_CATEGORIES[5]], + "remove_additional_categories": [DUMMY_CATEGORIES[6]], + })) + .to_request(); + let resp = test_env.call(req).await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let alpha_body = get_project_body(&test_env, &alpha_project_id, ADMIN_USER_PAT).await; + assert_eq!(alpha_body["categories"], json!(DUMMY_CATEGORIES[0..=2])); + assert_eq!( + alpha_body["additional_categories"], + json!(DUMMY_CATEGORIES[4..=5]) + ); + + let beta_body = get_project_body(&test_env, &beta_project_id, ADMIN_USER_PAT).await; + assert_eq!(beta_body["categories"], alpha_body["categories"]); + assert_eq!( + beta_body["additional_categories"], + alpha_body["additional_categories"], + ); + }) + .await; +} + +async fn get_project( + test_env: &TestEnvironment, + project_slug: &str, + user_pat: &str, +) -> ServiceResponse { + let req = test::TestRequest::get() + .uri(&format!("/v2/project/{project_slug}")) + .append_header(("Authorization", user_pat)) + .to_request(); + test_env.call(req).await +} + +async fn get_project_body( + test_env: &TestEnvironment, + project_slug: &str, + user_pat: &str, +) -> serde_json::Value { + let resp = get_project(test_env, project_slug, user_pat).await; + assert_eq!(resp.status(), StatusCode::OK); + test::read_body_json(resp).await +} + // TODO: Missing routes on projects // TODO: using permissions/scopes, can we SEE projects existence that we are not allowed to? (ie 401 instead of 404) From f1ff88f452c03c2e0900250422cd76afd83f02a9 Mon Sep 17 00:00:00 2001 From: BasiqueEvangelist Date: Thu, 12 Oct 2023 00:01:32 +0300 Subject: [PATCH 3/4] fix maven filters for versions with dashes (#725) Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> --- src/routes/maven.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/maven.rs b/src/routes/maven.rs index e5641106..ae19b7cf 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -191,7 +191,7 @@ async fn find_version( .partition::, _>(|el| db_loaders.contains(el)); let matched = all_versions - .into_iter() + .iter() .filter(|x| { let mut bool = x.inner.version_number == vnumber; @@ -206,7 +206,7 @@ async fn find_version( }) .collect::>(); - Ok(matched.get(0).cloned()) + Ok(matched.get(0).or_else(|| exact_matches.get(0)).copied().cloned()) } fn find_file<'a>( From 07ecd13554766402c2da94e152be1612537b9662 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:55:01 -0700 Subject: [PATCH 4/4] Switch to Trolley for Modrinth Payments (#727) * most of trolley * Switch to trolley for payments * run prepare * fix clippy * fix more * Fix most tests + bitflags * Update src/auth/flows.rs Co-authored-by: Jackson Kruger * Finish trolley * run prep for merge * Update src/queue/payouts.rs Co-authored-by: Jackson Kruger --------- Co-authored-by: Jackson Kruger --- .env | 6 +- .idea/labrinth.iml | 4 +- Cargo.lock | 1322 ++++++++++-------------- Cargo.toml | 4 +- migrations/20230919183129_trolley.sql | 16 + sqlx-data.json | 440 ++++---- src/auth/flows.rs | 90 +- src/auth/validate.rs | 11 +- src/clickhouse/fetch.rs | 32 +- src/database/models/collection_item.rs | 2 +- src/database/models/project_item.rs | 10 +- src/database/models/thread_item.rs | 2 +- src/database/models/user_item.rs | 24 +- src/database/models/version_item.rs | 6 +- src/file_hosting/mock.rs | 1 + src/lib.rs | 6 +- src/main.rs | 2 - src/models/collections.rs | 2 +- src/models/pats.rs | 6 +- src/models/projects.rs | 14 +- src/models/teams.rs | 11 +- src/models/threads.rs | 2 +- src/models/users.rs | 167 +-- src/queue/analytics.rs | 6 + src/queue/download.rs | 6 + src/queue/payouts.rs | 427 +++++--- src/queue/session.rs | 6 + src/ratelimit/memory.rs | 6 + src/routes/analytics.rs | 6 +- src/routes/v2/admin.rs | 186 +++- src/routes/v2/mod.rs | 1 - src/routes/v2/users.rs | 257 ++--- src/scheduler.rs | 6 + src/util/bitflag.rs | 18 + src/util/date.rs | 9 + src/util/mod.rs | 2 + src/validate/fabric.rs | 2 +- src/validate/forge.rs | 6 +- src/validate/quilt.rs | 2 +- src/validate/resourcepack.rs | 6 +- tests/scopes.rs | 18 - 41 files changed, 1704 insertions(+), 1446 deletions(-) create mode 100644 migrations/20230919183129_trolley.sql create mode 100644 src/util/bitflag.rs create mode 100644 src/util/date.rs diff --git a/.env b/.env index e89b7b9d..1b0cc280 100644 --- a/.env +++ b/.env @@ -49,9 +49,9 @@ WHITELISTED_MODPACK_DOMAINS='["cdn.modrinth.com", "github.com", "raw.githubuserc ALLOWED_CALLBACK_URLS='["localhost", ".modrinth.com", "127.0.0.1"]' -PAYPAL_API_URL=https://api-m.sandbox.paypal.com/v1/ -PAYPAL_CLIENT_ID=none -PAYPAL_CLIENT_SECRET=none +TROLLEY_ACCESS_KEY=none +TROLLEY_SECRET_KEY=none +TROLLEY_WEBHOOK_SIGNATURE=none GITHUB_CLIENT_ID=none GITHUB_CLIENT_SECRET=none diff --git a/.idea/labrinth.iml b/.idea/labrinth.iml index 5faf0f4e..280681b7 100644 --- a/.idea/labrinth.iml +++ b/.idea/labrinth.iml @@ -1,6 +1,7 @@ - + + @@ -144,6 +145,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index 40237625..b2028f8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,14 @@ version = 3 [[package]] name = "actix" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f728064aca1c318585bf4bb04ffcfac9e75e508ab4e8b1bd9ba5dfe04e2cbed5" +checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2" dependencies = [ + "actix-macros", "actix-rt", "actix_derive", - "bitflags 1.3.2", + "bitflags 2.4.0", "bytes", "crossbeam-channel", "futures-core", @@ -92,7 +93,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash 0.8.3", - "base64 0.21.2", + "base64 0.21.4", "bitflags 2.4.0", "brotli", "bytes", @@ -112,29 +113,29 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand", - "sha1 0.10.5", + "sha1 0.10.6", "smallvec", "tokio", "tokio-util", "tracing", - "zstd 0.12.3+zstd.1.5.2", + "zstd 0.12.4", ] [[package]] name = "actix-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "actix-multipart" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e" +checksum = "3b960e2aea75f49c8f069108063d12a48d329fc8b60b786dfc7552a9d5918d2d" dependencies = [ "actix-multipart-derive", "actix-utils", @@ -157,15 +158,15 @@ dependencies = [ [[package]] name = "actix-multipart-derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7" +checksum = "0a0a77f836d869f700e5b47ac7c3c8b9c8bc82e4aec861954c6198abee3ebd4d" dependencies = [ - "darling 0.14.4", + "darling 0.20.3", "parse-size", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -183,9 +184,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" dependencies = [ "actix-macros", "futures-core", @@ -194,9 +195,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" dependencies = [ "actix-rt", "actix-service", @@ -204,8 +205,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "num_cpus", - "socket2", + "socket2 0.5.4", "tokio", "tracing", ] @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" dependencies = [ "actix-codec", "actix-http", @@ -246,16 +246,15 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash 0.7.6", + "ahash 0.8.3", "bytes", "bytestring", - "cfg-if 1.0.0", + "cfg-if", "cookie", "derive_more", "encoding_rs", "futures-core", "futures-util", - "http", "itoa", "language-tags", "log", @@ -267,21 +266,21 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", - "time 0.3.22", + "socket2 0.5.4", + "time", "url", ] [[package]] name = "actix-web-codegen" -version = "4.2.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -312,20 +311,20 @@ dependencies = [ [[package]] name = "actix_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -342,7 +341,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", ] @@ -364,7 +363,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "getrandom", "once_cell", "version_check", @@ -372,9 +371,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -396,9 +395,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-tzdata" @@ -417,12 +416,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" dependencies = [ "base64ct", "blake2", + "cpufeatures", "password-hash 0.5.0", ] @@ -438,18 +438,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "as-slice" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" -dependencies = [ - "generic-array 0.12.4", - "generic-array 0.13.3", - "generic-array 0.14.7", - "stable_deref_trait", -] - [[package]] name = "askama_escape" version = "0.10.3" @@ -458,9 +446,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" [[package]] name = "async-channel" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener", @@ -469,13 +457,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -520,7 +508,7 @@ dependencies = [ "rust-ini", "serde", "thiserror", - "time 0.3.22", + "time", "url", ] @@ -535,13 +523,13 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -562,9 +550,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -641,7 +629,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -650,7 +638,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -700,9 +688,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -711,9 +699,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -721,31 +709,18 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", ] -[[package]] -name = "build_id" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6deb6795d8b4d2269c3fcf87a87bff9f4cd45a99e259806603ee8007077daf3" -dependencies = [ - "byteorder", - "once_cell", - "palaver", - "twox-hash", - "uuid 0.8.2", -] - [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytecheck" @@ -771,21 +746,21 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytestring" @@ -825,11 +800,12 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -841,12 +817,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -855,18 +825,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -881,9 +850,9 @@ dependencies = [ [[package]] name = "clickhouse" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33816ee1fea4f60d97abfeb773b9b566ae85f8bfa891758d00a1fb1e5a606591" +checksum = "a0875e527e299fc5f4faba42870bf199a39ab0bb2dbba1b8aef0a2151451130f" dependencies = [ "bstr", "bytes", @@ -897,10 +866,10 @@ dependencies = [ "serde", "static_assertions", "thiserror", - "time 0.3.22", + "time", "tokio", "url", - "uuid 1.4.0", + "uuid", ] [[package]] @@ -955,9 +924,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] @@ -996,7 +965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.22", + "time", "version_check", ] @@ -1046,7 +1015,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1055,7 +1024,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -1065,7 +1034,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] @@ -1077,7 +1046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", @@ -1089,7 +1058,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -1099,7 +1068,7 @@ version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1114,7 +1083,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array", "typenum", ] @@ -1124,7 +1093,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.7", + "generic-array", "subtle", ] @@ -1139,15 +1108,15 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2", + "socket2 0.4.9", "winapi", ] [[package]] name = "curl-sys" -version = "0.4.63+curl-8.1.2" +version = "0.4.67+curl-8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc" +checksum = "3cc35d066510b197a0f72de863736641539957628c8a42e70e27c66849e77c34" dependencies = [ "cc", "libc", @@ -1156,7 +1125,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "winapi", + "windows-sys", ] [[package]] @@ -1171,12 +1140,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core 0.20.1", - "darling_macro 0.20.1", + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -1195,16 +1164,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -1220,23 +1189,23 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core 0.20.1", + "darling_core 0.20.3", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.12.3", + "cfg-if", + "hashbrown 0.14.1", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -1267,9 +1236,9 @@ dependencies = [ [[package]] name = "deadpool-runtime" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" +checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" dependencies = [ "tokio", ] @@ -1281,7 +1250,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.4.0", + "uuid", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", ] [[package]] @@ -1335,7 +1313,7 @@ dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.109", ] @@ -1345,7 +1323,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -1393,9 +1371,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" dependencies = [ "serde", ] @@ -1406,7 +1384,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "memchr", ] @@ -1418,11 +1396,11 @@ checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1439,24 +1417,19 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "cc", "libc", + "windows-sys", ] [[package]] @@ -1467,9 +1440,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exr" -version = "1.6.5" +version = "1.71.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a7b44a196573e272e0cf0bcf130281c71e9a0c67062954b3323fd364bfdac9" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" dependencies = [ "bit_field", "flume", @@ -1500,6 +1473,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "fdeflate" version = "0.3.0" @@ -1511,13 +1490,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "windows-sys", ] @@ -1533,11 +1512,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide", @@ -1545,14 +1530,10 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", "spin 0.9.8", ] @@ -1657,7 +1638,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", + "fastrand 1.9.0", "futures-core", "futures-io", "memchr", @@ -1674,7 +1655,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -1713,24 +1694,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1747,11 +1710,9 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ - "cfg-if 1.0.0", - "js-sys", + "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", + "wasi", ] [[package]] @@ -1766,15 +1727,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -1782,7 +1743,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -1798,15 +1759,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hash32" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1827,9 +1779,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash 0.8.3", "allocator-api2", @@ -1837,23 +1789,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" -dependencies = [ - "hashbrown 0.14.0", -] - -[[package]] -name = "heapless" -version = "0.5.6" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "as-slice", - "generic-array 0.13.3", - "hash32", - "stable_deref_trait", + "hashbrown 0.14.1", ] [[package]] @@ -1876,9 +1816,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -1961,9 +1901,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1988,7 +1928,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -2065,9 +2005,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "image" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", @@ -2093,13 +2033,24 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", +] + [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -2108,18 +2059,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "cfg-if", ] [[package]] @@ -2144,7 +2084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.3", + "rustix", "windows-sys", ] @@ -2204,15 +2144,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -2241,8 +2181,8 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.2", - "ring", + "base64 0.21.4", + "ring 0.16.20", "serde", "serde_json", ] @@ -2262,8 +2202,8 @@ dependencies = [ "actix-ws", "argon2", "async-trait", - "base64 0.21.2", - "bitflags 1.3.2", + "base64 0.21.4", + "bitflags 2.4.0", "bytes", "censor", "chrono", @@ -2311,7 +2251,7 @@ dependencies = [ "totp-rs", "url", "urlencoding", - "uuid 1.4.0", + "uuid", "validator", "woothee", "xml-rs", @@ -2345,10 +2285,10 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "email-encoding", "email_address", - "fastrand", + "fastrand 1.9.0", "futures-util", "hostname", "httpdate", @@ -2358,7 +2298,7 @@ dependencies = [ "nom 7.1.3", "once_cell", "quoted_printable", - "socket2", + "socket2 0.4.9", "tokio", ] @@ -2370,22 +2310,22 @@ checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.2", "bitflags 1.3.2", - "cfg-if 1.0.0", + "cfg-if", "ryu", "static_assertions", ] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libnghttp2-sys" -version = "0.1.7+1.45.0" +version = "0.1.8+1.55.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" dependencies = [ "cc", "libc", @@ -2393,9 +2333,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -2411,25 +2351,18 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "local-channel" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178" dependencies = [ "futures-core", "futures-sink", - "futures-util", "local-waker", ] @@ -2451,9 +2384,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru-cache" @@ -2484,15 +2417,6 @@ dependencies = [ "libc", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "match_cfg" version = "0.1.0" @@ -2524,10 +2448,11 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest 0.10.7", ] @@ -2568,8 +2493,8 @@ dependencies = [ "serde", "serde_json", "thiserror", - "time 0.3.22", - "uuid 1.4.0", + "time", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2578,18 +2503,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -2649,19 +2565,10 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -2680,37 +2587,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cfg-if 0.1.10", - "libc", - "void", -] - -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", - "libc", - "static_assertions", -] - -[[package]] -name = "nom" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" - [[package]] name = "nom" version = "5.1.3" @@ -2734,9 +2610,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -2766,9 +2642,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -2785,9 +2661,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -2812,12 +2688,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", + "bitflags 2.4.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -2833,7 +2709,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -2844,9 +2720,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -2875,28 +2751,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "palaver" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49dfc200733ac34dcd9a1e4a7e454b521723936010bef3710e2d8024a32d685f" -dependencies = [ - "bitflags 1.3.2", - "heapless", - "lazy_static", - "libc", - "mach", - "nix 0.15.0", - "procinfo", - "typenum", - "winapi", -] - [[package]] name = "parking" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" [[package]] name = "parking_lot" @@ -2925,7 +2784,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "instant", "libc", "redox_syscall 0.2.16", @@ -2939,7 +2798,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall 0.3.5", "smallvec", @@ -2976,9 +2835,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pbkdf2" @@ -2989,7 +2848,7 @@ dependencies = [ "digest 0.10.7", "hmac 0.12.1", "password-hash 0.4.2", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -3000,14 +2859,14 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "phonenumber" -version = "0.3.2+8.13.9" +version = "0.3.3+8.13.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34749f64ea9d76f10cdc8a859588b57775f59177c7dd91f744d620bd62982d6f" +checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06" dependencies = [ "bincode", "either", "fnv", - "itertools 0.10.5", + "itertools 0.11.0", "lazy_static", "nom 7.1.3", "quick-xml 0.28.2", @@ -3015,34 +2874,35 @@ dependencies = [ "regex-cache", "serde", "serde_derive", + "strum", "thiserror", ] [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -3058,9 +2918,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "png" -version = "0.17.9" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3077,7 +2937,7 @@ checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags 1.3.2", - "cfg-if 1.0.0", + "cfg-if", "concurrent-queue", "libc", "log", @@ -3085,26 +2945,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "pprof" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196ded5d4be535690899a4631cc9f18cdc41b7ebf24a79400f46f48e49a11059" -dependencies = [ - "backtrace", - "cfg-if 1.0.0", - "findshlibs", - "libc", - "log", - "nix 0.26.2", - "once_cell", - "parking_lot 0.12.1", - "smallvec", - "symbolic-demangle", - "tempfile", - "thiserror", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3146,32 +2986,20 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] -[[package]] -name = "procinfo" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" -dependencies = [ - "byteorder", - "libc", - "nom 2.2.1", - "rustc_version 0.2.3", -] - [[package]] name = "prometheus" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fnv", "lazy_static", "memchr", @@ -3235,9 +3063,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -3297,9 +3125,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -3307,21 +3135,19 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "redis" -version = "0.23.0" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea8c51b5dc1d8e5fd3350ec8167f464ec0995e79f2e90a075b63371500d557f" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" dependencies = [ "ahash 0.7.6", "async-trait", @@ -3334,6 +3160,7 @@ dependencies = [ "r2d2", "ryu", "sha1_smol", + "socket2 0.4.9", "tokio", "tokio-util", "url", @@ -3370,25 +3197,25 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.3", + "regex-syntax 0.8.0", ] [[package]] name = "regex-automata" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaecc05d5c4b5f7da074b9a0d1a0867e71fd36e7fc0482d8bcfe8e8fc56290" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.3", + "regex-syntax 0.8.0", ] [[package]] @@ -3411,26 +3238,26 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" [[package]] name = "rend" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -3452,6 +3279,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-util", @@ -3489,11 +3317,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "rkyv" version = "0.7.42" @@ -3508,7 +3350,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.4.0", + "uuid", ] [[package]] @@ -3528,7 +3370,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ordered-multimap", ] @@ -3543,7 +3385,7 @@ dependencies = [ "aws-region", "base64 0.13.1", "bytes", - "cfg-if 1.0.0", + "cfg-if", "futures", "hex", "hmac 0.12.1", @@ -3557,9 +3399,9 @@ dependencies = [ "reqwest", "serde", "serde_derive", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", - "time 0.3.22", + "time", "tokio", "tokio-stream", "url", @@ -3567,14 +3409,12 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.30.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042" +checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" dependencies = [ "arrayvec 0.7.4", "borsh", - "bytecheck", - "byteorder", "bytes", "num-traits", "rand", @@ -3589,69 +3429,36 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustc_version_runtime" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" -dependencies = [ - "rustc_version 0.2.3", - "semver 0.9.0", -] - -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys", + "semver", ] [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys", "windows-sys", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", - "ring", + "ring 0.16.20", "sct", "webpki", ] @@ -3662,9 +3469,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "rxml" version = "0.9.1" @@ -3684,9 +3497,9 @@ checksum = "22a197350ece202f19a166d1ad6d9d6de145e1d2a8ef47db299abe164dbd7530" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" @@ -3708,9 +3521,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -3718,8 +3531,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -3742,9 +3555,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3755,9 +3568,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -3765,30 +3578,15 @@ dependencies = [ [[package]] name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" - -[[package]] -name = "semver-parser" -version = "0.7.0" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "sentry" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b0ad16faa5d12372f914ed40d00bda21a6d1bdcc99264c5e5e1c9495cf3654" +checksum = "0097a48cd1999d983909f07cb03b15241c5af29e5e679379efac1c06296abecc" dependencies = [ "httpdate", "native-tls", @@ -3805,9 +3603,9 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f81ee4dfb8e109fd99f0eb036fec548e66fd1db17a0224304c4a31ab0749ef" +checksum = "856a0131ca91c402aba7ce3b4f199b4fabc0b2d2b7fe0e8fdeac2a131ae75126" dependencies = [ "actix-web", "futures-util", @@ -3816,9 +3614,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f2ee8f147bb5f22ac59b5c35754a759b9a6f6722402e2a14750b2a63fc59bd" +checksum = "18a7b80fa1dd6830a348d38a8d3a9761179047757b7dca29aef82db0118b9670" dependencies = [ "backtrace", "once_cell", @@ -3828,44 +3626,36 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcd133362c745151eeba0ac61e3ba8350f034e9fe7509877d08059fe1d7720c6" +checksum = "7615dc588930f1fd2e721774f25844ae93add2dbe2d3c2f995ce5049af898147" dependencies = [ "hostname", "libc", "os_info", - "rustc_version 0.4.0", + "rustc_version", "sentry-core", "uname", ] [[package]] name = "sentry-core" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7163491708804a74446642ff2c80b3acd668d4b9e9f497f85621f3d250fd012b" +checksum = "8f51264e4013ed9b16558cce43917b983fa38170de2ca480349ceb57d71d6053" dependencies = [ - "build_id", - "findshlibs", - "indexmap", - "libc", "once_cell", - "pprof", "rand", - "rustc_version_runtime", "sentry-types", "serde", "serde_json", - "sys-info", - "uuid 1.4.0", ] [[package]] name = "sentry-debug-images" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a5003d7ff08aa3b2b76994080b183e8cfa06c083e280737c9cee02ca1c70f5e" +checksum = "2fe6180fa564d40bb942c9f0084ffb5de691c7357ead6a2b7a3154fae9e401dd" dependencies = [ "findshlibs", "once_cell", @@ -3874,9 +3664,9 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4dfe8371c9b2e126a8b64f6fefa54cef716ff2a50e63b5558a48b899265bccd" +checksum = "323160213bba549f9737317b152af116af35c0410f4468772ee9b606d3d6e0fa" dependencies = [ "sentry-backtrace", "sentry-core", @@ -3884,9 +3674,9 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca8b88978677a27ee1a91beafe4052306c474c06f582321fde72d2e2cc2f7f" +checksum = "38033822128e73f7b6ca74c1631cef8868890c6cb4008a291cf73530f87b4eac" dependencies = [ "sentry-backtrace", "sentry-core", @@ -3896,39 +3686,39 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7a88e0c1922d19b3efee12a8215f6a8a806e442e665ada71cc222cab72985f" +checksum = "0e663b3eb62ddfc023c9cf5432daf5f1a4f6acb1df4d78dd80b740b32dd1a740" dependencies = [ "debugid", - "getrandom", "hex", + "rand", "serde", "serde_json", "thiserror", - "time 0.3.22", + "time", "url", - "uuid 1.4.0", + "uuid", ] [[package]] name = "serde" -version = "1.0.167" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daf513456463b42aa1d94cff7e0c24d682b429f020b9afa4f5ba5c40a22b237" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.167" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69b106b68bc8054f0e974e70d19984040f8a5cf9215ca82626ea4853f82c4b9" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -3944,9 +3734,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -3955,9 +3745,9 @@ dependencies = [ [[package]] name = "serde_plain" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" dependencies = [ "serde", ] @@ -3976,30 +3766,31 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.0.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.0.2", "serde", "serde_json", "serde_with_macros", - "time 0.3.22", + "time", ] [[package]] name = "serde_with_macros" -version = "3.0.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" dependencies = [ - "darling 0.20.1", + "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -4013,11 +3804,11 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -4035,7 +3826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug", @@ -4043,11 +3834,11 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -4063,9 +3854,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" @@ -4075,9 +3866,9 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -4095,9 +3886,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smartstring" @@ -4120,11 +3911,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spdx" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2971cb691ca629f46174f73b1f95356c5617f89b4167f04107167c3dccb8dd89" +checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" dependencies = [ "smallvec", ] @@ -4146,11 +3947,11 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ - "itertools 0.10.5", + "itertools 0.11.0", "nom 7.1.3", "unicode_categories", ] @@ -4192,7 +3993,7 @@ dependencies = [ "hex", "hkdf", "hmac 0.12.1", - "indexmap", + "indexmap 1.9.3", "itoa", "libc", "log", @@ -4208,8 +4009,8 @@ dependencies = [ "rustls-pemfile", "serde", "serde_json", - "sha1 0.10.5", - "sha2 0.10.7", + "sha1 0.10.6", + "sha2 0.10.8", "smallvec", "sqlformat", "sqlx-rt", @@ -4236,7 +4037,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", "sqlx-core", "sqlx-rt", "syn 1.0.109", @@ -4254,12 +4055,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -4268,10 +4063,11 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -4283,32 +4079,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "subtle" -version = "2.4.1" +name = "strum" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] [[package]] -name = "symbolic-common" -version = "10.2.1" +name = "strum_macros" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b55cdc318ede251d0957f07afe5fed912119b8c1bc5a7804151826db999e737" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "debugid", - "memmap2", - "stable_deref_trait", - "uuid 1.4.0", + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] -name = "symbolic-demangle" -version = "10.2.1" +name = "subtle" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79be897be8a483a81fff6a3a4e195b4ac838ef73ca42d348b3f722da9902e489" -dependencies = [ - "rustc-demangle", - "symbolic-common", -] +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" @@ -4323,9 +4119,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -4333,12 +4129,23 @@ dependencies = [ ] [[package]] -name = "sys-info" -version = "0.9.1" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "cc", + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", "libc", ] @@ -4350,9 +4157,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -4361,52 +4168,51 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "fastrand", + "cfg-if", + "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.37.23", + "rustix", "windows-sys", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] name = "tiff" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" dependencies = [ "flate2", "jpeg-decoder", @@ -4415,21 +4221,11 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.22" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -4438,15 +4234,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -4468,11 +4264,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -4481,7 +4276,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", "windows-sys", ] @@ -4494,7 +4289,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -4531,9 +4326,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -4554,16 +4349,16 @@ dependencies = [ [[package]] name = "totp-rs" -version = "5.0.2" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad5e73765ff14ae797c1a61ee0c7beaf21b4e4a0047844300e332c6c24df1fc" +checksum = "df3504f96adf86d28e7eb16fa236a7951ec72c15ee100d1b5318e225944bc8cb" dependencies = [ "base32", "constant_time_eq 0.2.6", "hmac 0.12.1", "rand", - "sha1 0.10.5", - "sha2 0.10.7", + "sha1 0.10.6", + "sha2 0.10.8", ] [[package]] @@ -4578,7 +4373,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4593,7 +4388,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] @@ -4631,21 +4426,11 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if 1.0.0", - "static_assertions", -] - [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uname" @@ -4658,9 +4443,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -4673,9 +4458,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -4704,13 +4489,19 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "ureq" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "log", "native-tls", "once_cell", @@ -4719,9 +4510,9 @@ dependencies = [ [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna 0.4.0", @@ -4731,21 +4522,15 @@ dependencies = [ [[package]] name = "urlencoding" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" - -[[package]] -name = "uuid" -version = "0.8.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", "rand", @@ -4813,17 +4598,11 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "want" @@ -4834,12 +4613,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4852,7 +4625,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -4867,7 +4640,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", "wasm-bindgen-shared", ] @@ -4877,7 +4650,7 @@ version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -4901,7 +4674,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4914,9 +4687,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-streams" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" dependencies = [ "futures-util", "js-sys", @@ -4937,12 +4710,12 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring", - "untrusted", + "ring 0.17.3", + "untrusted 0.9.0", ] [[package]] @@ -4988,9 +4761,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -5021,9 +4794,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -5036,53 +4809,54 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", ] [[package]] @@ -5106,18 +4880,18 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] [[package]] name = "xml-rs" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" [[package]] name = "yaserde" @@ -5168,8 +4942,8 @@ dependencies = [ "flate2", "hmac 0.12.1", "pbkdf2", - "sha1 0.10.5", - "time 0.3.22", + "sha1 0.10.6", + "time", "zstd 0.11.2+zstd.1.5.2", ] @@ -5184,11 +4958,11 @@ dependencies = [ [[package]] name = "zstd" -version = "0.12.3+zstd.1.5.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" dependencies = [ - "zstd-safe 6.0.5+zstd.1.5.4", + "zstd-safe 6.0.6", ] [[package]] @@ -5203,9 +4977,9 @@ dependencies = [ [[package]] name = "zstd-safe" -version = "6.0.5+zstd.1.5.4" +version = "6.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" dependencies = [ "libc", "zstd-sys", @@ -5244,5 +5018,5 @@ dependencies = [ "lazy_static", "quick-error", "regex", - "time 0.3.22", + "time", ] diff --git a/Cargo.toml b/Cargo.toml index 898a4ae4..819d1484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ sha1 = { version = "0.6.1", features = ["std"] } sha2 = "0.9.9" hmac = "0.11.0" argon2 = { version = "0.5.0", features = ["std"] } -bitflags = "1.3.2" +bitflags = "2.4.0" hex = "0.4.3" zxcvbn = "2.2.2" totp-rs = { version = "5.0.2", features = ["gen_secret"] } @@ -96,7 +96,7 @@ maxminddb = "0.23.0" flate2 = "1.0.25" tar = "0.4.38" -sentry = { version = "0.31.5", features = ["profiling"] } +sentry = { version = "0.31.5" } sentry-actix = "0.31.5" image = "0.24.6" diff --git a/migrations/20230919183129_trolley.sql b/migrations/20230919183129_trolley.sql new file mode 100644 index 00000000..8dfb148f --- /dev/null +++ b/migrations/20230919183129_trolley.sql @@ -0,0 +1,16 @@ +ALTER TABLE users + ADD COLUMN trolley_id text NULL, + ADD COLUMN trolley_account_status text NULL, + DROP COLUMN midas_expires, + DROP COLUMN is_overdue, + DROP COLUMN stripe_customer_id, + DROP COLUMN payout_wallet, + DROP COLUMN payout_wallet_type, + DROP COLUMN payout_address; + +ALTER TABLE historical_payouts + ADD COLUMN batch_id text NULL, + ADD COLUMN payment_id text NULL; + +UPDATE historical_payouts +SET status = 'processed' \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 9549939c..3d99d453 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -144,25 +144,25 @@ }, "query": "\n UPDATE versions\n SET status = $1, date_published = $2\n WHERE (id = $3)\n " }, - "03284fe5b045e2cf93f160863c4d121439382b348b728fffb5ac588dee980731": { + "04128dd06489004e0d0305bfd0f4ca5ee4b4a6b9f610de6e1b9ef9c8543cc025": { "describe": { "columns": [ { - "name": "exists", + "name": "id", "ordinal": 0, - "type_info": "Bool" + "type_info": "Int8" } ], "nullable": [ - null + false ], "parameters": { "Left": [ - "Int8" + "Text" ] } }, - "query": "\n SELECT EXISTS(SELECT 1 FROM users WHERE id = $1 AND email IS NULL)\n " + "query": "SELECT id FROM users WHERE trolley_id = $1" }, "04345d9c23430267f755b1420520df91bd403524fd60ba1a94e3a239ea70cae7": { "describe": { @@ -1609,6 +1609,18 @@ }, "query": "\n UPDATE mods\n SET follows = follows - 1\n WHERE id = $1\n " }, + "382753714620109f2ad1a4cacbb6f699732db321a2dcb1f9d83e57332e32357d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET trolley_account_status = NULL, trolley_id = NULL\n WHERE id = $1\n " + }, "38429340be03cc5f539d9d14c156e6b6710051d2826b53a5ccfdbd231af964ca": { "describe": { "columns": [ @@ -1756,6 +1768,20 @@ }, "query": "\n UPDATE mods\n SET title = $1\n WHERE (id = $2)\n " }, + "3f525e05e94ccaea4abc059d54f48011517bd8997df0c7d42cc4caae62194ae6": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET trolley_id = $1, trolley_account_status = $2\n WHERE id = $3\n " + }, "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08": { "describe": { "columns": [], @@ -1986,20 +2012,6 @@ }, "query": "\n INSERT INTO mods_donations (\n joining_mod_id, joining_platform_id, url\n )\n SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[])\n " }, - "4778d2f5994fda2f978fa53e0840c1a9a2582ef0434a5ff7f21706f1dc4edcf4": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Numeric", - "Varchar" - ] - } - }, - "query": "\n INSERT INTO historical_payouts (user_id, amount, status)\n VALUES ($1, $2, $3)\n " - }, "4838777a8ef4371f4f5bb4f4f038bb6d041455f0849a3972a5418d75165ae9c7": { "describe": { "columns": [ @@ -2032,6 +2044,22 @@ }, "query": "\n SELECT d.dependency_id, COALESCE(vd.mod_id, 0) mod_id, d.mod_dependency_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n LEFT JOIN versions vd ON d.dependency_id = vd.id\n WHERE v.mod_id = $1\n " }, + "48dc011567c5d50ee734fd0bdd1f5d07d9ef066c485a9b34495120c9947489f8": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Numeric", + "Varchar", + "Text", + "Text" + ] + } + }, + "query": "\n INSERT INTO historical_payouts (user_id, amount, status, batch_id, payment_id)\n VALUES ($1, $2, $3, $4, $5)\n " + }, "49813a96f007216072d69468aae705d73d5b85dcdd64a22060009b12d947ed5a": { "describe": { "columns": [], @@ -2706,153 +2734,6 @@ }, "query": "\n UPDATE mods_gallery\n SET ordering = $2\n WHERE id = $1\n " }, - "60a251aea1efbc7d9357255e520f0ac13f3697fecb84b1e9edd5d9ea61fe0cb0": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 6, - "type_info": "Timestamptz" - }, - { - "name": "role", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 8, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 9, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "github_id", - "ordinal": 13, - "type_info": "Int8" - }, - { - "name": "discord_id", - "ordinal": 14, - "type_info": "Int8" - }, - { - "name": "gitlab_id", - "ordinal": 15, - "type_info": "Int8" - }, - { - "name": "google_id", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "steam_id", - "ordinal": 17, - "type_info": "Int8" - }, - { - "name": "microsoft_id", - "ordinal": 18, - "type_info": "Varchar" - }, - { - "name": "email_verified", - "ordinal": 19, - "type_info": "Bool" - }, - { - "name": "password", - "ordinal": 20, - "type_info": "Text" - }, - { - "name": "totp_secret", - "ordinal": 21, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - false, - true, - true - ], - "parameters": { - "Left": [ - "Int8Array", - "TextArray" - ] - } - }, - "query": "\n SELECT id, name, email,\n avatar_url, username, bio,\n created, role, badges,\n balance, payout_wallet, payout_wallet_type, payout_address,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n " - }, "61a7f29e024bf2f1368370e3f6e8ef70317c7e8545b5b6d4235f21164948ba27": { "describe": { "columns": [], @@ -3953,20 +3834,20 @@ }, "query": "\n SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.organization_permissions, tm.accepted, tm.payouts_split, tm.ordering, v.mod_id \n FROM versions v\n INNER JOIN mods m ON m.id = v.mod_id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.user_id = $2 AND tm.accepted = TRUE\n WHERE v.id = $1\n " }, - "8cbd74dad7a21128d99fd32b430c2e0427480f910e1f125ff56b893c67a6e8a4": { + "8f45a48700b8836f4ba8626b25b7be7f838d35d260430a46817729d9787e2013": { "describe": { "columns": [], "nullable": [], "parameters": { "Left": [ "Varchar", - "Varchar", - "Varchar", + "Bool", + "Text", "Int8" ] } }, - "query": "\n UPDATE users\n SET payout_wallet = $1, payout_wallet_type = $2, payout_address = $3\n WHERE (id = $4)\n " + "query": "\n UPDATE users\n SET email = $1, email_verified = $2, trolley_account_status = $3\n WHERE id = $4\n " }, "8f5e2a570cf35b2d158182bac37fd40bcec277bbdeddaece5efaa88600048a70": { "describe": { @@ -4188,6 +4069,19 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM reports WHERE id=$1)" }, + "9774f59e5d5ce6ba00ca7e3a4a81f80f78b908bdf664a4cdfad592a1b14c0d44": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + } + }, + "query": "\n UPDATE historical_payouts\n SET status = $1\n WHERE payment_id = $2\n " + }, "99a1eac69d7f5a5139703df431e6a5c3012a90143a8c635f93632f04d0bc41d4": { "describe": { "columns": [], @@ -5104,18 +4998,6 @@ }, "query": "\n SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type, n.body,\n JSONB_AGG(DISTINCT jsonb_build_object('id', na.id, 'notification_id', na.notification_id, 'title', na.title, 'action_route_method', na.action_route_method, 'action_route', na.action_route)) filter (where na.id is not null) actions\n FROM notifications n\n LEFT OUTER JOIN notifications_actions na on n.id = na.notification_id\n WHERE n.user_id = $1\n GROUP BY n.id, n.user_id;\n " }, - "c4b167ec7452cc92be0e33f7e4f3908f0c4109291511c94909e9105fc62a432f": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n UPDATE users\n SET payout_wallet = NULL, payout_wallet_type = NULL, payout_address = NULL\n WHERE (id = $1)\n " - }, "c55d2132e3e6e92dd50457affab758623dca175dc27a2d3cd4aace9cfdecf789": { "describe": { "columns": [], @@ -6051,6 +5933,44 @@ }, "query": "\n UPDATE versions\n SET featured = $1\n WHERE (id = $2)\n " }, + "e5adaf219c52ec828b72bd89c6b86a475f73181abf180a024dfe05f918e58edb": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "amount", + "ordinal": 1, + "type_info": "Numeric" + }, + { + "name": "user_id", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "status", + "ordinal": 3, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT id, amount, user_id, status FROM historical_payouts WHERE payment_id = $1" + }, "e60ea75112db37d3e73812e21b1907716e4762e06aa883af878e3be82e3f87d3": { "describe": { "columns": [ @@ -6417,6 +6337,19 @@ }, "query": "\n INSERT INTO collections_mods (collection_id, mod_id)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[])\n ON CONFLICT DO NOTHING\n " }, + "f141cc6711123b4fe5a5d9a7337a0b009b80e5d8fbda664b8d62b1a3f38eb936": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Numeric", + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET balance = balance + $1\n WHERE id = $2\n " + }, "f1525930830e17b5ee8feb796d9950dd3741131965f050840fa75423b5a54f01": { "describe": { "columns": [], @@ -6648,6 +6581,147 @@ }, "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n SELECT * FROM UNNEST($1::integer[], $2::bigint[])\n " }, + "faec0a606ccaeb3f21c81e60a1749640b929e97db40252118fb72610df64a457": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 6, + "type_info": "Timestamptz" + }, + { + "name": "role", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 8, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 9, + "type_info": "Numeric" + }, + { + "name": "github_id", + "ordinal": 10, + "type_info": "Int8" + }, + { + "name": "discord_id", + "ordinal": 11, + "type_info": "Int8" + }, + { + "name": "gitlab_id", + "ordinal": 12, + "type_info": "Int8" + }, + { + "name": "google_id", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "steam_id", + "ordinal": 14, + "type_info": "Int8" + }, + { + "name": "microsoft_id", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "email_verified", + "ordinal": 16, + "type_info": "Bool" + }, + { + "name": "password", + "ordinal": 17, + "type_info": "Text" + }, + { + "name": "totp_secret", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "trolley_id", + "ordinal": 19, + "type_info": "Text" + }, + { + "name": "trolley_account_status", + "ordinal": 20, + "type_info": "Text" + } + ], + "nullable": [ + false, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8Array", + "TextArray" + ] + } + }, + "query": "\n SELECT id, name, email,\n avatar_url, username, bio,\n created, role, badges,\n balance,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, trolley_id, trolley_account_status\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n " + }, "fb955ca41b95120f66c98c0b528b1db10c4be4a55e9641bb104d772e390c9bb7": { "describe": { "columns": [ diff --git a/src/auth/flows.rs b/src/auth/flows.rs index 03771312..7b54f5b1 100644 --- a/src/auth/flows.rs +++ b/src/auth/flows.rs @@ -8,7 +8,8 @@ use crate::file_hosting::FileHost; use crate::models::ids::base62_impl::{parse_base62, to_base62}; use crate::models::ids::random_base62_rng; use crate::models::pats::Scopes; -use crate::models::users::{Badges, Role}; +use crate::models::users::{Badges, RecipientStatus, Role, UserPayoutData}; +use crate::queue::payouts::{AccountUser, PayoutsQueue}; use crate::queue::session::AuthQueue; use crate::queue::socket::ActiveSockets; use crate::routes::ApiError; @@ -30,7 +31,7 @@ use serde::{Deserialize, Serialize}; use sqlx::postgres::PgPool; use std::collections::HashMap; use std::sync::Arc; -use tokio::sync::RwLock; +use tokio::sync::{Mutex, RwLock}; use validator::Validate; pub fn config(cfg: &mut ServiceConfig) { @@ -51,7 +52,8 @@ pub fn config(cfg: &mut ServiceConfig) { .service(resend_verify_email) .service(set_email) .service(verify_email) - .service(subscribe_newsletter), + .service(subscribe_newsletter) + .service(link_trolley), ); } @@ -225,9 +227,8 @@ impl TempUser { role: Role::Developer.to_string(), badges: Badges::default(), balance: Decimal::ZERO, - payout_wallet: None, - payout_wallet_type: None, - payout_address: None, + trolley_id: None, + trolley_account_status: None, } .insert(transaction) .await?; @@ -1013,7 +1014,7 @@ pub async fn auth_callback( let sockets = active_sockets.clone(); let state = state_string.clone(); - let res: Result = (|| async move { + let res: Result = async move { let flow = Flow::get(&state, &redis).await?; @@ -1175,7 +1176,7 @@ pub async fn auth_callback( } else { Err::(AuthenticationError::InvalidCredentials) } - })().await; + }.await; // Because this is callback route, if we have an error, we need to ensure we close the original socket if it exists if let Err(ref e) = res { @@ -1385,9 +1386,8 @@ pub async fn create_account_with_password( role: Role::Developer.to_string(), badges: Badges::default(), balance: Decimal::ZERO, - payout_wallet: None, - payout_wallet_type: None, - payout_address: None, + trolley_id: None, + trolley_account_status: None, } .insert(&mut transaction) .await?; @@ -2011,6 +2011,7 @@ pub async fn set_email( redis: Data, email: web::Json, session_queue: Data, + payouts_queue: Data>, ) -> Result { email .0 @@ -2064,6 +2065,17 @@ pub async fn set_email( "We need to verify your email address.", )?; + if let Some(UserPayoutData { + trolley_id: Some(trolley_id), + .. + }) = user.payout_data + { + let queue = payouts_queue.lock().await; + queue + .update_recipient_email(&trolley_id, &email.email) + .await?; + } + crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?; transaction.commit().await?; @@ -2206,3 +2218,59 @@ fn send_email_verify( Some(("Verify email", &format!("{}/{}?flow={}", dotenvy::var("SITE_URL")?, dotenvy::var("SITE_VERIFY_EMAIL_PATH")?, flow))), ) } + +#[post("trolley/link")] +pub async fn link_trolley( + req: HttpRequest, + pool: Data, + redis: Data, + session_queue: Data, + payouts_queue: Data>, + body: web::Json, +) -> Result { + let user = get_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Some(&[Scopes::PAYOUTS_WRITE]), + ) + .await? + .1; + + if let Some(payout_data) = user.payout_data { + if payout_data.trolley_id.is_some() { + return Err(ApiError::InvalidInput( + "User already has a trolley account.".to_string(), + )); + } + } + + if let Some(email) = user.email { + let id = payouts_queue.lock().await.register_recipient(&email, body.0).await?; + + let mut transaction = pool.begin().await?; + + sqlx::query!( + " + UPDATE users + SET trolley_id = $1, trolley_account_status = $2 + WHERE id = $3 + ", + id, + RecipientStatus::Incomplete.as_str(), + user.id.0 as i64, + ) + .execute(&mut transaction) + .await?; + + transaction.commit().await?; + crate::database::models::User::clear_caches(&[(user.id.into(), None)], &redis).await?; + + Ok(HttpResponse::NoContent().finish()) + } else { + Err(ApiError::InvalidInput( + "User needs to have an email set on account.".to_string(), + )) + } +} diff --git a/src/auth/validate.rs b/src/auth/validate.rs index 34a0d128..cb979354 100644 --- a/src/auth/validate.rs +++ b/src/auth/validate.rs @@ -56,16 +56,15 @@ where created: db_user.created, role: Role::from_string(&db_user.role), badges: db_user.badges, - payout_data: Some(UserPayoutData { - balance: db_user.balance, - payout_wallet: db_user.payout_wallet, - payout_wallet_type: db_user.payout_wallet_type, - payout_address: db_user.payout_address, - }), auth_providers: Some(auth_providers), has_password: Some(db_user.password.is_some()), has_totp: Some(db_user.totp_secret.is_some()), github_id: None, + payout_data: Some(UserPayoutData { + balance: db_user.balance, + trolley_id: db_user.trolley_id, + trolley_status: db_user.trolley_account_status, + }), }; if let Some(required_scopes) = required_scopes { diff --git a/src/clickhouse/fetch.rs b/src/clickhouse/fetch.rs index 7fddedd5..a17d69ff 100644 --- a/src/clickhouse/fetch.rs +++ b/src/clickhouse/fetch.rs @@ -76,10 +76,10 @@ pub async fn fetch_playtimes( .bind(start_date) .bind(end_date); - if projects.is_some() { - query = query.bind(projects.unwrap().iter().map(|x| x.0).collect::>()); - } else if versions.is_some() { - query = query.bind(versions.unwrap().iter().map(|x| x.0).collect::>()); + if let Some(projects) = projects { + query = query.bind(projects.iter().map(|x| x.0).collect::>()); + } else if let Some(versions) = versions { + query = query.bind(versions.iter().map(|x| x.0).collect::>()); } Ok(query.fetch_all().await?) @@ -123,10 +123,10 @@ pub async fn fetch_views( .bind(start_date) .bind(end_date); - if projects.is_some() { - query = query.bind(projects.unwrap().iter().map(|x| x.0).collect::>()); - } else if versions.is_some() { - query = query.bind(versions.unwrap().iter().map(|x| x.0).collect::>()); + if let Some(projects) = projects { + query = query.bind(projects.iter().map(|x| x.0).collect::>()); + } else if let Some(versions) = versions { + query = query.bind(versions.iter().map(|x| x.0).collect::>()); } Ok(query.fetch_all().await?) @@ -170,10 +170,10 @@ pub async fn fetch_downloads( .bind(start_date) .bind(end_date); - if projects.is_some() { - query = query.bind(projects.unwrap().iter().map(|x| x.0).collect::>()); - } else if versions.is_some() { - query = query.bind(versions.unwrap().iter().map(|x| x.0).collect::>()); + if let Some(projects) = projects { + query = query.bind(projects.iter().map(|x| x.0).collect::>()); + } else if let Some(versions) = versions { + query = query.bind(versions.iter().map(|x| x.0).collect::>()); } Ok(query.fetch_all().await?) @@ -233,10 +233,10 @@ pub async fn fetch_countries( " )).bind(start_date).bind(end_date).bind(start_date).bind(end_date); - if projects.is_some() { - query = query.bind(projects.unwrap().iter().map(|x| x.0).collect::>()); - } else if versions.is_some() { - query = query.bind(versions.unwrap().iter().map(|x| x.0).collect::>()); + if let Some(projects) = projects { + query = query.bind(projects.iter().map(|x| x.0).collect::>()); + } else if let Some(versions) = versions { + query = query.bind(versions.iter().map(|x| x.0).collect::>()); } Ok(query.fetch_all().await?) diff --git a/src/database/models/collection_item.rs b/src/database/models/collection_item.rs index a24f5dcb..d0cf23e7 100644 --- a/src/database/models/collection_item.rs +++ b/src/database/models/collection_item.rs @@ -210,7 +210,7 @@ impl Collection { color: m.color.map(|x| x as u32), created: m.created, updated: m.updated, - status: CollectionStatus::from_str(&m.status), + status: CollectionStatus::from_string(&m.status), projects: m .mods .unwrap_or_default() diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index f214c9ad..68e44967 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -630,10 +630,10 @@ impl Project { license_url: m.license_url.clone(), discord_url: m.discord_url.clone(), client_side: SideTypeId(m.client_side), - status: ProjectStatus::from_str( + status: ProjectStatus::from_string( &m.status, ), - requested_status: m.requested_status.map(|x| ProjectStatus::from_str( + requested_status: m.requested_status.map(|x| ProjectStatus::from_string( &x, )), server_side: SideTypeId(m.server_side), @@ -647,7 +647,7 @@ impl Project { webhook_sent: m.webhook_sent, color: m.color.map(|x| x as u32), queued: m.queued, - monetization_status: MonetizationStatus::from_str( + monetization_status: MonetizationStatus::from_string( &m.monetization_status, ), loaders: m.loaders, @@ -685,8 +685,8 @@ impl Project { donation_urls: serde_json::from_value( m.donations.unwrap_or_default(), ).ok().unwrap_or_default(), - client_side: crate::models::projects::SideType::from_str(&m.client_side_type), - server_side: crate::models::projects::SideType::from_str(&m.server_side_type), + client_side: crate::models::projects::SideType::from_string(&m.client_side_type), + server_side: crate::models::projects::SideType::from_string(&m.server_side_type), thread_id: ThreadId(m.thread_id), }})) }) diff --git a/src/database/models/thread_item.rs b/src/database/models/thread_item.rs index b8582cee..afad3241 100644 --- a/src/database/models/thread_item.rs +++ b/src/database/models/thread_item.rs @@ -148,7 +148,7 @@ impl Thread { id: ThreadId(x.id), project_id: x.mod_id.map(ProjectId), report_id: x.report_id.map(ReportId), - type_: ThreadType::from_str(&x.thread_type), + type_: ThreadType::from_string(&x.thread_type), messages: { let mut messages: Vec = serde_json::from_value( x.messages.unwrap_or_default(), diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index 5f732e2c..4f73aab7 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -3,7 +3,7 @@ use super::CollectionId; use crate::database::models::DatabaseError; use crate::database::redis::RedisPool; use crate::models::ids::base62_impl::{parse_base62, to_base62}; -use crate::models::users::{Badges, RecipientType, RecipientWallet}; +use crate::models::users::{Badges, RecipientStatus}; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -35,10 +35,10 @@ pub struct User { pub created: DateTime, pub role: String, pub badges: Badges, + pub balance: Decimal, - pub payout_wallet: Option, - pub payout_wallet_type: Option, - pub payout_address: Option, + pub trolley_id: Option, + pub trolley_account_status: Option, } impl User { @@ -188,9 +188,9 @@ impl User { SELECT id, name, email, avatar_url, username, bio, created, role, badges, - balance, payout_wallet, payout_wallet_type, payout_address, + balance, github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id, - email_verified, password, totp_secret + email_verified, password, totp_secret, trolley_id, trolley_account_status FROM users WHERE id = ANY($1) OR LOWER(username) = ANY($2) ", @@ -220,13 +220,13 @@ impl User { role: u.role, badges: Badges::from_bits(u.badges as u64).unwrap_or_default(), balance: u.balance, - payout_wallet: u.payout_wallet.map(|x| RecipientWallet::from_string(&x)), - payout_wallet_type: u - .payout_wallet_type - .map(|x| RecipientType::from_string(&x)), - payout_address: u.payout_address, password: u.password, totp_secret: u.totp_secret, + trolley_id: u.trolley_id, + trolley_account_status: u + .trolley_account_status + .as_ref() + .map(|x| RecipientStatus::from_string(x)), })) }) .try_collect::>() @@ -352,7 +352,7 @@ impl User { redis: &RedisPool, ) -> Result<(), DatabaseError> { redis - .delete_many(user_ids.into_iter().flat_map(|(id, username)| { + .delete_many(user_ids.iter().flat_map(|(id, username)| { [ (USERS_NAMESPACE, Some(id.0.to_string())), ( diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index 451f22f4..49933677 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -590,9 +590,9 @@ impl Version { downloads: v.downloads, version_type: v.version_type, featured: v.featured, - status: VersionStatus::from_str(&v.status), + status: VersionStatus::from_string(&v.status), requested_status: v.requested_status - .map(|x| VersionStatus::from_str(&x)), + .map(|x| VersionStatus::from_string(&x)), }, files: { #[derive(Deserialize)] @@ -803,7 +803,7 @@ impl Version { .unwrap_or_default().into_iter().map(|x| (x.algorithm, x.hash)).collect(), primary: f.is_primary, size: f.size as u32, - file_type: f.file_type.map(|x| FileType::from_str(&x)), + file_type: f.file_type.map(|x| FileType::from_string(&x)), } } )) diff --git a/src/file_hosting/mock.rs b/src/file_hosting/mock.rs index aa4706bf..c1b62c4c 100644 --- a/src/file_hosting/mock.rs +++ b/src/file_hosting/mock.rs @@ -4,6 +4,7 @@ use bytes::Bytes; use chrono::Utc; use sha2::Digest; +#[derive(Default)] pub struct MockHost(()); impl MockHost { diff --git a/src/lib.rs b/src/lib.rs index 01ff0bcd..fe4a8307 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -365,9 +365,9 @@ pub fn check_env_vars() -> bool { failed |= true; } - failed |= check_var::("PAYPAL_API_URL"); - failed |= check_var::("PAYPAL_CLIENT_ID"); - failed |= check_var::("PAYPAL_CLIENT_SECRET"); + failed |= check_var::("TROLLEY_ACCESS_KEY"); + failed |= check_var::("TROLLEY_SECRET_KEY"); + failed |= check_var::("TROLLEY_WEBHOOK_SIGNATURE"); failed |= check_var::("GITHUB_CLIENT_ID"); failed |= check_var::("GITHUB_CLIENT_SECRET"); diff --git a/src/main.rs b/src/main.rs index 67093c0e..4b25580e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,8 +31,6 @@ async fn main() -> std::io::Result<()> { let sentry = sentry::init(sentry::ClientOptions { release: sentry::release_name!(), traces_sample_rate: 0.1, - enable_profiling: true, - profiles_sample_rate: 0.1, ..Default::default() }); if sentry.is_enabled() { diff --git a/src/models/collections.rs b/src/models/collections.rs index cd8cd90c..63188183 100644 --- a/src/models/collections.rs +++ b/src/models/collections.rs @@ -80,7 +80,7 @@ impl std::fmt::Display for CollectionStatus { } impl CollectionStatus { - pub fn from_str(string: &str) -> CollectionStatus { + pub fn from_string(string: &str) -> CollectionStatus { match string { "listed" => CollectionStatus::Listed, "unlisted" => CollectionStatus::Unlisted, diff --git a/src/models/pats.rs b/src/models/pats.rs index 5d3f65ca..6ac2afc8 100644 --- a/src/models/pats.rs +++ b/src/models/pats.rs @@ -1,4 +1,5 @@ use super::ids::Base62Id; +use crate::bitflags_serde_impl; use crate::models::ids::UserId; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -10,8 +11,7 @@ use serde::{Deserialize, Serialize}; pub struct PatId(pub u64); bitflags::bitflags! { - #[derive(Serialize, Deserialize)] - #[serde(transparent)] + #[derive(Copy, Clone, Debug)] pub struct Scopes: u64 { // read a user's email const USER_READ_EMAIL = 1 << 0; @@ -107,6 +107,8 @@ bitflags::bitflags! { } } +bitflags_serde_impl!(Scopes, u64); + impl Scopes { // these scopes cannot be specified in a personal access token pub fn restricted() -> Scopes { diff --git a/src/models/projects.rs b/src/models/projects.rs index 716f8ac5..63f48f11 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -247,7 +247,7 @@ impl SideType { } } - pub fn from_str(string: &str) -> SideType { + pub fn from_string(string: &str) -> SideType { match string { "required" => SideType::Required, "optional" => SideType::Optional, @@ -308,7 +308,7 @@ impl std::fmt::Display for ProjectStatus { } impl ProjectStatus { - pub fn from_str(string: &str) -> ProjectStatus { + pub fn from_string(string: &str) -> ProjectStatus { match string { "processing" => ProjectStatus::Processing, "rejected" => ProjectStatus::Rejected, @@ -433,7 +433,7 @@ impl std::fmt::Display for MonetizationStatus { } impl MonetizationStatus { - pub fn from_str(string: &str) -> MonetizationStatus { + pub fn from_string(string: &str) -> MonetizationStatus { match string { "force-demonetized" => MonetizationStatus::ForceDemonetized, "demonetized" => MonetizationStatus::Demonetized, @@ -537,7 +537,7 @@ impl From for Version { version_id: d.version_id.map(|i| VersionId(i.0 as u64)), project_id: d.project_id.map(|i| ProjectId(i.0 as u64)), file_name: d.file_name, - dependency_type: DependencyType::from_str(d.dependency_type.as_str()), + dependency_type: DependencyType::from_string(d.dependency_type.as_str()), }) .collect(), game_versions: data.game_versions.into_iter().map(GameVersion).collect(), @@ -570,7 +570,7 @@ impl std::fmt::Display for VersionStatus { } impl VersionStatus { - pub fn from_str(string: &str) -> VersionStatus { + pub fn from_string(string: &str) -> VersionStatus { match string { "listed" => VersionStatus::Listed, "draft" => VersionStatus::Draft, @@ -718,7 +718,7 @@ impl DependencyType { } } - pub fn from_str(string: &str) -> DependencyType { + pub fn from_string(string: &str) -> DependencyType { match string { "required" => DependencyType::Required, "optional" => DependencyType::Optional, @@ -753,7 +753,7 @@ impl FileType { } } - pub fn from_str(string: &str) -> FileType { + pub fn from_string(string: &str) -> FileType { match string { "required-resource-pack" => FileType::RequiredResourcePack, "optional-resource-pack" => FileType::OptionalResourcePack, diff --git a/src/models/teams.rs b/src/models/teams.rs index 046a1f6b..9182474d 100644 --- a/src/models/teams.rs +++ b/src/models/teams.rs @@ -1,4 +1,5 @@ use super::ids::Base62Id; +use crate::bitflags_serde_impl; use crate::models::users::User; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -22,8 +23,7 @@ pub struct Team { } bitflags::bitflags! { - #[derive(Serialize, Deserialize)] - #[serde(transparent)] + #[derive(Copy, Clone, Debug)] pub struct ProjectPermissions: u64 { const UPLOAD_VERSION = 1 << 0; const DELETE_VERSION = 1 << 1; @@ -40,6 +40,8 @@ bitflags::bitflags! { } } +bitflags_serde_impl!(ProjectPermissions, u64); + impl Default for ProjectPermissions { fn default() -> ProjectPermissions { ProjectPermissions::UPLOAD_VERSION | ProjectPermissions::DELETE_VERSION @@ -77,8 +79,7 @@ impl ProjectPermissions { } bitflags::bitflags! { - #[derive(Serialize, Deserialize)] - #[serde(transparent)] + #[derive(Copy, Clone, Debug)] pub struct OrganizationPermissions: u64 { const EDIT_DETAILS = 1 << 0; const EDIT_BODY = 1 << 1; @@ -94,6 +95,8 @@ bitflags::bitflags! { } } +bitflags_serde_impl!(OrganizationPermissions, u64); + impl Default for OrganizationPermissions { fn default() -> OrganizationPermissions { OrganizationPermissions::NONE diff --git a/src/models/threads.rs b/src/models/threads.rs index 5a5a214d..2bcb706e 100644 --- a/src/models/threads.rs +++ b/src/models/threads.rs @@ -78,7 +78,7 @@ impl ThreadType { } } - pub fn from_str(string: &str) -> ThreadType { + pub fn from_string(string: &str) -> ThreadType { match string { "report" => ThreadType::Report, "project" => ThreadType::Project, diff --git a/src/models/users.rs b/src/models/users.rs index 4b2a0e90..8a012bf1 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,5 +1,6 @@ use super::ids::Base62Id; use crate::auth::flows::AuthProvider; +use crate::bitflags_serde_impl; use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; @@ -12,8 +13,7 @@ pub struct UserId(pub u64); pub const DELETED_USER: UserId = UserId(127155982985829); bitflags::bitflags! { - #[derive(Serialize, Deserialize)] - #[serde(transparent)] + #[derive(Copy, Clone, Debug)] pub struct Badges: u64 { // 1 << 0 unused - ignore + replace with something later const MIDAS = 1 << 0; @@ -29,6 +29,8 @@ bitflags::bitflags! { } } +bitflags_serde_impl!(Badges, u64); + impl Default for Badges { fn default() -> Badges { Badges::NONE @@ -46,12 +48,12 @@ pub struct User { pub role: Role, pub badges: Badges, - pub payout_data: Option, pub auth_providers: Option>, pub email: Option, pub email_verified: Option, pub has_password: Option, pub has_totp: Option, + pub payout_data: Option, // DEPRECATED. Always returns None pub github_id: Option, @@ -60,77 +62,8 @@ pub struct User { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct UserPayoutData { pub balance: Decimal, - pub payout_wallet: Option, - pub payout_wallet_type: Option, - pub payout_address: Option, -} - -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(rename_all = "snake_case")] -pub enum RecipientType { - Email, - Phone, - UserHandle, -} - -impl std::fmt::Display for RecipientType { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - fmt.write_str(self.as_str()) - } -} - -impl RecipientType { - pub fn from_string(string: &str) -> RecipientType { - match string { - "user_handle" => RecipientType::UserHandle, - "phone" => RecipientType::Phone, - _ => RecipientType::Email, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - RecipientType::Email => "email", - RecipientType::Phone => "phone", - RecipientType::UserHandle => "user_handle", - } - } -} - -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(rename_all = "snake_case")] -pub enum RecipientWallet { - Venmo, - Paypal, -} - -impl std::fmt::Display for RecipientWallet { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - fmt.write_str(self.as_str()) - } -} - -impl RecipientWallet { - pub fn from_string(string: &str) -> RecipientWallet { - match string { - "venmo" => RecipientWallet::Venmo, - _ => RecipientWallet::Paypal, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - RecipientWallet::Paypal => "paypal", - RecipientWallet::Venmo => "venmo", - } - } - - pub fn as_str_api(&self) -> &'static str { - match self { - RecipientWallet::Paypal => "PayPal", - RecipientWallet::Venmo => "Venmo", - } - } + pub trolley_id: Option, + pub trolley_status: Option, } use crate::database::models::user_item::User as DBUser; @@ -201,3 +134,89 @@ impl Role { } } } + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +#[serde(rename_all = "lowercase")] +pub enum RecipientStatus { + Active, + Incomplete, + Disabled, + Archived, + Suspended, + Blocked, +} + +impl RecipientStatus { + pub fn from_string(string: &str) -> RecipientStatus { + match string { + "active" => RecipientStatus::Active, + "incomplete" => RecipientStatus::Incomplete, + "disabled" => RecipientStatus::Disabled, + "archived" => RecipientStatus::Archived, + "suspended" => RecipientStatus::Suspended, + "blocked" => RecipientStatus::Blocked, + _ => RecipientStatus::Disabled, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + RecipientStatus::Active => "active", + RecipientStatus::Incomplete => "incomplete", + RecipientStatus::Disabled => "disabled", + RecipientStatus::Archived => "archived", + RecipientStatus::Suspended => "suspended", + RecipientStatus::Blocked => "blocked", + } + } +} + +#[derive(Serialize)] +pub struct Payout { + pub created: DateTime, + pub amount: Decimal, + pub status: PayoutStatus, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PayoutStatus { + Pending, + Failed, + Processed, + Returned, + Processing, +} + +impl PayoutStatus { + pub fn from_string(string: &str) -> PayoutStatus { + match string { + "pending" => PayoutStatus::Pending, + "failed" => PayoutStatus::Failed, + "processed" => PayoutStatus::Processed, + "returned" => PayoutStatus::Returned, + "processing" => PayoutStatus::Processing, + _ => PayoutStatus::Processing, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + PayoutStatus::Pending => "pending", + PayoutStatus::Failed => "failed", + PayoutStatus::Processed => "processed", + PayoutStatus::Returned => "returned", + PayoutStatus::Processing => "processing", + } + } + + pub fn is_failed(&self) -> bool { + match self { + PayoutStatus::Pending => false, + PayoutStatus::Failed => true, + PayoutStatus::Processed => false, + PayoutStatus::Returned => true, + PayoutStatus::Processing => false, + } + } +} diff --git a/src/queue/analytics.rs b/src/queue/analytics.rs index 78a63ebc..8a02d99c 100644 --- a/src/queue/analytics.rs +++ b/src/queue/analytics.rs @@ -14,6 +14,12 @@ pub struct AnalyticsQueue { playtime_queue: DashSet, } +impl Default for AnalyticsQueue { + fn default() -> Self { + Self::new() + } +} + // Batches analytics data points + transactions every few minutes impl AnalyticsQueue { pub fn new() -> Self { diff --git a/src/queue/download.rs b/src/queue/download.rs index 05bad06b..6bd571f1 100644 --- a/src/queue/download.rs +++ b/src/queue/download.rs @@ -6,6 +6,12 @@ pub struct DownloadQueue { queue: Mutex>, } +impl Default for DownloadQueue { + fn default() -> Self { + Self::new() + } +} + // Batches download transactions every thirty seconds impl DownloadQueue { pub fn new() -> Self { diff --git a/src/queue/payouts.rs b/src/queue/payouts.rs index 737fe31b..2c9608a2 100644 --- a/src/queue/payouts.rs +++ b/src/queue/payouts.rs @@ -1,203 +1,332 @@ use crate::routes::ApiError; use crate::util::env::parse_var; use crate::{database::redis::RedisPool, models::projects::MonetizationStatus}; -use base64::Engine; use chrono::{DateTime, Datelike, Duration, Utc, Weekday}; +use hex::ToHex; +use hmac::{Hmac, Mac, NewMac}; +use reqwest::Method; use rust_decimal::Decimal; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use serde_json::json; +use serde_json::{json, Value}; +use sha2::Sha256; use sqlx::PgPool; use std::collections::HashMap; pub struct PayoutsQueue { - credential: PaypalCredential, - credential_expires: DateTime, + access_key: String, + secret_key: String, } -#[derive(Deserialize, Default)] -struct PaypalCredential { - access_token: String, - token_type: String, - expires_in: i64, +impl Default for PayoutsQueue { + fn default() -> Self { + Self::new() + } } -#[derive(Serialize)] -pub struct PayoutItem { - pub amount: PayoutAmount, - pub receiver: String, - pub note: String, - pub recipient_type: String, - pub recipient_wallet: String, - pub sender_item_id: String, +#[derive(Clone, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AccountUser { + Business { name: String }, + Individual { first: String, last: String }, } -#[derive(Serialize, Deserialize)] -pub struct PayoutAmount { - pub currency: String, - #[serde(with = "rust_decimal::serde::str")] - pub value: Decimal, +#[derive(Serialize)] +pub struct PaymentInfo { + country: String, + payout_method: String, + route_minimum: Decimal, + estimated_fees: Decimal, + deduct_fees: Decimal, } // Batches payouts and handles token refresh impl PayoutsQueue { pub fn new() -> Self { PayoutsQueue { - credential: Default::default(), - credential_expires: Utc::now() - Duration::days(30), + access_key: dotenvy::var("TROLLEY_ACCESS_KEY").expect("missing trolley access key"), + secret_key: dotenvy::var("TROLLEY_SECRET_KEY").expect("missing trolley secret key"), } } - pub async fn refresh_token(&mut self) -> Result<(), ApiError> { + pub async fn make_trolley_request( + &self, + method: Method, + path: &str, + body: Option, + ) -> Result { + let timestamp = Utc::now().timestamp(); + + let mut mac: Hmac = Hmac::new_from_slice(self.secret_key.as_bytes()) + .map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?; + mac.update( + if let Some(body) = &body { + format!( + "{}\n{}\n{}\n{}\n", + timestamp, + method.as_str(), + path, + serde_json::to_string(&body)? + ) + } else { + format!("{}\n{}\n{}\n\n", timestamp, method.as_str(), path) + } + .as_bytes(), + ); + let request_signature = mac.finalize().into_bytes().encode_hex::(); + let client = reqwest::Client::new(); - let combined_key = format!( - "{}:{}", - dotenvy::var("PAYPAL_CLIENT_ID")?, - dotenvy::var("PAYPAL_CLIENT_SECRET")? - ); - let formatted_key = format!( - "Basic {}", - base64::engine::general_purpose::STANDARD.encode(combined_key) - ); + let mut request = client + .request(method, format!("https://api.trolley.com{path}")) + .header( + "Authorization", + format!("prsign {}:{}", self.access_key, request_signature), + ) + .header("X-PR-Timestamp", timestamp); - let mut form = HashMap::new(); - form.insert("grant_type", "client_credentials"); + if let Some(body) = body { + request = request.json(&body); + } - let credential: PaypalCredential = client - .post(&format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?)) - .header("Accept", "application/json") - .header("Accept-Language", "en_US") - .header("Authorization", formatted_key) - .form(&form) + let resp = request .send() .await - .map_err(|_| ApiError::Payments("Error while authenticating with PayPal".to_string()))? - .json() - .await - .map_err(|_| { - ApiError::Payments( - "Error while authenticating with PayPal (deser error)".to_string(), - ) - })?; + .map_err(|_| ApiError::Payments("could not communicate with Trolley".to_string()))?; + + let value = resp.json::().await.map_err(|_| { + ApiError::Payments("could not retrieve Trolley response body".to_string()) + })?; + + if let Some(obj) = value.as_object() { + if !obj.get("ok").and_then(|x| x.as_bool()).unwrap_or(true) { + #[derive(Deserialize)] + struct TrolleyError { + field: Option, + message: String, + } + + if let Some(array) = obj.get("errors") { + let err = serde_json::from_value::>(array.clone()).map_err( + |_| { + ApiError::Payments( + "could not retrieve Trolley error json body".to_string(), + ) + }, + )?; + + if let Some(first) = err.into_iter().next() { + return Err(ApiError::Payments(if let Some(field) = &first.field { + format!("error - field: {field} message: {}", first.message) + } else { + first.message + })); + } + } - self.credential_expires = Utc::now() + Duration::seconds(credential.expires_in); - self.credential = credential; + return Err(ApiError::Payments( + "could not retrieve Trolley error body".to_string(), + )); + } + } - Ok(()) + Ok(serde_json::from_value(value)?) } - pub async fn send_payout(&mut self, mut payout: PayoutItem) -> Result { - if self.credential_expires < Utc::now() { - self.refresh_token().await.map_err(|_| { - ApiError::Payments("Error while authenticating with PayPal".to_string()) - })?; + pub async fn send_payout( + &mut self, + recipient: &str, + amount: Decimal, + ) -> Result<(String, Option), ApiError> { + #[derive(Deserialize)] + struct TrolleyRes { + batch: Batch, } - let wallet = payout.recipient_wallet.clone(); + #[derive(Deserialize)] + struct Batch { + id: String, + payments: BatchPayments, + } - let fee = if wallet == *"Venmo" { - Decimal::ONE / Decimal::from(4) - } else { - std::cmp::min( - std::cmp::max( - Decimal::ONE / Decimal::from(4), - (Decimal::from(2) / Decimal::ONE_HUNDRED) * payout.amount.value, - ), - Decimal::from(20), - ) - }; + #[derive(Deserialize)] + struct Payment { + id: String, + } + + #[derive(Deserialize)] + struct BatchPayments { + payments: Vec, + } - payout.amount.value -= fee; - payout.amount.value = payout.amount.value.round_dp(2); + let fee = self.get_estimated_fees(recipient, amount).await?; - if payout.amount.value <= Decimal::ZERO { - return Err(ApiError::InvalidInput( - "You do not have enough funds to make this payout!".to_string(), + if fee.estimated_fees > amount || fee.route_minimum > amount { + return Err(ApiError::Payments( + "Account balance is too low to withdraw funds".to_string(), )); } - let client = reqwest::Client::new(); + let send_amount = amount - fee.deduct_fees; + + let res = self + .make_trolley_request::<_, TrolleyRes>( + Method::POST, + "/v1/batches/", + Some(json!({ + "currency": "USD", + "description": "labrinth payout", + "payments": [{ + "recipient": { + "id": recipient + }, + "amount": send_amount.to_string(), + "currency": "USD", + "memo": "Modrinth ad revenue payout" + }], + })), + ) + .await?; - let res = client.post(&format!("{}payments/payouts", dotenvy::var("PAYPAL_API_URL")?)) - .header("Authorization", format!("{} {}", self.credential.token_type, self.credential.access_token)) - .json(&json! ({ - "sender_batch_header": { - "sender_batch_id": format!("{}-payouts", Utc::now().to_rfc3339()), - "email_subject": "You have received a payment from Modrinth!", - "email_message": "Thank you for creating projects on Modrinth. Please claim this payment within 30 days.", - }, - "items": vec![payout] - })) - .send().await.map_err(|_| ApiError::Payments("Error while sending payout to PayPal".to_string()))?; - - if !res.status().is_success() { - #[derive(Deserialize)] - struct PayPalError { - pub body: PayPalErrorBody, - } + self.make_trolley_request::( + Method::POST, + &format!("/v1/batches/{}/start-processing", res.batch.id), + None, + ) + .await?; - #[derive(Deserialize)] - struct PayPalErrorBody { - pub message: String, - } + let payment_id = res.batch.payments.payments.into_iter().next().map(|x| x.id); - let body: PayPalError = res.json().await.map_err(|_| { - ApiError::Payments("Error while registering payment in PayPal!".to_string()) - })?; - - return Err(ApiError::Payments(format!( - "Error while registering payment in PayPal: {}", - body.body.message - ))); - } else if wallet != *"Venmo" { - #[derive(Deserialize)] - struct PayPalLink { - href: String, - } + Ok((res.batch.id, payment_id)) + } - #[derive(Deserialize)] - struct PayoutsResponse { - pub links: Vec, - } + pub async fn register_recipient( + &self, + email: &str, + user: AccountUser, + ) -> Result { + #[derive(Deserialize)] + struct TrolleyRes { + recipient: Recipient, + } - #[derive(Deserialize)] - struct PayoutDataItem { - payout_item_fee: PayoutAmount, - } + #[derive(Deserialize)] + struct Recipient { + id: String, + } - #[derive(Deserialize)] - struct PayoutData { - pub items: Vec, - } + let id = self + .make_trolley_request::<_, TrolleyRes>( + Method::POST, + "/v1/recipients/", + Some(match user { + AccountUser::Business { name } => json!({ + "type": "business", + "email": email, + "name": name, + }), + AccountUser::Individual { first, last } => json!({ + "type": "individual", + "firstName": first, + "lastName": last, + "email": email, + }), + }), + ) + .await?; - // Calculate actual fee + refund if we took too big of a fee. - if let Ok(res) = res.json::().await { - if let Some(link) = res.links.first() { - if let Ok(res) = client - .get(&link.href) - .header( - "Authorization", - format!( - "{} {}", - self.credential.token_type, self.credential.access_token - ), - ) - .send() - .await - { - if let Ok(res) = res.json::().await { - if let Some(data) = res.items.first() { - if (fee - data.payout_item_fee.value) > Decimal::ZERO { - return Ok(fee - data.payout_item_fee.value); - } - } - } - } - } - } + Ok(id.recipient.id) + } + + // lhs minimum, rhs estimate + pub async fn get_estimated_fees( + &self, + id: &str, + amount: Decimal, + ) -> Result { + #[derive(Deserialize)] + struct TrolleyRes { + recipient: Recipient, } - Ok(Decimal::ZERO) + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Recipient { + route_minimum: Option, + estimated_fees: Option, + address: RecipientAddress, + payout_method: String, + } + + #[derive(Deserialize)] + struct RecipientAddress { + country: String, + } + + let id = self + .make_trolley_request::( + Method::GET, + &format!("/v1/recipients/{id}"), + None, + ) + .await?; + + if &id.recipient.payout_method == "paypal" { + // based on https://www.paypal.com/us/webapps/mpp/merchant-fees. see paypal payouts section + let fee = if &id.recipient.address.country == "US" { + std::cmp::min( + std::cmp::max( + Decimal::ONE / Decimal::from(4), + (Decimal::from(2) / Decimal::ONE_HUNDRED) * amount, + ), + Decimal::from(1), + ) + } else { + std::cmp::min( + (Decimal::from(2) / Decimal::ONE_HUNDRED) * amount, + Decimal::from(20), + ) + }; + + Ok(PaymentInfo { + country: id.recipient.address.country, + payout_method: id.recipient.payout_method, + route_minimum: fee, + estimated_fees: fee, + deduct_fees: fee, + }) + } else if &id.recipient.payout_method == "venmo" { + let venmo_fee = Decimal::ONE / Decimal::from(4); + + Ok(PaymentInfo { + country: id.recipient.address.country, + payout_method: id.recipient.payout_method, + route_minimum: id.recipient.route_minimum.unwrap_or(Decimal::ZERO) + venmo_fee, + estimated_fees: id.recipient.estimated_fees.unwrap_or(Decimal::ZERO) + venmo_fee, + deduct_fees: venmo_fee, + }) + } else { + Ok(PaymentInfo { + country: id.recipient.address.country, + payout_method: id.recipient.payout_method, + route_minimum: id.recipient.route_minimum.unwrap_or(Decimal::ZERO), + estimated_fees: id.recipient.estimated_fees.unwrap_or(Decimal::ZERO), + deduct_fees: Decimal::ZERO, + }) + } + } + + pub async fn update_recipient_email(&self, id: &str, email: &str) -> Result<(), ApiError> { + self.make_trolley_request::<_, Value>( + Method::PATCH, + &format!("/v1/recipients/{}", id), + Some(json!({ + "email": email, + })), + ) + .await?; + + Ok(()) } } @@ -206,7 +335,7 @@ pub async fn process_payout( redis: &RedisPool, client: &clickhouse::Client, ) -> Result<(), ApiError> { - let start: DateTime = DateTime::from_utc( + let start: DateTime = DateTime::from_naive_utc_and_offset( (Utc::now() - Duration::days(1)) .date_naive() .and_hms_nano_opt(0, 0, 0, 0) diff --git a/src/queue/session.rs b/src/queue/session.rs index bbc2896e..8948810d 100644 --- a/src/queue/session.rs +++ b/src/queue/session.rs @@ -13,6 +13,12 @@ pub struct AuthQueue { pat_queue: Mutex>, } +impl Default for AuthQueue { + fn default() -> Self { + Self::new() + } +} + // Batches session accessing transactions every 30 seconds impl AuthQueue { pub fn new() -> Self { diff --git a/src/ratelimit/memory.rs b/src/ratelimit/memory.rs index 2e786835..ee52ca6a 100644 --- a/src/ratelimit/memory.rs +++ b/src/ratelimit/memory.rs @@ -15,6 +15,12 @@ pub struct MemoryStore { inner: Arc>, } +impl Default for MemoryStore { + fn default() -> Self { + Self::new() + } +} + impl MemoryStore { /// Create a new hashmap /// diff --git a/src/routes/analytics.rs b/src/routes/analytics.rs index 932cc8b7..36efdde0 100644 --- a/src/routes/analytics.rs +++ b/src/routes/analytics.rs @@ -6,10 +6,10 @@ use crate::queue::analytics::AnalyticsQueue; use crate::queue::maxmind::MaxMindIndexer; use crate::queue::session::AuthQueue; use crate::routes::ApiError; +use crate::util::date::get_current_tenths_of_ms; use crate::util::env::parse_strings_from_var; use actix_web::{post, web}; use actix_web::{HttpRequest, HttpResponse}; -use chrono::Utc; use serde::Deserialize; use sqlx::PgPool; use std::collections::HashMap; @@ -108,7 +108,7 @@ pub async fn page_view_ingest( let mut view = PageView { id: Uuid::new_v4(), - recorded: Utc::now().timestamp_nanos() / 100_000, + recorded: get_current_tenths_of_ms(), domain: domain.to_string(), site_path: url.path().to_string(), user_id: 0, @@ -204,7 +204,7 @@ pub async fn playtime_ingest( if let Some(version) = versions.iter().find(|x| id == x.inner.id.into()) { analytics_queue.add_playtime(Playtime { id: Default::default(), - recorded: Utc::now().timestamp_nanos() / 100_000, + recorded: get_current_tenths_of_ms(), seconds: playtime.seconds as u64, user_id: user.id.0, project_id: version.inner.project_id.0 as u64, diff --git a/src/routes/v2/admin.rs b/src/routes/v2/admin.rs index 439e83ec..cc5bd9e6 100644 --- a/src/routes/v2/admin.rs +++ b/src/routes/v2/admin.rs @@ -1,17 +1,23 @@ use crate::auth::validate::get_user_record_from_bearer_token; +use crate::database::models::User; use crate::database::redis::RedisPool; use crate::models::analytics::Download; use crate::models::ids::ProjectId; use crate::models::pats::Scopes; +use crate::models::users::{PayoutStatus, RecipientStatus}; use crate::queue::analytics::AnalyticsQueue; use crate::queue::download::DownloadQueue; use crate::queue::maxmind::MaxMindIndexer; use crate::queue::session::AuthQueue; use crate::routes::ApiError; +use crate::util::date::get_current_tenths_of_ms; use crate::util::guards::admin_key_guard; -use actix_web::{patch, web, HttpRequest, HttpResponse}; -use chrono::Utc; +use crate::util::routes::read_from_payload; +use actix_web::{patch, post, web, HttpRequest, HttpResponse}; +use hex::ToHex; +use hmac::{Hmac, Mac, NewMac}; use serde::Deserialize; +use sha2::Sha256; use sqlx::PgPool; use std::collections::HashMap; use std::net::Ipv4Addr; @@ -19,7 +25,11 @@ use std::sync::Arc; use uuid::Uuid; pub fn config(cfg: &mut web::ServiceConfig) { - cfg.service(web::scope("admin").service(count_download)); + cfg.service( + web::scope("admin") + .service(count_download) + .service(trolley_webhook), + ); } #[derive(Deserialize)] @@ -110,7 +120,7 @@ pub async fn count_download( analytics_queue.add_download(Download { id: Uuid::new_v4(), - recorded: Utc::now().timestamp_nanos() / 100_000, + recorded: get_current_tenths_of_ms(), domain: url.host_str().unwrap_or_default().to_string(), site_path: url.path().to_string(), user_id: user @@ -141,3 +151,171 @@ pub async fn count_download( Ok(HttpResponse::NoContent().body("")) } + +#[derive(Deserialize)] +pub struct TrolleyWebhook { + model: String, + action: String, + body: HashMap, +} + +#[post("/_trolley")] +#[allow(clippy::too_many_arguments)] +pub async fn trolley_webhook( + req: HttpRequest, + pool: web::Data, + redis: web::Data, + mut payload: web::Payload, +) -> Result { + if let Some(signature) = req.headers().get("X-PaymentRails-Signature") { + let payload = read_from_payload( + &mut payload, + 1 << 20, + "Webhook payload exceeds the maximum of 1MiB.", + ) + .await?; + + let mut signature = signature.to_str().ok().unwrap_or_default().split(','); + let timestamp = signature + .next() + .and_then(|x| x.split('=').nth(1)) + .unwrap_or_default(); + let v1 = signature + .next() + .and_then(|x| x.split('=').nth(1)) + .unwrap_or_default(); + + let mut mac: Hmac = + Hmac::new_from_slice(dotenvy::var("TROLLEY_WEBHOOK_SIGNATURE")?.as_bytes()) + .map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?; + mac.update(timestamp.as_bytes()); + mac.update(&payload); + let request_signature = mac.finalize().into_bytes().encode_hex::(); + + if &*request_signature == v1 { + let webhook = serde_json::from_slice::(&payload)?; + + if webhook.model == "recipient" { + #[derive(Deserialize)] + struct Recipient { + pub id: String, + pub email: Option, + pub status: Option, + } + + if let Some(body) = webhook.body.get("recipient") { + if let Ok(recipient) = serde_json::from_value::(body.clone()) { + let value = sqlx::query!( + "SELECT id FROM users WHERE trolley_id = $1", + recipient.id + ) + .fetch_optional(&**pool) + .await?; + + if let Some(user) = value { + let user = User::get_id( + crate::database::models::UserId(user.id), + &**pool, + &redis, + ) + .await?; + + if let Some(user) = user { + let mut transaction = pool.begin().await?; + + if webhook.action == "deleted" { + sqlx::query!( + " + UPDATE users + SET trolley_account_status = NULL, trolley_id = NULL + WHERE id = $1 + ", + user.id.0 + ) + .execute(&mut transaction) + .await?; + } else { + sqlx::query!( + " + UPDATE users + SET email = $1, email_verified = $2, trolley_account_status = $3 + WHERE id = $4 + ", + recipient.email.clone(), + user.email_verified && recipient.email == user.email, + recipient.status.map(|x| x.as_str()), + user.id.0 + ) + .execute(&mut transaction).await?; + } + + transaction.commit().await?; + User::clear_caches(&[(user.id, None)], &redis).await?; + } + } + } + } + } + + if webhook.model == "payment" { + #[derive(Deserialize)] + struct Payment { + pub id: String, + pub status: PayoutStatus, + } + + if let Some(body) = webhook.body.get("payment") { + if let Ok(payment) = serde_json::from_value::(body.clone()) { + let value = sqlx::query!( + "SELECT id, amount, user_id, status FROM historical_payouts WHERE payment_id = $1", + payment.id + ) + .fetch_optional(&**pool) + .await?; + + if let Some(payout) = value { + let mut transaction = pool.begin().await?; + + if payment.status.is_failed() + && !PayoutStatus::from_string(&payout.status).is_failed() + { + sqlx::query!( + " + UPDATE users + SET balance = balance + $1 + WHERE id = $2 + ", + payout.amount, + payout.user_id, + ) + .execute(&mut transaction) + .await?; + } + + sqlx::query!( + " + UPDATE historical_payouts + SET status = $1 + WHERE payment_id = $2 + ", + payment.status.as_str(), + payment.id, + ) + .execute(&mut transaction) + .await?; + + transaction.commit().await?; + User::clear_caches( + &[(crate::database::models::UserId(payout.user_id), None)], + &redis, + ) + .await?; + } + } + } + } + } + } + + Ok(HttpResponse::NoContent().finish()) +} diff --git a/src/routes/v2/mod.rs b/src/routes/v2/mod.rs index 622f2cab..3f95ae6d 100644 --- a/src/routes/v2/mod.rs +++ b/src/routes/v2/mod.rs @@ -32,7 +32,6 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) { .configure(moderation::config) .configure(notifications::config) .configure(organizations::config) - //.configure(pats::config) .configure(project_creation::config) .configure(collections::config) .configure(images::config) diff --git a/src/routes/v2/users.rs b/src/routes/v2/users.rs index bda564a7..f2b483f3 100644 --- a/src/routes/v2/users.rs +++ b/src/routes/v2/users.rs @@ -1,4 +1,4 @@ -use crate::auth::{get_user_from_headers, AuthenticationError}; +use crate::auth::get_user_from_headers; use crate::database::models::User; use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; @@ -6,14 +6,15 @@ use crate::models::collections::{Collection, CollectionStatus}; use crate::models::notifications::Notification; use crate::models::pats::Scopes; use crate::models::projects::Project; -use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId}; -use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue}; +use crate::models::users::{ + Badges, Payout, PayoutStatus, RecipientStatus, Role, UserId, UserPayoutData, +}; +use crate::queue::payouts::PayoutsQueue; use crate::queue::session::AuthQueue; use crate::routes::ApiError; use crate::util::routes::read_from_payload; use crate::util::validate::validation_errors_to_string; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; -use chrono::{DateTime, Utc}; use lazy_static::lazy_static; use regex::Regex; use rust_decimal::Decimal; @@ -39,6 +40,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .service(user_notifications) .service(user_follows) .service(user_payouts) + .service(user_payouts_fees) .service(user_payouts_request), ); } @@ -218,21 +220,6 @@ pub struct EditUser { pub bio: Option>, pub role: Option, pub badges: Option, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate] - pub payout_data: Option>, -} - -#[derive(Serialize, Deserialize, Validate)] -pub struct EditPayoutData { - pub payout_wallet: RecipientWallet, - pub payout_wallet_type: RecipientType, - #[validate(length(max = 128))] - pub payout_address: String, } #[patch("{id}")] @@ -244,7 +231,7 @@ pub async fn user_edit( redis: web::Data, session_queue: web::Data, ) -> Result { - let (scopes, user) = get_user_from_headers( + let (_scopes, user) = get_user_from_headers( &req, &**pool, &redis, @@ -364,79 +351,6 @@ pub async fn user_edit( .await?; } - if let Some(payout_data) = &new_user.payout_data { - if let Some(payout_data) = payout_data { - if payout_data.payout_wallet_type == RecipientType::UserHandle - && payout_data.payout_wallet == RecipientWallet::Paypal - { - return Err(ApiError::InvalidInput( - "You cannot use a paypal wallet with a user handle!".to_string(), - )); - } - - if !scopes.contains(Scopes::PAYOUTS_WRITE) { - return Err(ApiError::Authentication( - AuthenticationError::InvalidCredentials, - )); - } - - if !match payout_data.payout_wallet_type { - RecipientType::Email => { - validator::validate_email(&payout_data.payout_address) - } - RecipientType::Phone => { - validator::validate_phone(&payout_data.payout_address) - } - RecipientType::UserHandle => true, - } { - return Err(ApiError::InvalidInput( - "Invalid wallet specified!".to_string(), - )); - } - - let results = sqlx::query!( - " - SELECT EXISTS(SELECT 1 FROM users WHERE id = $1 AND email IS NULL) - ", - id as crate::database::models::ids::UserId, - ) - .fetch_one(&mut *transaction) - .await?; - - if results.exists.unwrap_or(false) { - return Err(ApiError::InvalidInput( - "You must have an email set on your Modrinth account to enroll in the monetization program!" - .to_string(), - )); - } - - sqlx::query!( - " - UPDATE users - SET payout_wallet = $1, payout_wallet_type = $2, payout_address = $3 - WHERE (id = $4) - ", - payout_data.payout_wallet.as_str(), - payout_data.payout_wallet_type.as_str(), - payout_data.payout_address, - id as crate::database::models::ids::UserId, - ) - .execute(&mut *transaction) - .await?; - } else { - sqlx::query!( - " - UPDATE users - SET payout_wallet = NULL, payout_wallet_type = NULL, payout_address = NULL - WHERE (id = $1) - ", - id as crate::database::models::ids::UserId, - ) - .execute(&mut *transaction) - .await?; - } - } - User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?; transaction.commit().await?; Ok(HttpResponse::NoContent().body("")) @@ -691,13 +605,6 @@ pub async fn user_notifications( } } -#[derive(Serialize)] -pub struct Payout { - pub created: DateTime, - pub amount: Decimal, - pub status: String, -} - #[get("{id}/payouts")] pub async fn user_payouts( req: HttpRequest, @@ -757,7 +664,7 @@ pub async fn user_payouts( Ok(e.right().map(|row| Payout { created: row.created, amount: row.amount, - status: row.status, + status: PayoutStatus::from_string(&row.status), })) }) .try_collect::>(), @@ -776,6 +683,61 @@ pub async fn user_payouts( } } +#[derive(Deserialize)] +pub struct FeeEstimateAmount { + amount: Decimal, +} + +#[get("{id}/payouts_fees")] +pub async fn user_payouts_fees( + req: HttpRequest, + info: web::Path<(String,)>, + web::Query(amount): web::Query, + pool: web::Data, + redis: web::Data, + session_queue: web::Data, + payouts_queue: web::Data>, +) -> Result { + let user = get_user_from_headers( + &req, + &**pool, + &redis, + &session_queue, + Some(&[Scopes::PAYOUTS_READ]), + ) + .await? + .1; + let actual_user = User::get(&info.into_inner().0, &**pool, &redis).await?; + + if let Some(actual_user) = actual_user { + if !user.role.is_admin() && user.id != actual_user.id.into() { + return Err(ApiError::CustomAuthentication( + "You do not have permission to request payouts of this user!".to_string(), + )); + } + + if let Some(UserPayoutData { + trolley_id: Some(trolley_id), + .. + }) = user.payout_data + { + let payouts = payouts_queue + .lock() + .await + .get_estimated_fees(&trolley_id, amount.amount) + .await?; + + Ok(HttpResponse::Ok().json(payouts)) + } else { + Err(ApiError::InvalidInput( + "You must set up your trolley account first!".to_string(), + )) + } + } else { + Ok(HttpResponse::NotFound().body("")) + } +} + #[derive(Deserialize)] pub struct PayoutData { amount: Decimal, @@ -811,67 +773,60 @@ pub async fn user_payouts_request( )); } - if let Some(payouts_data) = user.payout_data { - if let Some(payout_address) = payouts_data.payout_address { - if let Some(payout_wallet_type) = payouts_data.payout_wallet_type { - if let Some(payout_wallet) = payouts_data.payout_wallet { - return if data.amount < payouts_data.balance { - let mut transaction = pool.begin().await?; - - let leftover = payouts_queue - .send_payout(PayoutItem { - amount: PayoutAmount { - currency: "USD".to_string(), - value: data.amount, - }, - receiver: payout_address, - note: "Payment from Modrinth creator monetization program" - .to_string(), - recipient_type: payout_wallet_type.to_string().to_uppercase(), - recipient_wallet: payout_wallet.as_str_api().to_string(), - sender_item_id: format!( - "{}-{}", - UserId::from(id), - Utc::now().timestamp() - ), - }) - .await?; + if let Some(UserPayoutData { + trolley_id: Some(trolley_id), + trolley_status: Some(trolley_status), + balance, + .. + }) = user.payout_data + { + if trolley_status == RecipientStatus::Active { + return if data.amount < balance { + let mut transaction = pool.begin().await?; - sqlx::query!( + let (batch_id, payment_id) = + payouts_queue.send_payout(&trolley_id, data.amount).await?; + + sqlx::query!( " - INSERT INTO historical_payouts (user_id, amount, status) - VALUES ($1, $2, $3) + INSERT INTO historical_payouts (user_id, amount, status, batch_id, payment_id) + VALUES ($1, $2, $3, $4, $5) ", id as crate::database::models::ids::UserId, data.amount, - "success" + "processing", + batch_id, + payment_id, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; - sqlx::query!( - " + sqlx::query!( + " UPDATE users SET balance = balance - $1 WHERE id = $2 ", - data.amount - leftover, - id as crate::database::models::ids::UserId - ) - .execute(&mut *transaction) - .await?; - User::clear_caches(&[(id, None)], &redis).await?; - - transaction.commit().await?; - - Ok(HttpResponse::NoContent().body("")) - } else { - Err(ApiError::InvalidInput( - "You do not have enough funds to make this payout!".to_string(), - )) - }; - } - } + data.amount, + id as crate::database::models::ids::UserId + ) + .execute(&mut *transaction) + .await?; + + User::clear_caches(&[(id, None)], &redis).await?; + + transaction.commit().await?; + + Ok(HttpResponse::NoContent().body("")) + } else { + Err(ApiError::InvalidInput( + "You do not have enough funds to make this payout!".to_string(), + )) + }; + } else { + return Err(ApiError::InvalidInput( + "Please complete payout information via the trolley dashboard!".to_string(), + )); } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 30df2624..055601c3 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -5,6 +5,12 @@ pub struct Scheduler { arbiter: Arbiter, } +impl Default for Scheduler { + fn default() -> Self { + Self::new() + } +} + impl Scheduler { pub fn new() -> Self { Scheduler { diff --git a/src/util/bitflag.rs b/src/util/bitflag.rs new file mode 100644 index 00000000..08647ab6 --- /dev/null +++ b/src/util/bitflag.rs @@ -0,0 +1,18 @@ +#[macro_export] +macro_rules! bitflags_serde_impl { + ($type:ident, $int_type:ident) => { + impl serde::Serialize for $type { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_i64(self.bits() as i64) + } + } + + impl<'de> serde::Deserialize<'de> for $type { + fn deserialize>(deserializer: D) -> Result { + let v: i64 = Deserialize::deserialize(deserializer)?; + + Ok($type::from_bits_truncate(v as $int_type)) + } + } + }; +} diff --git a/src/util/date.rs b/src/util/date.rs new file mode 100644 index 00000000..3551307b --- /dev/null +++ b/src/util/date.rs @@ -0,0 +1,9 @@ +use chrono::Utc; + +// this converts timestamps to the timestamp format clickhouse requires/uses +pub fn get_current_tenths_of_ms() -> i64 { + Utc::now() + .timestamp_nanos_opt() + .expect("value can not be represented in a timestamp with nanosecond precision.") + / 100_000 +} diff --git a/src/util/mod.rs b/src/util/mod.rs index fa514c59..74588dd7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,7 @@ +pub mod bitflag; pub mod captcha; pub mod cors; +pub mod date; pub mod env; pub mod ext; pub mod guards; diff --git a/src/validate/fabric.rs b/src/validate/fabric.rs index 0a9b3a02..2ee44753 100644 --- a/src/validate/fabric.rs +++ b/src/validate/fabric.rs @@ -20,7 +20,7 @@ impl super::Validator for FabricValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // Time since release of 18w49a, the first fabric version - SupportedGameVersions::PastDate(DateTime::from_utc( + SupportedGameVersions::PastDate(DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1543969469, 0).unwrap(), Utc, )) diff --git a/src/validate/forge.rs b/src/validate/forge.rs index 6a4907da..04355fcd 100644 --- a/src/validate/forge.rs +++ b/src/validate/forge.rs @@ -20,7 +20,7 @@ impl super::Validator for ForgeValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // Time since release of 1.13, the first forge version which uses the new TOML system - SupportedGameVersions::PastDate(DateTime::::from_utc( + SupportedGameVersions::PastDate(DateTime::::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1540122067, 0).unwrap(), Utc, )) @@ -58,11 +58,11 @@ impl super::Validator for LegacyForgeValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // Times between versions 1.5.2 to 1.12.2, which all use the legacy way of defining mods SupportedGameVersions::Range( - DateTime::from_utc( + DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1366818300, 0).unwrap(), Utc, ), - DateTime::from_utc( + DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1505810340, 0).unwrap(), Utc, ), diff --git a/src/validate/quilt.rs b/src/validate/quilt.rs index ddb885f5..66741b8b 100644 --- a/src/validate/quilt.rs +++ b/src/validate/quilt.rs @@ -19,7 +19,7 @@ impl super::Validator for QuiltValidator { } fn get_supported_game_versions(&self) -> SupportedGameVersions { - SupportedGameVersions::PastDate(DateTime::from_utc( + SupportedGameVersions::PastDate(DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1646070100, 0).unwrap(), Utc, )) diff --git a/src/validate/resourcepack.rs b/src/validate/resourcepack.rs index 8e9b1beb..97f041de 100644 --- a/src/validate/resourcepack.rs +++ b/src/validate/resourcepack.rs @@ -20,7 +20,7 @@ impl super::Validator for PackValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // Time since release of 13w24a which replaced texture packs with resource packs - SupportedGameVersions::PastDate(DateTime::from_utc( + SupportedGameVersions::PastDate(DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1371137542, 0).unwrap(), Utc, )) @@ -58,11 +58,11 @@ impl super::Validator for TexturePackValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // a1.2.2a to 13w23b SupportedGameVersions::Range( - DateTime::from_utc( + DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1289339999, 0).unwrap(), Utc, ), - DateTime::from_utc( + DateTime::from_naive_utc_and_offset( NaiveDateTime::from_timestamp_opt(1370651522, 0).unwrap(), Utc, ), diff --git a/tests/scopes.rs b/tests/scopes.rs index 806905ab..d99614a1 100644 --- a/tests/scopes.rs +++ b/tests/scopes.rs @@ -70,24 +70,6 @@ async fn user_scopes() { .await .unwrap(); - // User payout info writing - let failure_write_user_payout = Scopes::all() ^ Scopes::PAYOUTS_WRITE; // Failure case should include USER_WRITE - let write_user_payout = Scopes::USER_WRITE | Scopes::PAYOUTS_WRITE; - let req_gen = || { - TestRequest::patch().uri("/v2/user/user").set_json(json!( { - "payout_data": { - "payout_wallet": "paypal", - "payout_wallet_type": "email", - "payout_address": "test@modrinth.com" - } - })) - }; - ScopeTest::new(&test_env) - .with_failure_scopes(failure_write_user_payout) - .test(req_gen, write_user_payout) - .await - .unwrap(); - // User deletion // (The failure is first, and this is the last test for this test function, we can delete it and use the same PAT for both tests) let delete_user = Scopes::USER_DELETE;