diff --git a/.sqlx/query-155361716f9d697c0d961b7bbad30e70698a8e5c9ceaa03b2091e058b58fb938.json b/.sqlx/query-155361716f9d697c0d961b7bbad30e70698a8e5c9ceaa03b2091e058b58fb938.json new file mode 100644 index 00000000..46917471 --- /dev/null +++ b/.sqlx/query-155361716f9d697c0d961b7bbad30e70698a8e5c9ceaa03b2091e058b58fb938.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT v.id id, v.mod_id mod_id FROM files f\n INNER JOIN versions v ON v.id = f.version_id\n WHERE f.url = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "mod_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "155361716f9d697c0d961b7bbad30e70698a8e5c9ceaa03b2091e058b58fb938" +} diff --git a/.sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json b/.sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json new file mode 100644 index 00000000..bdf80942 --- /dev/null +++ b/.sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json @@ -0,0 +1,124 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering,\n JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files,\n JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,\n JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', dependency_file_name)) filter (where d.dependency_type is not null) dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC;\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "mod_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "author_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "version_name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "version_number", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "changelog", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "date_published", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 8, + "name": "version_type", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "featured", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Varchar" + }, + { + "ordinal": 11, + "name": "requested_status", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "ordering", + "type_info": "Int4" + }, + { + "ordinal": 13, + "name": "game_versions", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "loaders", + "type_info": "VarcharArray" + }, + { + "ordinal": 15, + "name": "files", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "hashes", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "dependencies", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + null, + null, + null, + null, + null + ] + }, + "hash": "1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6" +} diff --git a/.sqlx/query-49a5d21a1454afc6383b78e468fd0decc75b9163e7286f34ceab22d563a0d3f7.json b/.sqlx/query-49a5d21a1454afc6383b78e468fd0decc75b9163e7286f34ceab22d563a0d3f7.json deleted file mode 100644 index 41439dec..00000000 --- a/.sqlx/query-49a5d21a1454afc6383b78e468fd0decc75b9163e7286f34ceab22d563a0d3f7.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mods\n SET downloads = downloads + 1\n WHERE (id = $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "49a5d21a1454afc6383b78e468fd0decc75b9163e7286f34ceab22d563a0d3f7" -} diff --git a/.sqlx/query-4d54032b02c860f4facec39eacb4548a0701d4505e7a80b4834650696df69c2b.json b/.sqlx/query-4d54032b02c860f4facec39eacb4548a0701d4505e7a80b4834650696df69c2b.json deleted file mode 100644 index 62a4aac1..00000000 --- a/.sqlx/query-4d54032b02c860f4facec39eacb4548a0701d4505e7a80b4834650696df69c2b.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE versions\n SET downloads = downloads + 1\n WHERE (id = $1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "4d54032b02c860f4facec39eacb4548a0701d4505e7a80b4834650696df69c2b" -} diff --git a/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json b/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json new file mode 100644 index 00000000..d29360d1 --- /dev/null +++ b/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE versions\n SET ordering = $1\n WHERE (id = $2)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6" +} diff --git a/.sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json b/.sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json similarity index 62% rename from .sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json rename to .sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json index 7768dbbc..10cba080 100644 --- a/.sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json +++ b/.sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11\n )\n ", + "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status, ordering\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11, $12\n )\n ", "describe": { "columns": [], "parameters": { @@ -15,10 +15,11 @@ "Int4", "Varchar", "Bool", - "Varchar" + "Varchar", + "Int4" ] }, "nullable": [] }, - "hash": "df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed" + "hash": "a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce" } diff --git a/.sqlx/query-b993ec7579f06603a2a308dccd1ea1fbffd94286db48bc0e36a30f4f6a9d39af.json b/.sqlx/query-b993ec7579f06603a2a308dccd1ea1fbffd94286db48bc0e36a30f4f6a9d39af.json new file mode 100644 index 00000000..0db3e537 --- /dev/null +++ b/.sqlx/query-b993ec7579f06603a2a308dccd1ea1fbffd94286db48bc0e36a30f4f6a9d39af.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE versions\n SET downloads = downloads + 1\n WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [] + }, + "hash": "b993ec7579f06603a2a308dccd1ea1fbffd94286db48bc0e36a30f4f6a9d39af" +} diff --git a/.sqlx/query-d08c9ef6a8829ce1d23d66f27c58f4b9b64f4ce985e60ded871d1f31eb0c818b.json b/.sqlx/query-d08c9ef6a8829ce1d23d66f27c58f4b9b64f4ce985e60ded871d1f31eb0c818b.json new file mode 100644 index 00000000..7eab9304 --- /dev/null +++ b/.sqlx/query-d08c9ef6a8829ce1d23d66f27c58f4b9b64f4ce985e60ded871d1f31eb0c818b.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mods\n SET downloads = downloads + 1\n WHERE id = ANY($1)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [] + }, + "hash": "d08c9ef6a8829ce1d23d66f27c58f4b9b64f4ce985e60ded871d1f31eb0c818b" +} diff --git a/.sqlx/query-dd57a6dd89fefedbde796ef02b308ce7dba17ca0c65ffd5f9e35e296a72d4c1c.json b/.sqlx/query-dd57a6dd89fefedbde796ef02b308ce7dba17ca0c65ffd5f9e35e296a72d4c1c.json deleted file mode 100644 index fb30e3f5..00000000 --- a/.sqlx/query-dd57a6dd89fefedbde796ef02b308ce7dba17ca0c65ffd5f9e35e296a72d4c1c.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT v.id id, v.mod_id mod_id, file_type FROM files f\n INNER JOIN versions v ON v.id = f.version_id\n WHERE f.url = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "mod_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "file_type", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true - ] - }, - "hash": "dd57a6dd89fefedbde796ef02b308ce7dba17ca0c65ffd5f9e35e296a72d4c1c" -} diff --git a/.sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json b/.sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json similarity index 80% rename from .sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json rename to .sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json index 4794dd9a..151a7fa3 100644 --- a/.sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json +++ b/.sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY date_published ASC\n ", + "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY ordering ASC NULLS LAST, date_published ASC\n ", "describe": { "columns": [ { @@ -31,5 +31,5 @@ false ] }, - "hash": "f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a" + "hash": "defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad" } diff --git a/Cargo.lock b/Cargo.lock index fc2b10ae..06b62adc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "actix-macros", "actix-rt", "actix_derive", - "bitflags 2.4.0", + "bitflags 2.4.1", "bytes", "crossbeam-channel", "futures-core", @@ -92,9 +92,9 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "ahash 0.8.3", - "base64 0.21.4", - "bitflags 2.4.0", + "ahash 0.8.6", + "base64 0.21.5", + "bitflags 2.4.1", "brotli", "bytes", "bytestring", @@ -128,7 +128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -166,7 +166,7 @@ dependencies = [ "parse-size", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -205,7 +205,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", "tracing", ] @@ -246,7 +246,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash 0.8.3", + "ahash 0.8.6", "bytes", "bytestring", "cfg-if", @@ -266,7 +266,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.4", + "socket2 0.5.5", "time", "url", ] @@ -280,7 +280,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -317,7 +317,7 @@ checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -348,9 +348,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -359,14 +359,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -457,13 +458,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -514,9 +515,9 @@ dependencies = [ [[package]] name = "aws-region" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056557a61427d0e5ba29dd931031c8ffed4ee7a550e7cd55692a9d8deb0a9dba" +checksum = "42fed2b9fca70f2908268d057a607f2a906f47edbf856ea8587de9038d264e22" dependencies = [ "thiserror", ] @@ -550,9 +551,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -598,9 +599,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -702,9 +703,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -767,9 +768,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytestring" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" dependencies = [ "bytes", ] @@ -996,9 +997,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -1014,9 +1015,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" @@ -1117,15 +1118,15 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.9", + "socket2 0.4.10", "winapi", ] [[package]] name = "curl-sys" -version = "0.4.67+curl-8.3.0" +version = "0.4.68+curl-8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc35d066510b197a0f72de863736641539957628c8a42e70e27c66849e77c34" +checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" dependencies = [ "cc", "libc", @@ -1182,7 +1183,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1204,7 +1205,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1214,7 +1215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "lock_api", "once_cell", "parking_lot_core", @@ -1222,22 +1223,21 @@ dependencies = [ [[package]] name = "deadpool" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" dependencies = [ "async-trait", "deadpool-runtime", "num_cpus", - "retain_mut", "tokio", ] [[package]] name = "deadpool-redis" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1760f60ffc6653b4afd924c5792098d8c00d9a3deb6b3d989eac17949dc422" +checksum = "84930e585871d35b8e06d3e03d03e3a8a4c5dc71afa4376c7cd5f9223e1da1ea" dependencies = [ "deadpool", "redis", @@ -1275,10 +1275,11 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -1405,7 +1406,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "memchr", ] @@ -1513,9 +1514,9 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdeflate" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" dependencies = [ "simd-adler32", ] @@ -1552,9 +1553,9 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -1609,9 +1610,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -1624,9 +1625,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -1634,15 +1635,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -1662,9 +1663,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -1683,26 +1684,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-timer" @@ -1712,9 +1713,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1799,7 +1800,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] @@ -1808,16 +1809,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", ] [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "allocator-api2", ] @@ -1827,7 +1828,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.1", + "hashbrown 0.14.2", ] [[package]] @@ -1971,7 +1972,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1993,16 +1994,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -2078,12 +2079,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.2", "serde", ] @@ -2107,9 +2108,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "ipnetwork" @@ -2211,9 +2212,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -2224,8 +2225,8 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", - "ring", + "base64 0.21.5", + "ring 0.16.20", "serde", "serde_json", ] @@ -2245,8 +2246,8 @@ dependencies = [ "actix-ws", "argon2", "async-trait", - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "bytes", "censor", "chrono", @@ -2331,7 +2332,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "email-encoding", "email_address", "fastrand 1.9.0", @@ -2344,7 +2345,7 @@ dependencies = [ "nom 7.1.3", "once_cell", "quoted_printable", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", ] @@ -2363,9 +2364,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" @@ -2383,6 +2384,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -2420,9 +2432,9 @@ checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "local-channel" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ "futures-core", "futures-sink", @@ -2431,15 +2443,15 @@ dependencies = [ [[package]] name = "local-waker" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -2622,9 +2634,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", @@ -2780,11 +2792,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -2801,7 +2813,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2812,9 +2824,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -2845,9 +2857,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -2861,13 +2873,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "smallvec", "windows-targets", ] @@ -2971,7 +2983,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3042,6 +3054,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3246,7 +3264,7 @@ version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", "async-trait", "bytes", "combine", @@ -3257,7 +3275,7 @@ dependencies = [ "r2d2", "ryu", "sha1_smol", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tokio-util", "url", @@ -3265,54 +3283,54 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.0", + "regex-syntax 0.8.2", ] [[package]] name = "regex-automata" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.0", + "regex-syntax 0.8.2", ] [[package]] @@ -3335,9 +3353,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rend" @@ -3354,7 +3372,7 @@ version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -3389,17 +3407,11 @@ dependencies = [ "winreg", ] -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "rgb" -version = "0.8.36" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" dependencies = [ "bytemuck", ] @@ -3414,11 +3426,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "rkyv" version = "0.7.42" @@ -3449,16 +3475,14 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" dependencies = [ - "byteorder", "const-oid", "digest 0.10.7", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", "pkcs8", @@ -3545,11 +3569,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.18" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -3558,11 +3582,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ - "ring", + "ring 0.17.5", "rustls-webpki", "sct", ] @@ -3573,17 +3597,17 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -3641,12 +3665,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -3817,22 +3841,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "a834c4821019838224821468552240d4d95d14e751986442c816572d39a080c9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "46fa52d5646bce91b680189fe5b1c049d2ea38dabb4e2e7c8d00ca12cfbfbcfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3848,9 +3872,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -3880,15 +3904,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.2", + "indexmap 2.1.0", "serde", "serde_json", "serde_with_macros", @@ -3897,14 +3921,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4027,9 +4051,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -4037,9 +4061,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -4109,7 +4133,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.6", "atoi", "byteorder", "bytes", @@ -4126,7 +4150,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.2", + "indexmap 2.1.0", "log", "memchr", "once_cell", @@ -4194,8 +4218,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" dependencies = [ "atoi", - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "bytes", "chrono", @@ -4238,8 +4262,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" dependencies = [ "atoi", - "base64 0.21.4", - "bitflags 2.4.0", + "base64 0.21.5", + "bitflags 2.4.1", "byteorder", "chrono", "crc", @@ -4360,9 +4384,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -4409,13 +4433,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix", "windows-sys", ] @@ -4431,22 +4455,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4462,12 +4486,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -4517,7 +4542,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -4530,7 +4555,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4556,9 +4581,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -4599,11 +4624,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4612,20 +4636,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -4719,13 +4743,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.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "log", "native-tls", "once_cell", @@ -4752,9 +4782,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", "rand", @@ -4845,9 +4875,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4855,24 +4885,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -4882,9 +4912,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4892,22 +4922,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-streams" @@ -4924,9 +4954,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", @@ -4985,10 +5015,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] @@ -5137,6 +5167,26 @@ dependencies = [ "url", ] +[[package]] +name = "zerocopy" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -5203,12 +5253,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 89ca5c31..bb38733c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,8 +86,8 @@ 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" +redis = { version = "0.23.3", features = ["tokio-comp", "ahash", "r2d2"]} +deadpool-redis = "0.13.0" clickhouse = { version = "0.11.2", features = ["uuid", "time"] } uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] } diff --git a/migrations/20231027195838_version_ordering.sql b/migrations/20231027195838_version_ordering.sql new file mode 100644 index 00000000..873f2c1b --- /dev/null +++ b/migrations/20231027195838_version_ordering.sql @@ -0,0 +1 @@ +ALTER TABLE versions ADD COLUMN ordering int NULL; \ No newline at end of file diff --git a/src/database/models/loader_fields.rs b/src/database/models/loader_fields.rs index 308ae691..4de959a0 100644 --- a/src/database/models/loader_fields.rs +++ b/src/database/models/loader_fields.rs @@ -197,7 +197,7 @@ pub struct LoaderFieldEnum { pub hidable: bool, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct LoaderFieldEnumValue { pub id: LoaderFieldEnumValueId, pub enum_id: LoaderFieldEnumId, @@ -208,14 +208,14 @@ pub struct LoaderFieldEnumValue { pub metadata: serde_json::Value, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct VersionField { pub version_id: VersionId, pub field_id: LoaderFieldId, pub field_name: String, pub value: VersionFieldValue, } -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub enum VersionFieldValue { Integer(i32), Text(String), @@ -228,7 +228,7 @@ pub enum VersionFieldValue { } #[derive(Clone, Serialize, Deserialize, Debug)] -pub struct QueryVersionField { +pub struct QueryVersionField { pub version_id: VersionId, pub field_id: LoaderFieldId, pub int_value: Option, diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index e98abb7a..9cff920b 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -29,6 +29,7 @@ pub struct VersionBuilder { pub featured: bool, pub status: VersionStatus, pub requested_status: Option, + pub ordering: Option, } #[derive(Clone)] @@ -215,6 +216,7 @@ impl VersionBuilder { version_type: self.version_type, status: self.status, requested_status: self.requested_status, + ordering: self.ordering, }; version.insert(transaction).await?; @@ -283,7 +285,7 @@ impl LoaderVersion { } } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct Version { pub id: VersionId, pub project_id: ProjectId, @@ -298,6 +300,7 @@ pub struct Version { pub featured: bool, pub status: VersionStatus, pub requested_status: Option, + pub ordering: Option, } impl Version { @@ -310,12 +313,12 @@ impl Version { INSERT INTO versions ( id, mod_id, author_id, name, version_number, changelog, date_published, downloads, - version_type, featured, status + version_type, featured, status, ordering ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, - $9, $10, $11 + $9, $10, $11, $12 ) ", self.id as VersionId, @@ -328,7 +331,8 @@ impl Version { self.downloads, &self.version_type, self.featured, - self.status.as_str() + self.status.as_str(), + self.ordering ) .execute(&mut **transaction) .await?; @@ -517,7 +521,7 @@ impl Version { " SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number, v.changelog changelog, v.date_published date_published, v.downloads downloads, - v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, + v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders, ARRAY_AGG(DISTINCT pt.name) filter (where pt.name is not null) project_types, ARRAY_AGG(DISTINCT g.name) filter (where g.name is not null) games, @@ -573,7 +577,7 @@ impl Version { WHERE v.id = ANY($1) GROUP BY v.id - ORDER BY v.date_published ASC; + ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC; ", &version_ids_parsed ) @@ -596,6 +600,7 @@ impl Version { status: VersionStatus::from_string(&v.status), requested_status: v.requested_status .map(|x| VersionStatus::from_string(&x)), + ordering: v.ordering, }, files: { #[derive(Deserialize)] @@ -839,7 +844,7 @@ impl Version { } } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryVersion { pub inner: Version, @@ -851,7 +856,7 @@ pub struct QueryVersion { pub dependencies: Vec, } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryDependency { pub project_id: Option, pub version_id: Option, @@ -859,7 +864,7 @@ pub struct QueryDependency { pub dependency_type: String, } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryFile { pub id: FileId, pub url: String, @@ -882,3 +887,84 @@ pub struct SingleFile { pub size: u32, pub file_type: Option, } + +impl std::cmp::Ord for QueryVersion { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.inner.cmp(&other.inner) + } +} + +impl std::cmp::PartialOrd for QueryVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + let ordering_order = match (self.ordering, other.ordering) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (Some(a), Some(b)) => a.cmp(&b), + }; + + match ordering_order { + Ordering::Equal => self.date_published.cmp(&other.date_published), + ordering => ordering, + } + } +} + +impl std::cmp::PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod tests { + use chrono::Months; + + use super::*; + + #[test] + fn test_version_sorting() { + let versions = vec![ + get_version(4, None, months_ago(6)), + get_version(3, None, months_ago(7)), + get_version(2, Some(1), months_ago(6)), + get_version(1, Some(0), months_ago(4)), + get_version(0, Some(0), months_ago(5)), + ]; + + let sorted = versions.iter().cloned().sorted().collect_vec(); + + let expected_sorted_ids = vec![0, 1, 2, 3, 4]; + let actual_sorted_ids = sorted.iter().map(|v| v.id.0).collect_vec(); + assert_eq!(expected_sorted_ids, actual_sorted_ids); + } + + fn months_ago(months: u32) -> DateTime { + Utc::now().checked_sub_months(Months::new(months)).unwrap() + } + + fn get_version(id: i64, ordering: Option, date_published: DateTime) -> Version { + Version { + id: VersionId(id), + ordering, + date_published, + project_id: ProjectId(0), + author_id: UserId(0), + name: Default::default(), + version_number: Default::default(), + changelog: Default::default(), + changelog_url: Default::default(), + downloads: Default::default(), + version_type: Default::default(), + featured: Default::default(), + status: VersionStatus::Listed, + requested_status: Default::default(), + } + } +} diff --git a/src/database/redis.rs b/src/database/redis.rs index b11e7d84..2a517264 100644 --- a/src/database/redis.rs +++ b/src/database/redis.rs @@ -7,7 +7,7 @@ const DEFAULT_EXPIRY: i64 = 1800; // 30 minutes #[derive(Clone)] pub struct RedisPool { - pool: deadpool_redis::Pool, + pub pool: deadpool_redis::Pool, meta_namespace: String, } diff --git a/src/lib.rs b/src/lib.rs index d0f595a6..61ff3807 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ use actix_web::web; use database::redis::RedisPool; use log::{info, warn}; use queue::{ - analytics::AnalyticsQueue, download::DownloadQueue, payouts::PayoutsQueue, session::AuthQueue, + analytics::AnalyticsQueue, payouts::PayoutsQueue, session::AuthQueue, socket::ActiveSockets, }; use scheduler::Scheduler; @@ -49,7 +49,6 @@ pub struct LabrinthConfig { pub scheduler: Arc, pub ip_salt: Pepper, pub search_config: search::SearchConfig, - pub download_queue: web::Data, pub session_queue: web::Data, pub payouts_queue: web::Data>, pub analytics_queue: Arc, @@ -139,24 +138,6 @@ pub fn app_setup( scheduler::schedule_versions(&mut scheduler, pool.clone(), redis_pool.clone()); - let download_queue = web::Data::new(DownloadQueue::new()); - - let pool_ref = pool.clone(); - let download_queue_ref = download_queue.clone(); - scheduler.run(std::time::Duration::from_secs(60 * 5), move || { - let pool_ref = pool_ref.clone(); - let download_queue_ref = download_queue_ref.clone(); - - async move { - info!("Indexing download queue"); - let result = download_queue_ref.index(&pool_ref).await; - if let Err(e) = result { - warn!("Indexing download queue failed: {:?}", e); - } - info!("Done indexing download queue"); - } - }); - let session_queue = web::Data::new(AuthQueue::new()); let pool_ref = pool.clone(); @@ -202,13 +183,19 @@ pub fn app_setup( { let client_ref = clickhouse.clone(); let analytics_queue_ref = analytics_queue.clone(); - scheduler.run(std::time::Duration::from_secs(60 * 5), move || { + let pool_ref = pool.clone(); + let redis_ref = redis_pool.clone(); + scheduler.run(std::time::Duration::from_secs(15), move || { let client_ref = client_ref.clone(); let analytics_queue_ref = analytics_queue_ref.clone(); + let pool_ref = pool_ref.clone(); + let redis_ref = redis_ref.clone(); async move { info!("Indexing analytics queue"); - let result = analytics_queue_ref.index(client_ref).await; + let result = analytics_queue_ref + .index(client_ref, &redis_ref, &pool_ref) + .await; if let Err(e) = result { warn!("Indexing analytics queue failed: {:?}", e); } @@ -252,7 +239,6 @@ pub fn app_setup( maxmind, scheduler: Arc::new(scheduler), ip_salt, - download_queue, search_config, session_queue, payouts_queue, @@ -282,7 +268,6 @@ pub fn app_config(cfg: &mut web::ServiceConfig, labrinth_config: LabrinthConfig) .app_data(web::Data::new(labrinth_config.pool.clone())) .app_data(web::Data::new(labrinth_config.file_host.clone())) .app_data(web::Data::new(labrinth_config.search_config.clone())) - .app_data(labrinth_config.download_queue.clone()) .app_data(labrinth_config.session_queue.clone()) .app_data(labrinth_config.payouts_queue.clone()) .app_data(web::Data::new(labrinth_config.ip_salt.clone())) diff --git a/src/models/v2/projects.rs b/src/models/v2/projects.rs index b5d9f967..7d2a0b85 100644 --- a/src/models/v2/projects.rs +++ b/src/models/v2/projects.rs @@ -236,6 +236,9 @@ pub struct LegacyVersion { /// A list of loaders this project supports pub loaders: Vec, + // TODO: remove this once we have v3 testing, as this is a v3 field and tests for it should be isolated to v3 + pub ordering: Option, + pub id: VersionId, pub project_id: ProjectId, pub author_id: UserId, @@ -297,6 +300,7 @@ impl From for LegacyVersion { files: data.files, dependencies: data.dependencies, game_versions, + ordering: data.ordering, loaders, } } diff --git a/src/models/v3/projects.rs b/src/models/v3/projects.rs index d1b7dd50..63c6593b 100644 --- a/src/models/v3/projects.rs +++ b/src/models/v3/projects.rs @@ -488,6 +488,8 @@ pub struct Version { /// The loaders that this version works on pub loaders: Vec, + /// Ordering override, lower is returned first + pub ordering: Option, // All other fields are loader-specific VersionFields // These are flattened during serialization @@ -527,6 +529,7 @@ impl From for Version { "alpha" => VersionType::Alpha, _ => VersionType::Release, }, + ordering: v.ordering, status: v.status, requested_status: v.requested_status, @@ -747,7 +750,7 @@ impl DependencyType { } } -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum FileType { RequiredResourcePack, diff --git a/src/queue/analytics.rs b/src/queue/analytics.rs index 8a02d99c..c3c8cfa3 100644 --- a/src/queue/analytics.rs +++ b/src/queue/analytics.rs @@ -1,16 +1,16 @@ +use crate::database::models::DatabaseError; use crate::models::analytics::{Download, PageView, Playtime}; -use dashmap::DashSet; +use crate::routes::ApiError; +use dashmap::{DashMap, DashSet}; +use redis::cmd; +use sqlx::PgPool; +use crate::database::redis::RedisPool; -#[cfg(test)] -mod tests; - -const VIEWS_TABLENAME: &str = "views"; -const DOWNLOADS_TABLENAME: &str = "downloads"; -const PLAYTIME_TABLENAME: &str = "playtime"; +const DOWNLOADS_NAMESPACE: &str = "downloads"; pub struct AnalyticsQueue { views_queue: DashSet, - downloads_queue: DashSet, + downloads_queue: DashMap, playtime_queue: DashSet, } @@ -25,7 +25,7 @@ impl AnalyticsQueue { pub fn new() -> Self { AnalyticsQueue { views_queue: DashSet::with_capacity(1000), - downloads_queue: DashSet::with_capacity(1000), + downloads_queue: DashMap::with_capacity(1000), playtime_queue: DashSet::with_capacity(1000), } } @@ -35,45 +35,138 @@ impl AnalyticsQueue { } pub fn add_download(&self, download: Download) { - self.downloads_queue.insert(download); + let octets = download.ip.octets(); + let ip_stripped = u64::from_be_bytes([ + octets[0], octets[1], octets[2], octets[3], octets[4], octets[5], octets[6], octets[7], + ]); + self.downloads_queue + .insert(format!("{}-{}", ip_stripped, download.project_id), download); } pub fn add_playtime(&self, playtime: Playtime) { self.playtime_queue.insert(playtime); } - pub async fn index(&self, client: clickhouse::Client) -> Result<(), clickhouse::error::Error> { - 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?; + pub async fn index( + &self, + client: clickhouse::Client, + redis: &RedisPool, + pool: &PgPool, + ) -> Result<(), ApiError> { + let views_queue = self.views_queue.clone(); + self.views_queue.clear(); - Ok(()) - } + let downloads_queue = self.downloads_queue.clone(); + self.downloads_queue.clear(); + + let playtime_queue = self.playtime_queue.clone(); + self.playtime_queue.clear(); - 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(()); + if !views_queue.is_empty() { + let mut views = client.insert("views")?; + + for view in views_queue { + views.write(&view).await?; + } + + views.end().await?; } - let current_queue = queue.clone(); - queue.clear(); + if !playtime_queue.is_empty() { + let mut playtimes = client.insert("playtime")?; - let mut inserter = client.inserter(table_name)?; + for playtime in playtime_queue { + playtimes.write(&playtime).await?; + } - for row in current_queue { - inserter.write(&row).await?; - inserter.commit().await?; + playtimes.end().await?; } - inserter.end().await?; + if !downloads_queue.is_empty() { + let mut downloads_keys = Vec::new(); + let raw_downloads = DashMap::new(); + + for (index, (key, download)) in downloads_queue.into_iter().enumerate() { + downloads_keys.push(key); + raw_downloads.insert(index, download); + } + + let mut redis = redis.pool.get().await.map_err(DatabaseError::RedisPool)?; + + let results = cmd("MGET") + .arg( + downloads_keys + .iter() + .map(|x| format!("{}:{}", DOWNLOADS_NAMESPACE, x)) + .collect::>(), + ) + .query_async::<_, Vec>>(&mut redis) + .await + .map_err(DatabaseError::CacheError)?; + + let mut pipe = redis::pipe(); + for (idx, count) in results.into_iter().enumerate() { + let key = &downloads_keys[idx]; + + let new_count = if let Some(count) = count { + if count > 5 { + raw_downloads.remove(&idx); + continue; + } + + count + 1 + } else { + 1 + }; + + pipe.atomic().set_ex( + format!("{}:{}", DOWNLOADS_NAMESPACE, key), + new_count, + 6 * 60 * 60, + ); + } + pipe.query_async(&mut *redis) + .await + .map_err(DatabaseError::CacheError)?; + + let version_ids = raw_downloads + .iter() + .map(|x| x.version_id as i64) + .collect::>(); + let project_ids = raw_downloads + .iter() + .map(|x| x.project_id as i64) + .collect::>(); + + let mut transaction = pool.begin().await?; + let mut downloads = client.insert("downloads")?; + + for (_, download) in raw_downloads { + downloads.write(&download).await?; + } + + sqlx::query!( + "UPDATE versions + SET downloads = downloads + 1 + WHERE id = ANY($1)", + &version_ids + ) + .execute(&mut *transaction) + .await?; + + sqlx::query!( + "UPDATE mods + SET downloads = downloads + 1 + WHERE id = ANY($1)", + &project_ids + ) + .execute(&mut *transaction) + .await?; + + transaction.commit().await?; + downloads.end().await?; + } Ok(()) } -} +} \ No newline at end of file diff --git a/src/queue/analytics/tests.rs b/src/queue/analytics/tests.rs deleted file mode 100644 index 52b3d8e1..00000000 --- a/src/queue/analytics/tests.rs +++ /dev/null @@ -1,128 +0,0 @@ -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/download.rs b/src/queue/download.rs deleted file mode 100644 index 6bd571f1..00000000 --- a/src/queue/download.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::database::models::{DatabaseError, ProjectId, VersionId}; -use sqlx::PgPool; -use tokio::sync::Mutex; - -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 { - DownloadQueue { - queue: Mutex::new(Vec::with_capacity(1000)), - } - } - pub async fn add(&self, project_id: ProjectId, version_id: VersionId) { - self.queue.lock().await.push((project_id, version_id)); - } - - pub async fn take(&self) -> Vec<(ProjectId, VersionId)> { - let mut queue = self.queue.lock().await; - let len = queue.len(); - - std::mem::replace(&mut queue, Vec::with_capacity(len)) - } - - pub async fn index(&self, pool: &PgPool) -> Result<(), DatabaseError> { - let queue = self.take().await; - - if !queue.is_empty() { - let mut transaction = pool.begin().await?; - - for (project_id, version_id) in queue { - sqlx::query!( - "UPDATE versions - SET downloads = downloads + 1 - WHERE (id = $1)", - version_id as VersionId - ) - .execute(&mut *transaction) - .await?; - - sqlx::query!( - "UPDATE mods - SET downloads = downloads + 1 - WHERE (id = $1)", - project_id as ProjectId - ) - .execute(&mut *transaction) - .await?; - } - - transaction.commit().await?; - } - - Ok(()) - } -} diff --git a/src/queue/mod.rs b/src/queue/mod.rs index e8136b4c..9501640b 100644 --- a/src/queue/mod.rs +++ b/src/queue/mod.rs @@ -1,5 +1,4 @@ pub mod analytics; -pub mod download; pub mod maxmind; pub mod payouts; pub mod session; diff --git a/src/routes/maven.rs b/src/routes/maven.rs index a50ec7f2..23d08637 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -103,7 +103,7 @@ pub async fn maven_metadata( SELECT id, version_number, version_type FROM versions WHERE mod_id = $1 AND status = ANY($2) - ORDER BY date_published ASC + ORDER BY ordering ASC NULLS LAST, date_published ASC ", project.inner.id as database::models::ids::ProjectId, &*crate::models::projects::VersionStatus::iterator() diff --git a/src/routes/v2/admin.rs b/src/routes/v2/admin.rs index c90dec19..be914e91 100644 --- a/src/routes/v2/admin.rs +++ b/src/routes/v2/admin.rs @@ -6,7 +6,6 @@ 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; @@ -55,7 +54,6 @@ pub async fn count_download( analytics_queue: web::Data>, session_queue: web::Data, download_body: web::Json, - download_queue: web::Data, ) -> Result { let token = download_body .headers @@ -74,9 +72,9 @@ pub async fn count_download( .ok() .map(|x| x as i64); - let (version_id, project_id, file_type) = if let Some(version) = sqlx::query!( + let (version_id, project_id) = if let Some(version) = sqlx::query!( " - SELECT v.id id, v.mod_id mod_id, file_type FROM files f + SELECT v.id id, v.mod_id mod_id FROM files f INNER JOIN versions v ON v.id = f.version_id WHERE f.url = $1 ", @@ -85,7 +83,7 @@ pub async fn count_download( .fetch_optional(pool.as_ref()) .await? { - (version.id, version.mod_id, version.file_type) + (version.id, version.mod_id) } else if let Some(version) = sqlx::query!( " SELECT id, mod_id FROM versions @@ -98,22 +96,13 @@ pub async fn count_download( .fetch_optional(pool.as_ref()) .await? { - (version.id, version.mod_id, None) + (version.id, version.mod_id) } else { return Err(ApiError::InvalidInput( "Specified version does not exist!".to_string(), )); }; - if file_type.is_none() { - download_queue - .add( - crate::database::models::ProjectId(project_id), - crate::database::models::VersionId(version_id), - ) - .await; - } - let url = url::Url::parse(&download_body.url) .map_err(|_| ApiError::InvalidInput("invalid download URL specified!".to_string()))?; diff --git a/src/routes/v2/analytics_get.rs b/src/routes/v2/analytics_get.rs index 91de3bb6..8dd6532a 100644 --- a/src/routes/v2/analytics_get.rs +++ b/src/routes/v2/analytics_get.rs @@ -22,7 +22,8 @@ pub fn config(cfg: &mut web::ServiceConfig) { /// The json data to be passed to fetch analytic data /// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively. +/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively +/// start_date and end_date are inclusive /// resolution_minutes is optional. This refers to the window by which we are looking (every day, every minute, etc) and defaults to 1440 (1 day) #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GetData { diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index d60921b0..a2eade1d 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -188,6 +188,7 @@ pub async fn project_create( status: v.status, file_types: v.file_types, uploaded_images: v.uploaded_images, + ordering: v.ordering, fields, } }) diff --git a/src/routes/v2/version_creation.rs b/src/routes/v2/version_creation.rs index 117fe6d0..5652e829 100644 --- a/src/routes/v2/version_creation.rs +++ b/src/routes/v2/version_creation.rs @@ -63,6 +63,9 @@ pub struct InitialVersionData { #[validate(length(max = 10))] #[serde(default)] pub uploaded_images: Vec, + + // The ordering relative to other versions + pub ordering: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -115,6 +118,7 @@ pub async fn version_create( status: legacy_create.status, file_types: legacy_create.file_types, uploaded_images: legacy_create.uploaded_images, + ordering: legacy_create.ordering, fields, }) }, diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index ffce00e2..255ecfc2 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -189,6 +189,7 @@ pub struct EditVersion { pub downloads: Option, pub status: Option, pub file_types: Option>, + pub ordering: Option>, //TODO: How do you actually pass this in json? } #[derive(Serialize, Deserialize)] @@ -237,6 +238,7 @@ pub async fn version_edit( }) .collect::>() }), + ordering: new_version.ordering, fields, }; diff --git a/src/routes/v3/analytics_get.rs b/src/routes/v3/analytics_get.rs index cc0529c5..2eb468ec 100644 --- a/src/routes/v3/analytics_get.rs +++ b/src/routes/v3/analytics_get.rs @@ -333,6 +333,15 @@ pub async fn revenue_get( let end_date = data.end_date.unwrap_or(Utc::now()); let resolution_minutes = data.resolution_minutes.unwrap_or(60 * 24); + // Round up/down to nearest duration as we are using pgadmin, does not have rounding in the fetch command + // Round start_date down to nearest resolution + let diff = start_date.timestamp() % (resolution_minutes as i64 * 60); + let start_date = start_date - Duration::seconds(diff); + + // Round end_date up to nearest resolution + let diff = end_date.timestamp() % (resolution_minutes as i64 * 60); + let end_date = end_date + Duration::seconds((resolution_minutes as i64 * 60) - diff); + // Convert String list to list of ProjectIds or VersionIds // - Filter out unauthorized projects/versions // - If no project_ids or version_ids are provided, we default to all projects the user has access to diff --git a/src/routes/v3/project_creation.rs b/src/routes/v3/project_creation.rs index 66890599..637aa46a 100644 --- a/src/routes/v3/project_creation.rs +++ b/src/routes/v3/project_creation.rs @@ -935,6 +935,7 @@ async fn create_initial_version( status: VersionStatus::Listed, version_type: version_data.release_channel.to_string(), requested_status: None, + ordering: version_data.ordering, }; Ok(version) diff --git a/src/routes/v3/version_creation.rs b/src/routes/v3/version_creation.rs index 3176a103..264b36b5 100644 --- a/src/routes/v3/version_creation.rs +++ b/src/routes/v3/version_creation.rs @@ -77,7 +77,9 @@ pub struct InitialVersionData { #[validate(length(max = 10))] #[serde(default)] pub uploaded_images: Vec, - + // The ordering relative to other versions + pub ordering: Option, + // Flattened loader fields // All other fields are loader-specific VersionFields // These are flattened during serialization @@ -325,6 +327,7 @@ async fn version_create_inner( featured: version_create_data.featured, status: version_create_data.status, requested_status: None, + ordering: version_create_data.ordering, }); return Ok(()); @@ -441,6 +444,7 @@ async fn version_create_inner( version_type: version_data.release_channel, status: builder.status, requested_status: builder.requested_status, + ordering: builder.ordering, files: builder .files .iter() diff --git a/src/routes/v3/version_file.rs b/src/routes/v3/version_file.rs index 28375d70..47318d85 100644 --- a/src/routes/v3/version_file.rs +++ b/src/routes/v3/version_file.rs @@ -160,7 +160,7 @@ pub async fn get_update_from_hash( } bool }) - .sorted_by(|a, b| a.inner.date_published.cmp(&b.inner.date_published)) + .sorted() .collect::>(); if let Some(first) = versions.pop() { @@ -363,7 +363,7 @@ pub async fn update_files( bool }) - .sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)) + .sorted() .next(); if let Some(version) = version { @@ -477,7 +477,7 @@ pub async fn update_individual_files( bool }) - .sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)) + .sorted() .next(); if let Some(version) = version { diff --git a/src/routes/v3/versions.rs b/src/routes/v3/versions.rs index dfe128f4..d3756cd5 100644 --- a/src/routes/v3/versions.rs +++ b/src/routes/v3/versions.rs @@ -200,6 +200,8 @@ pub struct EditVersion { pub downloads: Option, pub status: Option, pub file_types: Option>, + + pub ordering: Option>, //TODO: How do you actually pass this in json? // Flattened loader fields // All other fields are loader-specific VersionFields @@ -625,6 +627,20 @@ pub async fn version_edit_helper( } } + if let Some(ordering) = &new_version.ordering { + sqlx::query!( + " + UPDATE versions + SET ordering = $1 + WHERE (id = $2) + ", + ordering.to_owned() as Option, + id as database::models::ids::VersionId, + ) + .execute(&mut *transaction) + .await?; + } + // delete any images no longer in the changelog let checkable_strings: Vec<&str> = vec![&new_version.changelog] .into_iter() @@ -747,10 +763,11 @@ pub async fn version_list( .cloned() .collect::>(); - versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); + versions.sort(); // Attempt to populate versions with "auto featured" versions if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) { + // TODO: Re-implement this // let (loaders, game_versions) = futures::future::try_join( // database::models::loader_fields::Loader::list(&**pool, &redis), // database::models::loader_fields::GameVersion::list_filter( @@ -788,7 +805,7 @@ pub async fn version_list( } } - response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); + response.sort(); response.dedup_by(|a, b| a.inner.id == b.inner.id); let response = filter_authorized_versions(response, &user_option, &pool).await?; diff --git a/tests/analytics.rs b/tests/analytics.rs new file mode 100644 index 00000000..e762566f --- /dev/null +++ b/tests/analytics.rs @@ -0,0 +1,137 @@ +use chrono::{DateTime, Duration, Utc}; +use common::database::*; +use itertools::Itertools; +use labrinth::models::ids::base62_impl::parse_base62; +use rust_decimal::{prelude::ToPrimitive, Decimal}; + +use crate::common::environment::TestEnvironment; + +// importing common module. +mod common; + +#[actix_rt::test] +pub async fn analytics_revenue() { + let test_env = TestEnvironment::build(None).await; + let api = &test_env.v2; + + let alpha_project_id = test_env + .dummy + .as_ref() + .unwrap() + .project_alpha + .project_id + .clone(); + + let pool = test_env.db.pool.clone(); + + // Generate sample revenue data- directly insert into sql + let (mut insert_user_ids, mut insert_project_ids, mut insert_payouts, mut insert_starts) = + (Vec::new(), Vec::new(), Vec::new(), Vec::new()); + + // Note: these go from most recent to least recent + let money_time_pairs: [(f64, DateTime); 10] = [ + (50.0, Utc::now() - Duration::minutes(5)), + (50.1, Utc::now() - Duration::minutes(10)), + (101.0, Utc::now() - Duration::days(1)), + (200.0, Utc::now() - Duration::days(2)), + (311.0, Utc::now() - Duration::days(3)), + (400.0, Utc::now() - Duration::days(4)), + (526.0, Utc::now() - Duration::days(5)), + (633.0, Utc::now() - Duration::days(6)), + (800.0, Utc::now() - Duration::days(14)), + (800.0, Utc::now() - Duration::days(800)), + ]; + + let project_id = parse_base62(&alpha_project_id).unwrap() as i64; + for (money, time) in money_time_pairs.iter() { + insert_user_ids.push(USER_USER_ID_PARSED); + insert_project_ids.push(project_id); + insert_payouts.push(Decimal::from_f64_retain(*money).unwrap()); + insert_starts.push(*time); + } + + 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(&pool) + .await + .unwrap(); + + let day = 86400; + + // Test analytics endpoint with default values + // - all time points in the last 2 weeks + // - 1 day resolution + let analytics = api + .get_analytics_revenue_deserialized( + vec![&alpha_project_id], + None, + None, + None, + USER_USER_PAT, + ) + .await; + assert_eq!(analytics.len(), 1); // 1 project + let project_analytics = analytics.get(&alpha_project_id).unwrap(); + assert_eq!(project_analytics.len(), 8); // 1 days cut off, and 2 points take place on the same day. note that the day exactly 14 days ago is included + // sorted_by_key, values in the order of smallest to largest key + let (sorted_keys, sorted_by_key): (Vec, Vec) = project_analytics + .iter() + .sorted_by_key(|(k, _)| *k) + .rev() + .unzip(); + assert_eq!( + vec![100.1, 101.0, 200.0, 311.0, 400.0, 526.0, 633.0, 800.0], + to_f64_vec_rounded_up(sorted_by_key) + ); + // Ensure that the keys are in multiples of 1 day + for k in sorted_keys { + assert_eq!(k % day, 0); + } + + // Test analytics with last 900 days to include all data + // keep resolution at default + let analytics = api + .get_analytics_revenue_deserialized( + vec![&alpha_project_id], + Some(Utc::now() - Duration::days(801)), + None, + None, + USER_USER_PAT, + ) + .await; + let project_analytics = analytics.get(&alpha_project_id).unwrap(); + assert_eq!(project_analytics.len(), 9); // and 2 points take place on the same day + let (sorted_keys, sorted_by_key): (Vec, Vec) = project_analytics + .iter() + .sorted_by_key(|(k, _)| *k) + .rev() + .unzip(); + assert_eq!( + vec![100.1, 101.0, 200.0, 311.0, 400.0, 526.0, 633.0, 800.0, 800.0], + to_f64_vec_rounded_up(sorted_by_key) + ); + for k in sorted_keys { + assert_eq!(k % day, 0); + } + + // Cleanup test db + test_env.cleanup().await; +} + +fn to_f64_rounded_up(d: Decimal) -> f64 { + d.round_dp_with_strategy(1, rust_decimal::RoundingStrategy::MidpointAwayFromZero) + .to_f64() + .unwrap() +} + +fn to_f64_vec_rounded_up(d: Vec) -> Vec { + d.into_iter().map(to_f64_rounded_up).collect_vec() +} diff --git a/tests/common/api_v2/project.rs b/tests/common/api_v2/project.rs index 0ac2c420..25075089 100644 --- a/tests/common/api_v2/project.rs +++ b/tests/common/api_v2/project.rs @@ -1,14 +1,18 @@ +use std::collections::HashMap; + use actix_http::StatusCode; use actix_web::{ dev::ServiceResponse, test::{self, TestRequest}, }; use bytes::Bytes; +use chrono::{DateTime, Utc}; use labrinth::{ models::v2::projects::{LegacyProject, LegacyVersion}, search::SearchResults, util::actix::AppendsMultipart, }; +use rust_decimal::Decimal; use serde_json::json; use crate::common::{ @@ -197,4 +201,57 @@ impl ApiV2 { assert_eq!(status, 200); test::read_body_json(resp).await } + + pub async fn get_analytics_revenue( + &self, + id_or_slugs: Vec<&str>, + start_date: Option>, + end_date: Option>, + resolution_minutes: Option, + pat: &str, + ) -> ServiceResponse { + let projects_string = serde_json::to_string(&id_or_slugs).unwrap(); + let projects_string = urlencoding::encode(&projects_string); + + let mut extra_args = String::new(); + if let Some(start_date) = start_date { + let start_date = start_date.to_rfc3339(); + // let start_date = serde_json::to_string(&start_date).unwrap(); + let start_date = urlencoding::encode(&start_date); + extra_args.push_str(&format!("&start_date={start_date}")); + } + if let Some(end_date) = end_date { + let end_date = end_date.to_rfc3339(); + // let end_date = serde_json::to_string(&end_date).unwrap(); + let end_date = urlencoding::encode(&end_date); + extra_args.push_str(&format!("&end_date={end_date}")); + } + if let Some(resolution_minutes) = resolution_minutes { + extra_args.push_str(&format!("&resolution_minutes={}", resolution_minutes)); + } + + let req = test::TestRequest::get() + .uri(&format!( + "/v2/analytics/revenue?{projects_string}{extra_args}", + )) + .append_header(("Authorization", pat)) + .to_request(); + + self.call(req).await + } + + pub async fn get_analytics_revenue_deserialized( + &self, + id_or_slugs: Vec<&str>, + start_date: Option>, + end_date: Option>, + resolution_minutes: Option, + pat: &str, + ) -> HashMap> { + let resp = self + .get_analytics_revenue(id_or_slugs, start_date, end_date, resolution_minutes, pat) + .await; + assert_eq!(resp.status(), 200); + test::read_body_json(resp).await + } } diff --git a/tests/common/api_v2/version.rs b/tests/common/api_v2/version.rs index 6c0fc731..e2ac2743 100644 --- a/tests/common/api_v2/version.rs +++ b/tests/common/api_v2/version.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use actix_http::StatusCode; +use actix_http::{StatusCode, header::AUTHORIZATION}; use actix_web::{ dev::ServiceResponse, test::{self, TestRequest}, @@ -16,6 +16,11 @@ use crate::common::{asserts::assert_status, request_data::VersionCreationRequest use super::ApiV2; +pub fn url_encode_json_serialized_vec(elements: &[String]) -> String { + let serialized = serde_json::to_string(&elements).unwrap(); + urlencoding::encode(&serialized).to_string() +} + impl ApiV2 { pub async fn add_public_version( &self, @@ -321,4 +326,83 @@ impl ApiV2 { assert_eq!(resp.status(), 200); test::read_body_json(resp).await } + + + // TODO: remove redundancy in these functions + + pub async fn create_default_version( + &self, + project_id: &str, + ordering: Option, + pat: &str, + ) -> LegacyVersion { + let json_data = json!( + { + "project_id": project_id, + "file_parts": ["basic-mod-different.jar"], + "version_number": "1.2.3.4", + "version_title": "start", + "dependencies": [], + "game_versions": ["1.20.1"] , + "release_channel": "release", + "loaders": ["fabric"], + "featured": true, + "ordering": ordering, + } + ); + let json_segment = labrinth::util::actix::MultipartSegment { + name: "data".to_string(), + filename: None, + content_type: Some("application/json".to_string()), + data: labrinth::util::actix::MultipartSegmentData::Text( + serde_json::to_string(&json_data).unwrap(), + ), + }; + let file_segment = labrinth::util::actix::MultipartSegment { + name: "basic-mod-different.jar".to_string(), + filename: Some("basic-mod.jar".to_string()), + content_type: Some("application/java-archive".to_string()), + data: labrinth::util::actix::MultipartSegmentData::Binary( + include_bytes!("../../../tests/files/basic-mod-different.jar").to_vec(), + ), + }; + + let request = test::TestRequest::post() + .uri("/v2/version") + .set_multipart(vec![json_segment.clone(), file_segment.clone()]) + .append_header((AUTHORIZATION, pat)) + .to_request(); + let resp = self.call(request).await; + assert_status(&resp, StatusCode::OK); + test::read_body_json(resp).await + } + + pub async fn get_versions(&self, version_ids: Vec, pat: &str) -> Vec { + let ids = url_encode_json_serialized_vec(&version_ids); + let request = test::TestRequest::get() + .uri(&format!("/v2/versions?ids={}", ids)) + .append_header((AUTHORIZATION, pat)) + .to_request(); + let resp = self.call(request).await; + assert_status(&resp, StatusCode::OK); + test::read_body_json(resp).await + } + + pub async fn edit_version_ordering( + &self, + version_id: &str, + ordering: Option, + pat: &str, + ) -> ServiceResponse { + let request = test::TestRequest::patch() + .uri(&format!("/v2/version/{version_id}")) + .set_json(json!( + { + "ordering": ordering + } + )) + .append_header((AUTHORIZATION, pat)) + .to_request(); + self.call(request).await + } } diff --git a/tests/common/asserts.rs b/tests/common/asserts.rs index 3c7f585a..7263bf40 100644 --- a/tests/common/asserts.rs +++ b/tests/common/asserts.rs @@ -1,9 +1,24 @@ #![allow(dead_code)] +use crate::common::get_json_val_str; +use itertools::Itertools; +use labrinth::models::v2::projects::LegacyVersion; + pub fn assert_status(response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode) { assert_eq!(response.status(), status, "{:#?}", response.response()); } +pub fn assert_version_ids( + versions: &[LegacyVersion], + expected_ids: Vec, +) { + let version_ids = versions + .iter() + .map(|v| get_json_val_str(v.id)) + .collect_vec(); + assert_eq!(version_ids, expected_ids); +} + pub fn assert_any_status_except( response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 87dc467e..84ac23ab 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,8 +2,6 @@ use labrinth::{check_env_vars, clickhouse}; use labrinth::{file_hosting, queue, LabrinthConfig}; use std::sync::Arc; -use self::database::TemporaryDatabase; - pub mod api_v2; pub mod api_v3; pub mod asserts; @@ -17,7 +15,7 @@ pub mod scopes; // Testing equivalent to 'setup' function, producing a LabrinthConfig // If making a test, you should probably use environment::TestEnvironment::build() (which calls this) -pub async fn setup(db: &TemporaryDatabase) -> LabrinthConfig { +pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig { println!("Setting up labrinth config"); dotenvy::dotenv().ok(); diff --git a/tests/version.rs b/tests/version.rs index 823acf24..2edd400e 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -486,3 +486,127 @@ pub async fn test_project_versions() { test_env.cleanup().await; } +use crate::common::{asserts::assert_status, get_json_val_str}; +use actix_http::StatusCode; +use common::{ + asserts::assert_version_ids, database::USER_USER_PAT, environment::with_test_environment, +}; + +#[actix_rt::test] +async fn can_create_version_with_ordering() { + with_test_environment(|env| async move { + let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, Some(1), USER_USER_PAT) + .await + .id, + ); + + let versions = env + .v2 + .get_versions(vec![new_version_id.clone()], USER_USER_PAT) + .await; + assert_eq!(versions[0].ordering, Some(1)); + }) + .await; +} + +#[actix_rt::test] +async fn edit_version_ordering_works() { + with_test_environment(|env| async move { + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + + let resp = env + .v2 + .edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT) + .await; + assert_status(&resp, StatusCode::NO_CONTENT); + + let versions = env + .v2 + .get_versions(vec![alpha_version_id.clone()], USER_USER_PAT) + .await; + assert_eq!(versions[0].ordering, Some(10)); + }) + .await; +} + +#[actix_rt::test] +async fn version_ordering_for_specified_orderings_orders_lower_order_first() { + with_test_environment(|env| async move { + let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, Some(1), USER_USER_PAT) + .await + .id, + ); + env.v2 + .edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT) + .await; + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![new_version_id, alpha_version_id]); + }) + .await; +} + +#[actix_rt::test] +async fn version_ordering_when_unspecified_orders_oldest_first() { + with_test_environment(|env| async move { + let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(alpha_project_id, None, USER_USER_PAT) + .await + .id, + ); + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![alpha_version_id, new_version_id]); + }) + .await +} + +#[actix_rt::test] +async fn version_ordering_when_specified_orders_specified_before_unspecified() { + with_test_environment(|env| async move { + let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(alpha_project_id, Some(10000), USER_USER_PAT) + .await + .id, + ); + env.v2 + .edit_version_ordering(&alpha_version_id, None, USER_USER_PAT) + .await; + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![new_version_id, alpha_version_id]); + }) + .await; +}