You've already forked AstralRinth
forked from didirus/AstralRinth
Project gallery, webhook fixes, remove cache, re-enable donation URLs (#222)
This commit is contained in:
86
Cargo.lock
generated
86
Cargo.lock
generated
@@ -827,40 +827,6 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
|
||||
|
||||
[[package]]
|
||||
name = "cached"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e2afe73808fbaac302e39c9754bfc3c4b4d0f99c9c240b9f4e4efc841ad1b74"
|
||||
dependencies = [
|
||||
"async-mutex",
|
||||
"async-trait",
|
||||
"cached_proc_macro",
|
||||
"cached_proc_macro_types",
|
||||
"futures",
|
||||
"hashbrown 0.9.1",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf857ae42d910aede5c5186e62684b0d7a597ce2fe3bd14448ab8f7ef439848c"
|
||||
dependencies = [
|
||||
"async-mutex",
|
||||
"cached_proc_macro_types",
|
||||
"darling 0.10.2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cached_proc_macro_types"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.1"
|
||||
@@ -1122,38 +1088,14 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core 0.10.2",
|
||||
"darling_macro 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||
dependencies = [
|
||||
"darling_core 0.12.4",
|
||||
"darling_macro 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.9.3",
|
||||
"syn",
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1166,18 +1108,7 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core 0.10.2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -1187,7 +1118,7 @@ version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||
dependencies = [
|
||||
"darling_core 0.12.4",
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -2026,7 +1957,6 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.13.0",
|
||||
"bitflags",
|
||||
"cached",
|
||||
"chrono",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
@@ -3273,7 +3203,7 @@ version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48b35457e9d855d3dc05ef32a73e0df1e2c0fd72c38796a4ee909160c8eeec2"
|
||||
dependencies = [
|
||||
"darling 0.12.4",
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -3558,12 +3488,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
||||
@@ -56,5 +56,3 @@ sqlx = { version = "0.4.2", features = ["runtime-actix-rustls", "postgres", "chr
|
||||
|
||||
sentry = { version = "0.22.0", features = ["log"] }
|
||||
sentry-actix = "0.22.0"
|
||||
|
||||
cached = "0.23.0"
|
||||
6
migrations/20210718223710_gallery.sql
Normal file
6
migrations/20210718223710_gallery.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Add migration script here
|
||||
CREATE TABLE mods_gallery (
|
||||
id serial PRIMARY KEY,
|
||||
mod_id bigint REFERENCES mods ON UPDATE CASCADE NOT NULL,
|
||||
image_url varchar(2048) NOT NULL
|
||||
);
|
||||
905
sqlx-data.json
905
sqlx-data.json
@@ -1181,6 +1181,18 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"436dbf448697436ec90c30f44b27c92ec626601e7a7a9edb4d11bd916741b60f": {
|
||||
"query": "\n UPDATE mods\n SET icon_url = NULL\n WHERE (id = $1)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"43b793e2df30a6ace9e037e38bb4ea456656cfbe276c151e3a9e0a408d2c249f": {
|
||||
"query": "\n UPDATE versions\n SET release_channel = $1\n WHERE (id = $2)\n ",
|
||||
"describe": {
|
||||
@@ -1397,6 +1409,18 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"4d752ee3f43a1bf34d71c4391c9232537e0941294951f383ea8fa61e9d83fc96": {
|
||||
"query": "\n DELETE FROM mods_gallery\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"4e9f9eafbfd705dfc94571018cb747245a98ea61bad3fae4b3ce284229d99955": {
|
||||
"query": "\n UPDATE mods\n SET description = $1\n WHERE (id = $2)\n ",
|
||||
"describe": {
|
||||
@@ -2776,6 +2800,26 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"7c61fee015231f0a97c25d24f2c6be24821e39e330ab82344ad3b985d0d2aaea": {
|
||||
"query": "\n SELECT id FROM mods_gallery\n WHERE image_url = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"7e73d3a17807f57ba6def5ff718e6dcb3a65ef8da653d839560b24635334cf05": {
|
||||
"query": "\n SELECT m.title FROM mods m\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -3650,212 +3694,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"adca565e97b4cdd8095c9ee56a449a8ecd7858489a5f82628201e7dfd30217d0": {
|
||||
"query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "project_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "downloads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "follows",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "icon_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "body_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "published",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "updated",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "issues_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "source_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "wiki_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "discord_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "license_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "client_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "server_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "license",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "slug",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "rejection_reason",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "rejection_body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "status_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "client_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "server_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "short",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "license_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "project_type_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "categories",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"name": "versions",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"b0e3d1c70b87bb54819e3fac04b684a9b857aeedb4dcb7cb400c2af0dbb12922": {
|
||||
"query": "\n DELETE FROM teams\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
@@ -4659,212 +4497,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"caf24e9714afdea82cbfb8405cb291c8aee7c94bddc51260152ed1cbc629ffa1": {
|
||||
"query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "project_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "downloads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "follows",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "icon_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "body_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "published",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "updated",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "issues_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "source_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "wiki_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "discord_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "license_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "client_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "server_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "license",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "slug",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "rejection_reason",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "rejection_body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "status_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "client_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "server_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "short",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "license_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "project_type_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "categories",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"name": "versions",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"cb57ae673f1a7e50cc319efddb9bdc82e2251596bcf85aea52e8def343e423b8": {
|
||||
"query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n ",
|
||||
"describe": {
|
||||
@@ -5383,6 +5015,224 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"dc73655baea98436ddd0e266c7b123a9d240d4c1cb9d98875045e2235d772ab6": {
|
||||
"query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "project_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "downloads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "follows",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "icon_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "body_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "published",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "updated",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "issues_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "source_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "wiki_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "discord_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "license_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "client_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "server_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "license",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "slug",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "rejection_reason",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "rejection_body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "status_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "client_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "server_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "short",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "license_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "project_type_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "categories",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"name": "versions",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "gallery",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"name": "donations",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"e3235e872f98eb85d3eb4a2518fb9dc88049ce62362bfd02623e9b49ac2e9fed": {
|
||||
"query": "\n SELECT name FROM report_types\n ",
|
||||
"describe": {
|
||||
@@ -5458,6 +5308,224 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"e74c46f568e202e4e8cd02935bdfe6bef860e174c1648a9d3d5ef653fc7ac983": {
|
||||
"query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "project_type",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "title",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "description",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "downloads",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "follows",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "icon_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "body_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "published",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "updated",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "status",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "issues_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "source_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "wiki_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "discord_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "license_url",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "team_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "client_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "server_side",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "license",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "slug",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "rejection_reason",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "rejection_body",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "status_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "client_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "server_side_type",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "short",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "license_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "project_type_name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "categories",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"name": "versions",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "gallery",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"name": "donations",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
},
|
||||
"e7d0a64a08df6783c942f2fcadd94dd45f8d96ad3d3736e52ce90f68d396cdab": {
|
||||
"query": "SELECT EXISTS(SELECT 1 FROM team_members WHERE id=$1)",
|
||||
"describe": {
|
||||
@@ -5531,6 +5599,19 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"e9a79afea907cec2f6617e325b0b3b80d135ff99149a1516a8cffd4fbbd64e6d": {
|
||||
"query": "\n INSERT INTO mods_gallery (\n mod_id, image_url\n )\n VALUES (\n $1, $2\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"ea877d50ba461eae97ba3a35c3da71e7cdb7a92de1bb877d6b5dd766aca4e4ef": {
|
||||
"query": "\n SELECT u.id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role\n FROM users u\n WHERE u.github_id = $1\n ",
|
||||
"describe": {
|
||||
|
||||
50
src/database/cache/mod.rs
vendored
50
src/database/cache/mod.rs
vendored
@@ -1,50 +0,0 @@
|
||||
//pub mod project_cache;
|
||||
//pub mod project_query_cache;
|
||||
#[macro_export]
|
||||
macro_rules! generate_cache {
|
||||
($name:ident,$id:ty, $val:ty, $cache_name:ident, $mod_name:ident, $getter_name:ident, $setter_name:ident, $remover_name:ident) => {
|
||||
pub mod $mod_name {
|
||||
use cached::async_mutex::Mutex;
|
||||
use cached::{Cached, SizedCache};
|
||||
use lazy_static::lazy_static;
|
||||
lazy_static! {
|
||||
static ref $cache_name: Mutex<SizedCache<$id, $val>> =
|
||||
Mutex::new(SizedCache::with_size(400));
|
||||
}
|
||||
|
||||
pub async fn $getter_name<'a>(id: $id) -> Option<$val> {
|
||||
let mut cache = $cache_name.lock().await;
|
||||
Cached::cache_get(&mut *cache, &id).map(|e| e.clone())
|
||||
}
|
||||
pub async fn $setter_name<'a>(id: $id, val: &$val) {
|
||||
let mut cache = $cache_name.lock().await;
|
||||
Cached::cache_set(&mut *cache, id, val.clone());
|
||||
}
|
||||
pub async fn $remover_name<'a>(id: $id) {
|
||||
let mut cache = $cache_name.lock().await;
|
||||
Cached::cache_remove(&mut *cache, &id);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_cache!(
|
||||
project,
|
||||
String,
|
||||
crate::database::Project,
|
||||
PROJECT_CACHE,
|
||||
project_cache,
|
||||
get_cache_project,
|
||||
set_cache_project,
|
||||
remove_cache_project
|
||||
);
|
||||
generate_cache!(
|
||||
query_project,
|
||||
String,
|
||||
crate::database::models::project_item::QueryProject,
|
||||
QUERY_PROJECT_CACHE,
|
||||
query_project_cache,
|
||||
get_cache_query_project,
|
||||
set_cache_query_project,
|
||||
remove_cache_query_project
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod cache;
|
||||
pub mod models;
|
||||
mod postgres_database;
|
||||
pub use models::Project;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use super::ids::*;
|
||||
use crate::database::cache::project_cache::{get_cache_project, set_cache_project};
|
||||
use crate::database::cache::query_project_cache::{
|
||||
get_cache_query_project, set_cache_query_project,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DonationUrl {
|
||||
pub project_id: ProjectId,
|
||||
@@ -37,6 +34,36 @@ impl DonationUrl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GalleryItem {
|
||||
pub project_id: ProjectId,
|
||||
pub image_url: String,
|
||||
}
|
||||
|
||||
impl GalleryItem {
|
||||
pub async fn insert(
|
||||
&self,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), sqlx::error::Error> {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO mods_gallery (
|
||||
mod_id, image_url
|
||||
)
|
||||
VALUES (
|
||||
$1, $2
|
||||
)
|
||||
",
|
||||
self.project_id as ProjectId,
|
||||
self.image_url,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProjectBuilder {
|
||||
pub project_id: ProjectId,
|
||||
pub project_type_id: ProjectTypeId,
|
||||
@@ -58,6 +85,7 @@ pub struct ProjectBuilder {
|
||||
pub license: LicenseId,
|
||||
pub slug: Option<String>,
|
||||
pub donation_urls: Vec<DonationUrl>,
|
||||
pub gallery_items: Vec<GalleryItem>,
|
||||
}
|
||||
|
||||
impl ProjectBuilder {
|
||||
@@ -103,6 +131,11 @@ impl ProjectBuilder {
|
||||
donation.insert(&mut *transaction).await?;
|
||||
}
|
||||
|
||||
for mut gallery in self.gallery_items {
|
||||
gallery.project_id = self.project_id;
|
||||
gallery.insert(&mut *transaction).await?;
|
||||
}
|
||||
|
||||
for category in self.categories {
|
||||
sqlx::query!(
|
||||
"
|
||||
@@ -491,11 +524,6 @@ impl Project {
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
// Check in the cache
|
||||
let cached = get_cache_project(slug_or_project_id.clone()).await;
|
||||
if let Some(data) = cached {
|
||||
return Ok(Some(data));
|
||||
}
|
||||
let id_option =
|
||||
crate::models::ids::base62_impl::parse_base62(&*slug_or_project_id.clone()).ok();
|
||||
|
||||
@@ -505,22 +533,12 @@ impl Project {
|
||||
if project.is_none() {
|
||||
project = Project::get_from_slug(&slug_or_project_id, executor).await?;
|
||||
}
|
||||
// Cache the response
|
||||
if let Some(data) = project {
|
||||
set_cache_project(slug_or_project_id.clone(), &data).await;
|
||||
Ok(Some(data))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Ok(project)
|
||||
} else {
|
||||
let project = Project::get_from_slug(&slug_or_project_id, executor).await?;
|
||||
// Capture the data, and try to cache it
|
||||
if let Some(data) = project {
|
||||
set_cache_project(slug_or_project_id.clone(), &data).await;
|
||||
Ok(Some(data))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Ok(project)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,11 +549,6 @@ impl Project {
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||
{
|
||||
// Query cache
|
||||
let cached = get_cache_query_project(slug_or_project_id.clone()).await;
|
||||
if let Some(data) = cached {
|
||||
return Ok(Some(data));
|
||||
}
|
||||
let id_option =
|
||||
crate::models::ids::base62_impl::parse_base62(&*slug_or_project_id.clone()).ok();
|
||||
|
||||
@@ -545,21 +558,11 @@ impl Project {
|
||||
if project.is_none() {
|
||||
project = Project::get_full_from_slug(&slug_or_project_id, executor).await?;
|
||||
}
|
||||
// Save the variable
|
||||
if let Some(data) = project {
|
||||
set_cache_query_project(slug_or_project_id.clone(), &data).await;
|
||||
Ok(Some(data))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Ok(project)
|
||||
} else {
|
||||
let project = Project::get_full_from_slug(&slug_or_project_id, executor).await?;
|
||||
if let Some(data) = project {
|
||||
set_cache_query_project(slug_or_project_id.clone(), &data).await;
|
||||
Ok(Some(data))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(project)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,11 +581,15 @@ impl Project {
|
||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions
|
||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,
|
||||
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
||||
FROM mods m
|
||||
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
|
||||
LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id
|
||||
LEFT OUTER JOIN versions v ON v.mod_id = m.id
|
||||
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||
LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||
LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||
INNER JOIN statuses s ON s.id = m.status
|
||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||
@@ -637,7 +644,29 @@ impl Project {
|
||||
.split(',')
|
||||
.map(|x| VersionId(x.parse().unwrap_or_default()))
|
||||
.collect(),
|
||||
donation_urls: vec![],
|
||||
donation_urls: m
|
||||
.donations
|
||||
.unwrap_or_default()
|
||||
.split(" ,")
|
||||
.map(|d| {
|
||||
let strings: Vec<&str> = d.split(", ").collect();
|
||||
DonationUrl {
|
||||
project_id: id,
|
||||
platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)),
|
||||
platform_short: strings[2].to_string(),
|
||||
platform_name: strings[3].to_string(),
|
||||
url: strings[1].to_string(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
gallery_items: m
|
||||
.gallery
|
||||
.into_iter()
|
||||
.map(|x| GalleryItem {
|
||||
project_id: id,
|
||||
image_url: x,
|
||||
})
|
||||
.collect(),
|
||||
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
|
||||
license_id: m.short,
|
||||
license_name: m.license_name,
|
||||
@@ -667,11 +696,15 @@ impl Project {
|
||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions
|
||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,
|
||||
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
||||
FROM mods m
|
||||
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
|
||||
LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id
|
||||
LEFT OUTER JOIN versions v ON v.mod_id = m.id
|
||||
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||
LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||
LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||
INNER JOIN statuses s ON s.id = m.status
|
||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||
@@ -684,9 +717,11 @@ impl Project {
|
||||
)
|
||||
.fetch_many(exec)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right().map(|m| QueryProject {
|
||||
inner: Project {
|
||||
id: ProjectId(m.id),
|
||||
Ok(e.right().map(|m| {
|
||||
let id = m.id;
|
||||
QueryProject {
|
||||
inner: Project {
|
||||
id: ProjectId(id),
|
||||
project_type: ProjectTypeId(m.project_type),
|
||||
team_id: TeamId(m.team_id),
|
||||
title: m.title.clone(),
|
||||
@@ -714,13 +749,31 @@ impl Project {
|
||||
project_type: m.project_type_name,
|
||||
categories: m.categories.unwrap_or_default().split(',').map(|x| x.to_string()).collect(),
|
||||
versions: m.versions.unwrap_or_default().split(',').map(|x| VersionId(x.parse().unwrap_or_default())).collect(),
|
||||
donation_urls: vec![],
|
||||
donation_urls: m
|
||||
.donations
|
||||
.unwrap_or_default()
|
||||
.split(" ,")
|
||||
.map(|d| {
|
||||
let strings: Vec<&str> = d.split(", ").collect();
|
||||
DonationUrl {
|
||||
project_id: ProjectId(id),
|
||||
platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)),
|
||||
platform_short: strings[2].to_string(),
|
||||
platform_name: strings[3].to_string(),
|
||||
url: strings[1].to_string(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
gallery_items: m.gallery.iter().map(|x| GalleryItem {
|
||||
project_id: ProjectId(id),
|
||||
image_url: x.to_string()
|
||||
}).collect(),
|
||||
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
|
||||
license_id: m.short,
|
||||
license_name: m.license_name,
|
||||
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
|
||||
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
|
||||
}))
|
||||
}}))
|
||||
})
|
||||
.try_collect::<Vec<QueryProject>>()
|
||||
.await
|
||||
@@ -733,6 +786,7 @@ pub struct QueryProject {
|
||||
pub categories: Vec<String>,
|
||||
pub versions: Vec<VersionId>,
|
||||
pub donation_urls: Vec<DonationUrl>,
|
||||
pub gallery_items: Vec<GalleryItem>,
|
||||
pub status: crate::models::projects::ProjectStatus,
|
||||
pub license_id: String,
|
||||
pub license_name: String,
|
||||
|
||||
@@ -75,6 +75,9 @@ pub struct Project {
|
||||
pub discord_url: Option<String>,
|
||||
/// An optional list of all donation links the project has
|
||||
pub donation_urls: Option<Vec<DonationLink>>,
|
||||
|
||||
/// A string of URLs to visual content featuring the project
|
||||
pub gallery: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
||||
@@ -42,7 +42,7 @@ pub enum CreateError {
|
||||
FileValidationError(#[from] crate::validate::ValidationError),
|
||||
#[error("{}", .0)]
|
||||
MissingValueError(String),
|
||||
#[error("Invalid format for project icon: {0}")]
|
||||
#[error("Invalid format for image: {0}")]
|
||||
InvalidIconFormat(String),
|
||||
#[error("Error with multipart data: {0}")]
|
||||
InvalidInput(String),
|
||||
@@ -150,7 +150,7 @@ struct ProjectCreateData {
|
||||
/// The support range for the server project
|
||||
pub server_side: SideType,
|
||||
|
||||
#[validate(length(max = 64))]
|
||||
#[validate(length(max = 32))]
|
||||
#[validate]
|
||||
/// A list of initial versions to upload with the created project
|
||||
pub initial_versions: Vec<InitialVersionData>,
|
||||
@@ -182,6 +182,10 @@ struct ProjectCreateData {
|
||||
|
||||
/// The license id that the project follows
|
||||
pub license_id: String,
|
||||
|
||||
#[validate(length(max = 64))]
|
||||
/// The multipart names of the gallery items to upload
|
||||
pub gallery_items: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct UploadedFile {
|
||||
@@ -285,6 +289,7 @@ pub async fn project_create_inner(
|
||||
let project_create_data;
|
||||
let mut versions;
|
||||
let mut versions_map = std::collections::HashMap::new();
|
||||
let mut gallery_urls = Vec::new();
|
||||
|
||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||
@@ -421,6 +426,45 @@ pub async fn project_create_inner(
|
||||
continue;
|
||||
}
|
||||
|
||||
if project_create_data
|
||||
.gallery_items
|
||||
.iter()
|
||||
.find(|x| *x == name)
|
||||
.is_some()
|
||||
{
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
}
|
||||
|
||||
const FILE_SIZE_CAP: usize = 5 * (1 << 20);
|
||||
|
||||
if data.len() >= FILE_SIZE_CAP {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)));
|
||||
}
|
||||
|
||||
let hash = sha1::Sha1::from(&data).hexdigest();
|
||||
let (_, file_extension) = super::version_creation::get_name_ext(&content_disposition)?;
|
||||
let content_type = crate::util::ext::get_image_content_type(file_extension)
|
||||
.ok_or_else(|| CreateError::InvalidIconFormat(file_extension.to_string()))?;
|
||||
|
||||
let url = format!("data/{}/images/{}.{}", project_id, hash, file_extension);
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &url, data.to_vec())
|
||||
.await?;
|
||||
|
||||
uploaded_files.push(UploadedFile {
|
||||
file_id: upload_data.file_id,
|
||||
file_name: upload_data.file_name.clone(),
|
||||
});
|
||||
|
||||
gallery_urls.push(format!("{}/{}", cdn_url, url));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let index = if let Some(i) = versions_map.get(name) {
|
||||
*i
|
||||
} else {
|
||||
@@ -578,6 +622,13 @@ pub async fn project_create_inner(
|
||||
license: license_id,
|
||||
slug: Some(project_create_data.slug),
|
||||
donation_urls,
|
||||
gallery_items: gallery_urls
|
||||
.iter()
|
||||
.map(|x| models::project_item::GalleryItem {
|
||||
project_id: project_id.into(),
|
||||
image_url: x.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
@@ -616,6 +667,7 @@ pub async fn project_create_inner(
|
||||
wiki_url: project_builder.wiki_url.clone(),
|
||||
discord_url: project_builder.discord_url.clone(),
|
||||
donation_urls: project_create_data.donation_urls.clone(),
|
||||
gallery: gallery_urls,
|
||||
};
|
||||
|
||||
let _project_id = project_builder.insert(&mut *transaction).await?;
|
||||
@@ -726,7 +778,7 @@ async fn process_icon_upload(
|
||||
mut field: actix_multipart::Field,
|
||||
cdn_url: &str,
|
||||
) -> Result<String, CreateError> {
|
||||
if let Some(content_type) = get_image_content_type(file_extension) {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
@@ -756,22 +808,3 @@ async fn process_icon_upload(
|
||||
Err(CreateError::InvalidIconFormat(file_extension.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_image_content_type(extension: &str) -> Option<&'static str> {
|
||||
let content_type = match &*extension {
|
||||
"bmp" => "image/bmp",
|
||||
"gif" => "image/gif",
|
||||
"jpeg" | "jpg" | "jpe" => "image/jpeg",
|
||||
"png" => "image/png",
|
||||
"svg" | "svgz" => "image/svg+xml",
|
||||
"webp" => "image/webp",
|
||||
"rgb" => "image/x-rgb",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
if !content_type.is_empty() {
|
||||
Some(content_type)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::database;
|
||||
use crate::database::cache::project_cache::remove_cache_project;
|
||||
use crate::database::cache::query_project_cache::remove_cache_query_project;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{
|
||||
@@ -271,6 +269,11 @@ pub fn convert_project(
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
gallery: data
|
||||
.gallery_items
|
||||
.into_iter()
|
||||
.map(|x| x.image_url)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,33 +455,13 @@ pub async fn project_edit(
|
||||
));
|
||||
}
|
||||
|
||||
if status == &ProjectStatus::Processing && project_item.versions.is_empty() {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Project submitted for review with no initial versions",
|
||||
)));
|
||||
}
|
||||
if status == &ProjectStatus::Processing {
|
||||
if project_item.versions.is_empty() {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Project submitted for review with no initial versions",
|
||||
)));
|
||||
}
|
||||
|
||||
let status_id = database::models::StatusId::get_id(&status, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError(
|
||||
"No database entry for status provided.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET status = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
status_id as database::models::ids::StatusId,
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if project_item.status == ProjectStatus::Processing {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -511,6 +494,26 @@ pub async fn project_edit(
|
||||
}
|
||||
}
|
||||
|
||||
let status_id = database::models::StatusId::get_id(&status, &mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError(
|
||||
"No database entry for status provided.".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET status = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
status_id as database::models::ids::StatusId,
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if project_item.status.is_searchable() && !status.is_searchable() {
|
||||
delete_from_index(id.into(), config).await?;
|
||||
} else if !project_item.status.is_searchable() && status.is_searchable() {
|
||||
@@ -901,15 +904,6 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
let id: ProjectId = project_item.inner.id.into();
|
||||
remove_cache_project(id.to_string().clone()).await;
|
||||
remove_cache_query_project(id.to_string()).await;
|
||||
|
||||
if let Some(slug) = project_item.inner.slug {
|
||||
remove_cache_project(slug.clone()).await;
|
||||
remove_cache_query_project(slug).await;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -936,7 +930,7 @@ pub async fn project_icon_edit(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) = super::project_creation::get_image_content_type(&*ext.ext) {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
||||
let cdn_url = dotenv::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
@@ -988,7 +982,7 @@ pub async fn project_icon_edit(
|
||||
)));
|
||||
}
|
||||
|
||||
let hash = sha1::Sha1::from(bytes.clone()).hexdigest();
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
|
||||
let project_id: ProjectId = project_item.id.into();
|
||||
|
||||
@@ -1000,6 +994,8 @@ pub async fn project_icon_edit(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -1009,17 +1005,10 @@ pub async fn project_icon_edit(
|
||||
format!("{}/{}", cdn_url, upload_data.file_name),
|
||||
project_item.id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&**pool)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
let id: ProjectId = project_item.id.into();
|
||||
remove_cache_project(id.to_string().clone()).await;
|
||||
remove_cache_query_project(id.to_string()).await;
|
||||
|
||||
if let Some(slug) = project_item.slug {
|
||||
remove_cache_project(slug.clone()).await;
|
||||
remove_cache_query_project(slug).await;
|
||||
}
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
@@ -1030,6 +1019,232 @@ pub async fn project_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("{id}/icon")]
|
||||
pub async fn delete_project_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item =
|
||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
project_item.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(icon) = project_item.icon_url {
|
||||
let name = icon.split('/').next();
|
||||
|
||||
if let Some(icon_path) = name {
|
||||
file_host.delete_file_version("", icon_path).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET icon_url = NULL
|
||||
WHERE (id = $1)
|
||||
",
|
||||
project_item.id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[post("{id}/gallery")]
|
||||
pub async fn add_gallery_item(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
||||
let cdn_url = dotenv::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item =
|
||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
project_item.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to edit this project's gallery.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = payload.next().await {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInputError("Unable to parse bytes in payload sent!".to_string())
|
||||
})?);
|
||||
}
|
||||
|
||||
const FILE_SIZE_CAP: usize = 5 * (1 << 20);
|
||||
|
||||
if bytes.len() >= FILE_SIZE_CAP {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)));
|
||||
}
|
||||
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
|
||||
let id: ProjectId = project_item.id.into();
|
||||
let url = format!("data/{}/images/{}.{}", id, hash, &*ext.ext);
|
||||
file_host
|
||||
.upload_file(content_type, &url, bytes.to_vec())
|
||||
.await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
database::models::project_item::GalleryItem {
|
||||
project_id: project_item.id,
|
||||
image_url: format!("{}/{}", cdn_url, url),
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::InvalidInputError(format!(
|
||||
"Invalid format for gallery image: {}",
|
||||
ext.ext
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GalleryItem {
|
||||
pub item: String,
|
||||
}
|
||||
|
||||
#[delete("{id}/gallery")]
|
||||
pub async fn delete_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryItem>,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let project_item =
|
||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !user.role.is_mod() {
|
||||
let team_member = database::models::TeamMember::get_from_user_id(
|
||||
project_item.team_id,
|
||||
user.id.into(),
|
||||
&**pool,
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::DatabaseError)?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
||||
})?;
|
||||
|
||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthenticationError(
|
||||
"You don't have permission to edit this project's icon.".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
let id = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods_gallery
|
||||
WHERE image_url = $1
|
||||
",
|
||||
item.item
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInputError(format!(
|
||||
"Gallery item at URL {} is not part of the project's gallery.",
|
||||
item.item
|
||||
))
|
||||
})?
|
||||
.id;
|
||||
|
||||
let name = item.item.split('/').next();
|
||||
|
||||
if let Some(item_path) = name {
|
||||
file_host.delete_file_version("", item_path).await?;
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM mods_gallery
|
||||
WHERE id = $1
|
||||
",
|
||||
id
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
pub async fn project_delete(
|
||||
req: HttpRequest,
|
||||
@@ -1072,15 +1287,6 @@ pub async fn project_delete(
|
||||
|
||||
let result = database::models::Project::remove_full(project.id, &mut transaction).await?;
|
||||
|
||||
let id: ProjectId = project.id.into();
|
||||
remove_cache_project(id.to_string().clone()).await;
|
||||
remove_cache_query_project(id.to_string()).await;
|
||||
|
||||
if let Some(slug) = project.slug {
|
||||
remove_cache_project(slug.clone()).await;
|
||||
remove_cache_query_project(slug).await;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
delete_from_index(project.id.into(), config).await?;
|
||||
|
||||
@@ -286,7 +286,7 @@ pub async fn user_icon_edit(
|
||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
mut payload: web::Payload,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
if let Some(content_type) = super::project_creation::get_image_content_type(&*ext.ext) {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
||||
let cdn_url = dotenv::var("CDN_URL")?;
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
let id_option =
|
||||
|
||||
@@ -580,7 +580,7 @@ pub async fn upload_file(
|
||||
) -> Result<(), CreateError> {
|
||||
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
||||
|
||||
let content_type = project_file_type(file_extension)
|
||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
@@ -588,13 +588,13 @@ pub async fn upload_file(
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
}
|
||||
|
||||
// Project file size limit of 25MiB
|
||||
const FILE_SIZE_CAP: usize = 25 * (2 << 30);
|
||||
// Project file size limit of 100MiB
|
||||
const FILE_SIZE_CAP: usize = 100 * (1 << 20);
|
||||
|
||||
// TODO: override file size cap for authorized users or projects
|
||||
if data.len() >= FILE_SIZE_CAP {
|
||||
return Err(CreateError::InvalidInput(
|
||||
String::from("Project file exceeds the maximum of 25MiB. Contact a moderator or admin to request permission to upload larger files.")
|
||||
String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.")
|
||||
));
|
||||
}
|
||||
|
||||
@@ -649,14 +649,6 @@ pub async fn upload_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Currently we only support jar projects; this may change in the future (datapacks?)
|
||||
fn project_file_type(ext: &str) -> Option<&str> {
|
||||
match ext {
|
||||
"jar" => Some("application/java-archive"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name_ext(
|
||||
content_disposition: &actix_web::http::header::ContentDisposition,
|
||||
) -> Result<(&str, &str), CreateError> {
|
||||
|
||||
27
src/util/ext.rs
Normal file
27
src/util/ext.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
pub fn get_image_content_type(extension: &str) -> Option<&'static str> {
|
||||
let content_type = match &*extension {
|
||||
"bmp" => "image/bmp",
|
||||
"gif" => "image/gif",
|
||||
"jpeg" | "jpg" | "jpe" => "image/jpeg",
|
||||
"png" => "image/png",
|
||||
"svg" | "svgz" => "image/svg+xml",
|
||||
"webp" => "image/webp",
|
||||
"rgb" => "image/x-rgb",
|
||||
"mp4" => "video/mp4",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
if !content_type.is_empty() {
|
||||
Some(content_type)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_file_type(ext: &str) -> Option<&str> {
|
||||
match ext {
|
||||
"jar" => Some("application/java-archive"),
|
||||
"zip" => Some("application/zip"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod ext;
|
||||
pub mod validate;
|
||||
pub mod webhook;
|
||||
|
||||
@@ -113,5 +113,3 @@ fn game_version_supported(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo: fabric/forge validators for 1.8+ respectively
|
||||
|
||||
Reference in New Issue
Block a user