You've already forked AstralRinth
forked from didirus/AstralRinth
Add dependencies to search (#578)
* Add dependencies to search * add attrs for faceting * run prepare * Add user data route from token * update to 24hrs * Fix report bugs
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
edition = "2018"
|
|
||||||
max_width = 80
|
|
||||||
13
shell.nix
13
shell.nix
@@ -1,13 +0,0 @@
|
|||||||
# TODO: Move to flake
|
|
||||||
{pkgs ? import <nixpkgs> {},
|
|
||||||
fenix ? import (fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz") {}
|
|
||||||
}:
|
|
||||||
pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
fenix.default.toolchain
|
|
||||||
docker docker-compose
|
|
||||||
git
|
|
||||||
openssl pkg-config
|
|
||||||
sqlx-cli
|
|
||||||
];
|
|
||||||
}
|
|
||||||
444
sqlx-data.json
444
sqlx-data.json
@@ -525,26 +525,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)\n "
|
"query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)\n "
|
||||||
},
|
},
|
||||||
"0f6469055265ad8b114136368001aa927b587df9f64f0e19fd37d1f4b4adab60": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n SELECT id FROM mods\n WHERE status = $1 AND queued < NOW() - INTERVAL '1 day'\n ORDER BY updated ASC\n "
|
|
||||||
},
|
|
||||||
"0fb1cca8a2a37107104244953371fe2f8a5e6edd57f4b325c5842c6571eb16b4": {
|
"0fb1cca8a2a37107104244953371fe2f8a5e6edd57f4b325c5842c6571eb16b4": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -566,6 +546,178 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2)\n "
|
"query": "\n SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2)\n "
|
||||||
},
|
},
|
||||||
|
"113bffbd003f0f32eef61468148a51dd9437be841c5b79fdb52dd6c12ebaba61": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project_type",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "title",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "downloads",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "follows",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "icon_url",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "published",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approved",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated",
|
||||||
|
"ordinal": 9,
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "team_id",
|
||||||
|
"ordinal": 10,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "license",
|
||||||
|
"ordinal": 11,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slug",
|
||||||
|
"ordinal": 12,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status_name",
|
||||||
|
"ordinal": 13,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "color",
|
||||||
|
"ordinal": 14,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "client_side_type",
|
||||||
|
"ordinal": 15,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server_side_type",
|
||||||
|
"ordinal": 16,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project_type_name",
|
||||||
|
"ordinal": 17,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 18,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "categories",
|
||||||
|
"ordinal": 19,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "additional_categories",
|
||||||
|
"ordinal": 20,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "loaders",
|
||||||
|
"ordinal": 21,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "versions",
|
||||||
|
"ordinal": 22,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gallery",
|
||||||
|
"ordinal": 23,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "featured_gallery",
|
||||||
|
"ordinal": 24,
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dependencies",
|
||||||
|
"ordinal": 25,
|
||||||
|
"type_info": "Jsonb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"TextArray",
|
||||||
|
"TextArray",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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.published published, m.approved approved, m.updated updated,\n m.team_id team_id, m.license license, m.slug slug, m.status status_name, m.color color,\n cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is false) gallery,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_gallery,\n JSONB_AGG(DISTINCT jsonb_build_object('id', mdep.id, 'dep_type', d.dependency_type)) filter (where mdep.id is not null) dependencies\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 AND v.status != ANY($1)\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN dependencies d ON d.dependent_id = v.id\n LEFT OUTER JOIN mods mdep ON mdep.id = d.mod_dependency_id\n INNER JOIN project_types pt ON pt.id = m.project_type\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 team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE m.status = ANY($2)\n GROUP BY m.id, cs.id, ss.id, pt.id, u.id;\n "
|
||||||
|
},
|
||||||
"1209ffc1ffbea89f7060573275dc7325ac4d7b4885b6c1d1ec92998e6012e455": {
|
"1209ffc1ffbea89f7060573275dc7325ac4d7b4885b6c1d1ec92998e6012e455": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -611,6 +763,18 @@
|
|||||||
},
|
},
|
||||||
"query": "\n UPDATE mods\n SET webhook_sent = TRUE\n WHERE id = $1\n "
|
"query": "\n UPDATE mods\n SET webhook_sent = TRUE\n WHERE id = $1\n "
|
||||||
},
|
},
|
||||||
|
"127691940ca7e542e246dd2a1c9cb391041b30ddf0547d73b49c1dd9dc59d2ae": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8Array"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n UPDATE notifications\n SET read = TRUE\n WHERE id = ANY($1)\n "
|
||||||
|
},
|
||||||
"1411c9ae3af067679aa21d7f45937cd94d457e4eb17a108566776a9bd1ee77e2": {
|
"1411c9ae3af067679aa21d7f45937cd94d457e4eb17a108566776a9bd1ee77e2": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -1898,19 +2062,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed\n FROM reports r\n INNER JOIN report_types rt ON rt.id = r.report_type_id\n WHERE r.id = ANY($1)\n ORDER BY r.created DESC\n "
|
"query": "\n SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed\n FROM reports r\n INNER JOIN report_types rt ON rt.id = r.report_type_id\n WHERE r.id = ANY($1)\n ORDER BY r.created DESC\n "
|
||||||
},
|
},
|
||||||
"3ae7c4a29dab8bce0e84a9c47a4a4f50a3be4bcb86e5b13d7dd60975d62e9ea3": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"nullable": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8",
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "\n INSERT INTO threads (\n id, thread_type\n )\n VALUES (\n $1, $2\n )\n "
|
|
||||||
},
|
|
||||||
"3af747b5543a5a9b10dcce0a1eb9c2a1926dd5a507fe0d8b7f52d8ccc7fcd0af": {
|
"3af747b5543a5a9b10dcce0a1eb9c2a1926dd5a507fe0d8b7f52d8ccc7fcd0af": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -2673,6 +2824,26 @@
|
|||||||
},
|
},
|
||||||
"query": "\n UPDATE versions\n SET version_number = $1\n WHERE (id = $2)\n "
|
"query": "\n UPDATE versions\n SET version_number = $1\n WHERE (id = $2)\n "
|
||||||
},
|
},
|
||||||
|
"5586d60c8f3d58a31e6635ffb3cb30bac389bf21b190dfd1e64a44e837f3879c": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT id FROM mods\n WHERE status = $1 AND queued < NOW() - INTERVAL '40 hours'\n ORDER BY updated ASC\n "
|
||||||
|
},
|
||||||
"57743e20646dab2bcc02fe555d6b8ddb999697b7e95ec732d1a1a9e2bfdb8181": {
|
"57743e20646dab2bcc02fe555d6b8ddb999697b7e95ec732d1a1a9e2bfdb8181": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -4374,6 +4545,32 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE u.id = ANY($1)\n "
|
"query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE u.id = ANY($1)\n "
|
||||||
},
|
},
|
||||||
|
"a962f21969bba402258fca169c45f3d71bc1b71f754cdcc1f5c968e4948653b2": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "notifs_count",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "followed_projects",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Int8Array"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n SELECT COUNT(DISTINCT n.id) notifs_count, ARRAY_AGG(mf.mod_id) followed_projects FROM notifications n\n LEFT OUTER JOIN mod_follows mf ON mf.follower_id = $1\n WHERE user_id = $1 AND read = FALSE\n "
|
||||||
|
},
|
||||||
"aa59f79136ef87dd4121d5f367f5dbdbca80e936c1b986ec99c09c3e95daa756": {
|
"aa59f79136ef87dd4121d5f367f5dbdbca80e936c1b986ec99c09c3e95daa756": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -4752,172 +4949,6 @@
|
|||||||
},
|
},
|
||||||
"query": "\n DELETE FROM game_versions_versions gvv\n WHERE gvv.joining_version_id = $1\n "
|
"query": "\n DELETE FROM game_versions_versions gvv\n WHERE gvv.joining_version_id = $1\n "
|
||||||
},
|
},
|
||||||
"bf4afeda41a54e09a80a4cc505d1fbb72124c442ebaca731a291f022524daf1a": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "project_type",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "title",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "description",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "downloads",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "follows",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "icon_url",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "published",
|
|
||||||
"ordinal": 7,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approved",
|
|
||||||
"ordinal": 8,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated",
|
|
||||||
"ordinal": 9,
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "team_id",
|
|
||||||
"ordinal": 10,
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "license",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "slug",
|
|
||||||
"ordinal": 12,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "status_name",
|
|
||||||
"ordinal": 13,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "color",
|
|
||||||
"ordinal": 14,
|
|
||||||
"type_info": "Int4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "client_side_type",
|
|
||||||
"ordinal": 15,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "server_side_type",
|
|
||||||
"ordinal": 16,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "project_type_name",
|
|
||||||
"ordinal": 17,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 18,
|
|
||||||
"type_info": "Varchar"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "categories",
|
|
||||||
"ordinal": 19,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "additional_categories",
|
|
||||||
"ordinal": 20,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "loaders",
|
|
||||||
"ordinal": 21,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "versions",
|
|
||||||
"ordinal": 22,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gallery",
|
|
||||||
"ordinal": 23,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "featured_gallery",
|
|
||||||
"ordinal": 24,
|
|
||||||
"type_info": "VarcharArray"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"TextArray",
|
|
||||||
"TextArray",
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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.published published, m.approved approved, m.updated updated,\n m.team_id team_id, m.license license, m.slug slug, m.status status_name, m.color color,\n cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is false) gallery,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_gallery\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 AND v.status != ANY($1)\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN project_types pt ON pt.id = m.project_type\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 team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE m.status = ANY($2)\n GROUP BY m.id, cs.id, ss.id, pt.id, u.id;\n "
|
|
||||||
},
|
|
||||||
"bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": {
|
"bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -5373,6 +5404,21 @@
|
|||||||
},
|
},
|
||||||
"query": "\n SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h\n INNER JOIN files f on h.file_id = f.id\n INNER JOIN versions v on f.version_id = v.id\n WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)\n "
|
"query": "\n SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h\n INNER JOIN files f on h.file_id = f.id\n INNER JOIN versions v on f.version_id = v.id\n WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)\n "
|
||||||
},
|
},
|
||||||
|
"d0a65443aef9d3781000d0a59d8d81d1a8e51613dfee343d079c233375cebd12": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO threads (\n id, thread_type, report_id, project_id\n )\n VALUES (\n $1, $2, $3, $4\n )\n "
|
||||||
|
},
|
||||||
"d12bc07adb4dc8147d0ddccd72a4f23ed38cd31d7db3d36ebbe2c9b627130f0b": {
|
"d12bc07adb4dc8147d0ddccd72a4f23ed38cd31d7db3d36ebbe2c9b627130f0b": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
|||||||
@@ -52,10 +52,7 @@ pub struct DonationPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Category {
|
impl Category {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<CategoryId>, DatabaseError>
|
||||||
name: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<CategoryId>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -124,10 +121,7 @@ impl Category {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Loader {
|
impl Loader {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<LoaderId>, DatabaseError>
|
||||||
name: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<LoaderId>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -315,10 +309,7 @@ impl GameVersion {
|
|||||||
|
|
||||||
impl<'a> GameVersionBuilder<'a> {
|
impl<'a> GameVersionBuilder<'a> {
|
||||||
/// The game version. Spaces must be replaced with '_' for it to be valid
|
/// The game version. Spaces must be replaced with '_' for it to be valid
|
||||||
pub fn version(
|
pub fn version(self, version: &'a str) -> Result<GameVersionBuilder<'a>, DatabaseError> {
|
||||||
self,
|
|
||||||
version: &'a str,
|
|
||||||
) -> Result<GameVersionBuilder<'a>, DatabaseError> {
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version: Some(version),
|
version: Some(version),
|
||||||
..self
|
..self
|
||||||
@@ -342,10 +333,7 @@ impl<'a> GameVersionBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert<'b, E>(
|
pub async fn insert<'b, E>(self, exec: E) -> Result<GameVersionId, DatabaseError>
|
||||||
self,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<GameVersionId, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -393,9 +381,7 @@ impl DonationPlatform {
|
|||||||
Ok(result.map(|r| DonationPlatformId(r.id)))
|
Ok(result.map(|r| DonationPlatformId(r.id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list<'a, E>(
|
pub async fn list<'a, E>(exec: E) -> Result<Vec<DonationPlatform>, DatabaseError>
|
||||||
exec: E,
|
|
||||||
) -> Result<Vec<DonationPlatform>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -420,10 +406,7 @@ impl DonationPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ReportType {
|
impl ReportType {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<ReportTypeId>, DatabaseError>
|
||||||
name: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<ReportTypeId>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -459,10 +442,7 @@ impl ReportType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectType {
|
impl ProjectType {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<ProjectTypeId>, DatabaseError>
|
||||||
name: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<ProjectTypeId>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -498,10 +478,7 @@ impl ProjectType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SideType {
|
impl SideType {
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<SideTypeId>, DatabaseError>
|
||||||
name: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<SideTypeId>, DatabaseError>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ impl Notification {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let notification_ids_parsed: Vec<i64> =
|
let notification_ids_parsed: Vec<i64> = notification_ids.iter().map(|x| x.0).collect();
|
||||||
notification_ids.iter().map(|x| x.0).collect();
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type, n.body,
|
SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type, n.body,
|
||||||
@@ -204,6 +203,33 @@ impl Notification {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn read(
|
||||||
|
id: NotificationId,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<Option<()>, sqlx::error::Error> {
|
||||||
|
Self::read_many(&[id], transaction).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_many(
|
||||||
|
notification_ids: &[NotificationId],
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<Option<()>, sqlx::error::Error> {
|
||||||
|
let notification_ids_parsed: Vec<i64> = notification_ids.iter().map(|x| x.0).collect();
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE notifications
|
||||||
|
SET read = TRUE
|
||||||
|
WHERE id = ANY($1)
|
||||||
|
",
|
||||||
|
¬ification_ids_parsed
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(()))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn remove(
|
pub async fn remove(
|
||||||
id: NotificationId,
|
id: NotificationId,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
@@ -215,8 +241,7 @@ impl Notification {
|
|||||||
notification_ids: &[NotificationId],
|
notification_ids: &[NotificationId],
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Option<()>, sqlx::error::Error> {
|
) -> Result<Option<()>, sqlx::error::Error> {
|
||||||
let notification_ids_parsed: Vec<i64> =
|
let notification_ids_parsed: Vec<i64> = notification_ids.iter().map(|x| x.0).collect();
|
||||||
notification_ids.iter().map(|x| x.0).collect();
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -186,12 +186,11 @@ impl ProjectBuilder {
|
|||||||
self.project_id as ProjectId,
|
self.project_id as ProjectId,
|
||||||
category as CategoryId,
|
category as CategoryId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Project::update_game_versions(self.project_id, &mut *transaction)
|
Project::update_game_versions(self.project_id, &mut *transaction).await?;
|
||||||
.await?;
|
|
||||||
Project::update_loaders(self.project_id, &mut *transaction).await?;
|
Project::update_loaders(self.project_id, &mut *transaction).await?;
|
||||||
|
|
||||||
Ok(self.project_id)
|
Ok(self.project_id)
|
||||||
@@ -307,8 +306,7 @@ impl Project {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let project_ids_parsed: Vec<i64> =
|
let project_ids_parsed: Vec<i64> = project_ids.iter().map(|x| x.0).collect();
|
||||||
project_ids.iter().map(|x| x.0).collect();
|
|
||||||
let projects = sqlx::query!(
|
let projects = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id, project_type, title, description, downloads, follows,
|
SELECT id, project_type, title, description, downloads, follows,
|
||||||
@@ -342,12 +340,8 @@ impl Project {
|
|||||||
license_url: m.license_url,
|
license_url: m.license_url,
|
||||||
discord_url: m.discord_url,
|
discord_url: m.discord_url,
|
||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: ProjectStatus::from_str(
|
status: ProjectStatus::from_str(&m.status),
|
||||||
&m.status,
|
requested_status: m.requested_status.map(|x| ProjectStatus::from_str(&x)),
|
||||||
),
|
|
||||||
requested_status: m.requested_status.map(|x| ProjectStatus::from_str(
|
|
||||||
&x,
|
|
||||||
)),
|
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: m.license,
|
license: m.license,
|
||||||
slug: m.slug,
|
slug: m.slug,
|
||||||
@@ -402,11 +396,7 @@ impl Project {
|
|||||||
|
|
||||||
if let Some(thread_id) = thread_id {
|
if let Some(thread_id) = thread_id {
|
||||||
if let Some(id) = thread_id.thread_id {
|
if let Some(id) = thread_id.thread_id {
|
||||||
crate::database::models::Thread::remove_full(
|
crate::database::models::Thread::remove_full(ThreadId(id), transaction).await?;
|
||||||
ThreadId(id),
|
|
||||||
transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,23 +585,18 @@ impl Project {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let id_option =
|
let id_option = crate::models::ids::base62_impl::parse_base62(slug_or_project_id).ok();
|
||||||
crate::models::ids::base62_impl::parse_base62(slug_or_project_id)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let mut project =
|
let mut project = Project::get(ProjectId(id as i64), executor).await?;
|
||||||
Project::get(ProjectId(id as i64), executor).await?;
|
|
||||||
|
|
||||||
if project.is_none() {
|
if project.is_none() {
|
||||||
project = Project::get_from_slug(slug_or_project_id, executor)
|
project = Project::get_from_slug(slug_or_project_id, executor).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(project)
|
Ok(project)
|
||||||
} else {
|
} else {
|
||||||
let project =
|
let project = Project::get_from_slug(slug_or_project_id, executor).await?;
|
||||||
Project::get_from_slug(slug_or_project_id, executor).await?;
|
|
||||||
|
|
||||||
Ok(project)
|
Ok(project)
|
||||||
}
|
}
|
||||||
@@ -624,25 +609,18 @@ impl Project {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let id_option =
|
let id_option = crate::models::ids::base62_impl::parse_base62(slug_or_project_id).ok();
|
||||||
crate::models::ids::base62_impl::parse_base62(slug_or_project_id)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let mut project =
|
let mut project = Project::get_full(ProjectId(id as i64), executor).await?;
|
||||||
Project::get_full(ProjectId(id as i64), executor).await?;
|
|
||||||
|
|
||||||
if project.is_none() {
|
if project.is_none() {
|
||||||
project =
|
project = Project::get_full_from_slug(slug_or_project_id, executor).await?;
|
||||||
Project::get_full_from_slug(slug_or_project_id, executor)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(project)
|
Ok(project)
|
||||||
} else {
|
} else {
|
||||||
let project =
|
let project = Project::get_full_from_slug(slug_or_project_id, executor).await?;
|
||||||
Project::get_full_from_slug(slug_or_project_id, executor)
|
|
||||||
.await?;
|
|
||||||
Ok(project)
|
Ok(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -668,8 +646,7 @@ impl Project {
|
|||||||
{
|
{
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
let project_ids_parsed: Vec<i64> =
|
let project_ids_parsed: Vec<i64> = project_ids.iter().map(|x| x.0).collect();
|
||||||
project_ids.iter().map(|x| x.0).collect();
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
||||||
|
|||||||
@@ -58,10 +58,7 @@ impl Report {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get<'a, E>(
|
pub async fn get<'a, E>(id: ReportId, exec: E) -> Result<Option<QueryReport>, sqlx::Error>
|
||||||
id: ReportId,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<QueryReport>, sqlx::Error>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -79,8 +76,7 @@ impl Report {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let report_ids_parsed: Vec<i64> =
|
let report_ids_parsed: Vec<i64> = report_ids.iter().map(|x| x.0).collect();
|
||||||
report_ids.iter().map(|x| x.0).collect();
|
|
||||||
let reports = sqlx::query!(
|
let reports = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed
|
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created, r.thread_id, r.closed
|
||||||
@@ -141,11 +137,7 @@ impl Report {
|
|||||||
|
|
||||||
if let Some(thread_id) = thread_id {
|
if let Some(thread_id) = thread_id {
|
||||||
if let Some(id) = thread_id.thread_id {
|
if let Some(id) = thread_id.thread_id {
|
||||||
crate::database::models::Thread::remove_full(
|
crate::database::models::Thread::remove_full(ThreadId(id), transaction).await?;
|
||||||
ThreadId(id),
|
|
||||||
transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ impl TeamBuilder {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for member in self.members {
|
for member in self.members {
|
||||||
let team_member_id =
|
let team_member_id = generate_team_member_id(&mut *transaction).await?;
|
||||||
generate_team_member_id(&mut *transaction).await?;
|
|
||||||
let team_member = TeamMember {
|
let team_member = TeamMember {
|
||||||
id: team_member_id,
|
id: team_member_id,
|
||||||
team_id,
|
team_id,
|
||||||
@@ -224,16 +223,16 @@ impl TeamMember {
|
|||||||
.fetch_many(executor)
|
.fetch_many(executor)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
if let Some(m) = e.right() {
|
if let Some(m) = e.right() {
|
||||||
Ok(Some(Ok(TeamMember {
|
Ok(Some(Ok(TeamMember {
|
||||||
id: TeamMemberId(m.id),
|
id: TeamMemberId(m.id),
|
||||||
team_id: TeamId(m.team_id),
|
team_id: TeamId(m.team_id),
|
||||||
user_id,
|
user_id,
|
||||||
role: m.role,
|
role: m.role,
|
||||||
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
||||||
accepted: m.accepted,
|
accepted: m.accepted,
|
||||||
payouts_split: m.payouts_split,
|
payouts_split: m.payouts_split,
|
||||||
ordering: m.ordering,
|
ordering: m.ordering,
|
||||||
})))
|
})))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -275,8 +274,7 @@ impl TeamMember {
|
|||||||
team_id: id,
|
team_id: id,
|
||||||
user_id,
|
user_id,
|
||||||
role: m.role,
|
role: m.role,
|
||||||
permissions: Permissions::from_bits(m.permissions as u64)
|
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
accepted: m.accepted,
|
accepted: m.accepted,
|
||||||
payouts_split: m.payouts_split,
|
payouts_split: m.payouts_split,
|
||||||
ordering: m.ordering,
|
ordering: m.ordering,
|
||||||
@@ -448,8 +446,7 @@ impl TeamMember {
|
|||||||
team_id: TeamId(m.team_id),
|
team_id: TeamId(m.team_id),
|
||||||
user_id,
|
user_id,
|
||||||
role: m.role,
|
role: m.role,
|
||||||
permissions: Permissions::from_bits(m.permissions as u64)
|
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
accepted: m.accepted,
|
accepted: m.accepted,
|
||||||
payouts_split: m.payouts_split,
|
payouts_split: m.payouts_split,
|
||||||
ordering: m.ordering,
|
ordering: m.ordering,
|
||||||
@@ -486,8 +483,7 @@ impl TeamMember {
|
|||||||
team_id: TeamId(m.team_id),
|
team_id: TeamId(m.team_id),
|
||||||
user_id,
|
user_id,
|
||||||
role: m.role,
|
role: m.role,
|
||||||
permissions: Permissions::from_bits(m.permissions as u64)
|
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
accepted: m.accepted,
|
accepted: m.accepted,
|
||||||
payouts_split: m.payouts_split,
|
payouts_split: m.payouts_split,
|
||||||
ordering: m.ordering,
|
ordering: m.ordering,
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ impl ThreadMessageBuilder {
|
|||||||
&self,
|
&self,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<ThreadMessageId, DatabaseError> {
|
) -> Result<ThreadMessageId, DatabaseError> {
|
||||||
let thread_message_id =
|
let thread_message_id = generate_thread_message_id(&mut *transaction).await?;
|
||||||
generate_thread_message_id(&mut *transaction).await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -76,14 +75,16 @@ impl ThreadBuilder {
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO threads (
|
INSERT INTO threads (
|
||||||
id, thread_type
|
id, thread_type, report_id, project_id
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2
|
$1, $2, $3, $4
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
thread_id as ThreadId,
|
thread_id as ThreadId,
|
||||||
self.type_.as_str(),
|
self.type_.as_str(),
|
||||||
|
self.report_id.map(|x| x.0),
|
||||||
|
self.project_id.map(|x| x.0),
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -110,10 +111,7 @@ impl ThreadBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Thread {
|
impl Thread {
|
||||||
pub async fn get<'a, E>(
|
pub async fn get<'a, E>(id: ThreadId, exec: E) -> Result<Option<Thread>, sqlx::Error>
|
||||||
id: ThreadId,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<Thread>, sqlx::Error>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -131,8 +129,7 @@ impl Thread {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let thread_ids_parsed: Vec<i64> =
|
let thread_ids_parsed: Vec<i64> = thread_ids.iter().map(|x| x.0).collect();
|
||||||
thread_ids.iter().map(|x| x.0).collect();
|
|
||||||
let threads = sqlx::query!(
|
let threads = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT t.id, t.thread_type, t.show_in_mod_inbox, t.project_id, t.report_id,
|
SELECT t.id, t.thread_type, t.show_in_mod_inbox, t.project_id, t.report_id,
|
||||||
@@ -230,8 +227,7 @@ impl ThreadMessage {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let message_ids_parsed: Vec<i64> =
|
let message_ids_parsed: Vec<i64> = message_ids.iter().map(|x| x.0).collect();
|
||||||
message_ids.iter().map(|x| x.0).collect();
|
|
||||||
let messages = sqlx::query!(
|
let messages = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT tm.id, tm.author_id, tm.thread_id, tm.body, tm.created
|
SELECT tm.id, tm.author_id, tm.thread_id, tm.body, tm.created
|
||||||
@@ -246,8 +242,7 @@ impl ThreadMessage {
|
|||||||
id: ThreadMessageId(x.id),
|
id: ThreadMessageId(x.id),
|
||||||
thread_id: ThreadId(x.thread_id),
|
thread_id: ThreadId(x.thread_id),
|
||||||
author_id: x.author_id.map(UserId),
|
author_id: x.author_id.map(UserId),
|
||||||
body: serde_json::from_value(x.body)
|
body: serde_json::from_value(x.body).unwrap_or(MessageBody::Deleted),
|
||||||
.unwrap_or(MessageBody::Deleted),
|
|
||||||
created: x.created,
|
created: x.created,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -268,8 +263,7 @@ impl ThreadMessage {
|
|||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id as ThreadMessageId,
|
id as ThreadMessageId,
|
||||||
serde_json::to_value(MessageBody::Deleted)
|
serde_json::to_value(MessageBody::Deleted).unwrap_or(serde_json::json!({}))
|
||||||
.unwrap_or(serde_json::json!({}))
|
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -50,10 +50,7 @@ impl User {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub async fn get<'a, 'b, E>(
|
pub async fn get<'a, 'b, E>(id: UserId, executor: E) -> Result<Option<Self>, sqlx::error::Error>
|
||||||
id: UserId,
|
|
||||||
executor: E,
|
|
||||||
) -> Result<Option<Self>, sqlx::error::Error>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -95,12 +92,9 @@ impl User {
|
|||||||
bio: row.bio,
|
bio: row.bio,
|
||||||
created: row.created,
|
created: row.created,
|
||||||
role: row.role,
|
role: row.role,
|
||||||
badges: Badges::from_bits(row.badges as u64)
|
badges: Badges::from_bits(row.badges as u64).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
balance: row.balance,
|
balance: row.balance,
|
||||||
payout_wallet: row
|
payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||||
.payout_wallet
|
|
||||||
.map(|x| RecipientWallet::from_string(&x)),
|
|
||||||
payout_wallet_type: row
|
payout_wallet_type: row
|
||||||
.payout_wallet_type
|
.payout_wallet_type
|
||||||
.map(|x| RecipientType::from_string(&x)),
|
.map(|x| RecipientType::from_string(&x)),
|
||||||
@@ -144,12 +138,9 @@ impl User {
|
|||||||
bio: row.bio,
|
bio: row.bio,
|
||||||
created: row.created,
|
created: row.created,
|
||||||
role: row.role,
|
role: row.role,
|
||||||
badges: Badges::from_bits(row.badges as u64)
|
badges: Badges::from_bits(row.badges as u64).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
balance: row.balance,
|
balance: row.balance,
|
||||||
payout_wallet: row
|
payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||||
.payout_wallet
|
|
||||||
.map(|x| RecipientWallet::from_string(&x)),
|
|
||||||
payout_wallet_type: row
|
payout_wallet_type: row
|
||||||
.payout_wallet_type
|
.payout_wallet_type
|
||||||
.map(|x| RecipientType::from_string(&x)),
|
.map(|x| RecipientType::from_string(&x)),
|
||||||
@@ -160,10 +151,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_many<'a, E>(
|
pub async fn get_many<'a, E>(user_ids: &[UserId], exec: E) -> Result<Vec<User>, sqlx::Error>
|
||||||
user_ids: &[UserId],
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Vec<User>, sqlx::Error>
|
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -196,12 +184,8 @@ impl User {
|
|||||||
role: u.role,
|
role: u.role,
|
||||||
badges: Badges::from_bits(u.badges as u64).unwrap_or_default(),
|
badges: Badges::from_bits(u.badges as u64).unwrap_or_default(),
|
||||||
balance: u.balance,
|
balance: u.balance,
|
||||||
payout_wallet: u
|
payout_wallet: u.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||||
.payout_wallet
|
payout_wallet_type: u.payout_wallet_type.map(|x| RecipientType::from_string(&x)),
|
||||||
.map(|x| RecipientWallet::from_string(&x)),
|
|
||||||
payout_wallet_type: u
|
|
||||||
.payout_wallet_type
|
|
||||||
.map(|x| RecipientType::from_string(&x)),
|
|
||||||
payout_address: u.payout_address,
|
payout_address: u.payout_address,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -384,11 +368,8 @@ impl User {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for project_id in projects {
|
for project_id in projects {
|
||||||
let _result = super::project_item::Project::remove_full(
|
let _result =
|
||||||
project_id,
|
super::project_item::Project::remove_full(project_id, transaction).await?;
|
||||||
transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifications: Vec<i64> = sqlx::query!(
|
let notifications: Vec<i64> = sqlx::query!(
|
||||||
@@ -489,8 +470,7 @@ impl User {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let id_option =
|
let id_option = crate::models::ids::base62_impl::parse_base62(username_or_id).ok();
|
||||||
crate::models::ids::base62_impl::parse_base62(username_or_id).ok();
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let id = UserId(id as i64);
|
let id = UserId(id as i64);
|
||||||
|
|||||||
@@ -499,8 +499,7 @@ impl Version {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
let version_ids_parsed: Vec<i64> =
|
let version_ids_parsed: Vec<i64> = version_ids.iter().map(|x| x.0).collect();
|
||||||
version_ids.iter().map(|x| x.0).collect();
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||||
@@ -648,8 +647,7 @@ impl Version {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let project_id_opt =
|
let project_id_opt = parse_base62(project_id_or_slug).ok().map(|x| x as i64);
|
||||||
parse_base62(project_id_or_slug).ok().map(|x| x as i64);
|
|
||||||
let id_opt = parse_base62(slug).ok().map(|x| x as i64);
|
let id_opt = parse_base62(slug).ok().map(|x| x as i64);
|
||||||
let id = sqlx::query!(
|
let id = sqlx::query!(
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
pub async fn connect() -> Result<PgPool, sqlx::Error> {
|
pub async fn connect() -> Result<PgPool, sqlx::Error> {
|
||||||
info!("Initializing database connection");
|
info!("Initializing database connection");
|
||||||
let database_url =
|
let database_url = dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env");
|
||||||
dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env");
|
|
||||||
let pool = PgPoolOptions::new()
|
let pool = PgPoolOptions::new()
|
||||||
.min_connections(
|
.min_connections(
|
||||||
dotenvy::var("DATABASE_MIN_CONNECTIONS")
|
dotenvy::var("DATABASE_MIN_CONNECTIONS")
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ pub struct BackblazeHost {
|
|||||||
|
|
||||||
impl BackblazeHost {
|
impl BackblazeHost {
|
||||||
pub async fn new(key_id: &str, key: &str, bucket_id: &str) -> Self {
|
pub async fn new(key_id: &str, key: &str, bucket_id: &str) -> Self {
|
||||||
let authorization_data =
|
let authorization_data = authorization::authorize_account(key_id, key).await.unwrap();
|
||||||
authorization::authorize_account(key_id, key).await.unwrap();
|
let upload_url_data = authorization::get_upload_url(&authorization_data, bucket_id)
|
||||||
let upload_url_data =
|
.await
|
||||||
authorization::get_upload_url(&authorization_data, bucket_id)
|
.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
BackblazeHost {
|
BackblazeHost {
|
||||||
upload_url_data,
|
upload_url_data,
|
||||||
@@ -40,13 +38,8 @@ impl FileHost for BackblazeHost {
|
|||||||
) -> Result<UploadFileData, FileHostingError> {
|
) -> Result<UploadFileData, FileHostingError> {
|
||||||
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
||||||
|
|
||||||
let upload_data = upload::upload_file(
|
let upload_data =
|
||||||
&self.upload_url_data,
|
upload::upload_file(&self.upload_url_data, content_type, file_name, file_bytes).await?;
|
||||||
content_type,
|
|
||||||
file_name,
|
|
||||||
file_bytes,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(UploadFileData {
|
Ok(UploadFileData {
|
||||||
file_id: upload_data.file_id,
|
file_id: upload_data.file_id,
|
||||||
file_name: upload_data.file_name,
|
file_name: upload_data.file_name,
|
||||||
@@ -81,12 +74,8 @@ impl FileHost for BackblazeHost {
|
|||||||
file_id: &str,
|
file_id: &str,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
) -> Result<DeleteFileData, FileHostingError> {
|
) -> Result<DeleteFileData, FileHostingError> {
|
||||||
let delete_data = delete::delete_file_version(
|
let delete_data =
|
||||||
&self.authorization_data,
|
delete::delete_file_version(&self.authorization_data, file_id, file_name).await?;
|
||||||
file_id,
|
|
||||||
file_name,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(DeleteFileData {
|
Ok(DeleteFileData {
|
||||||
file_id: delete_data.file_id,
|
file_id: delete_data.file_id,
|
||||||
file_name: delete_data.file_name,
|
file_name: delete_data.file_name,
|
||||||
@@ -94,9 +83,7 @@ impl FileHost for BackblazeHost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_response<T>(
|
pub async fn process_response<T>(response: Response) -> Result<T, FileHostingError>
|
||||||
response: Response,
|
|
||||||
) -> Result<T, FileHostingError>
|
|
||||||
where
|
where
|
||||||
T: for<'de> Deserialize<'de>,
|
T: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,13 +56,7 @@ pub async fn get_upload_url(
|
|||||||
bucket_id: &str,
|
bucket_id: &str,
|
||||||
) -> Result<UploadUrlData, FileHostingError> {
|
) -> Result<UploadUrlData, FileHostingError> {
|
||||||
let response = reqwest::Client::new()
|
let response = reqwest::Client::new()
|
||||||
.post(
|
.post(&format!("{}/b2api/v2/b2_get_upload_url", authorization_data.api_url).to_string())
|
||||||
&format!(
|
|
||||||
"{}/b2api/v2/b2_get_upload_url",
|
|
||||||
authorization_data.api_url
|
|
||||||
)
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||||
.header(
|
.header(
|
||||||
reqwest::header::AUTHORIZATION,
|
reqwest::header::AUTHORIZATION,
|
||||||
|
|||||||
@@ -20,12 +20,9 @@ impl FileHost for MockHost {
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
file_bytes: Bytes,
|
file_bytes: Bytes,
|
||||||
) -> Result<UploadFileData, FileHostingError> {
|
) -> Result<UploadFileData, FileHostingError> {
|
||||||
let path =
|
let path = std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap())
|
||||||
std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap())
|
.join(file_name.replace("../", ""));
|
||||||
.join(file_name.replace("../", ""));
|
std::fs::create_dir_all(path.parent().ok_or(FileHostingError::InvalidFilename)?)?;
|
||||||
std::fs::create_dir_all(
|
|
||||||
path.parent().ok_or(FileHostingError::InvalidFilename)?,
|
|
||||||
)?;
|
|
||||||
let content_sha1 = sha1::Sha1::from(&file_bytes).hexdigest();
|
let content_sha1 = sha1::Sha1::from(&file_bytes).hexdigest();
|
||||||
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
||||||
|
|
||||||
@@ -47,9 +44,8 @@ impl FileHost for MockHost {
|
|||||||
file_id: &str,
|
file_id: &str,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
) -> Result<DeleteFileData, FileHostingError> {
|
) -> Result<DeleteFileData, FileHostingError> {
|
||||||
let path =
|
let path = std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap())
|
||||||
std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap())
|
.join(file_name.replace("../", ""));
|
||||||
.join(file_name.replace("../", ""));
|
|
||||||
std::fs::remove_file(path)?;
|
std::fs::remove_file(path)?;
|
||||||
|
|
||||||
Ok(DeleteFileData {
|
Ok(DeleteFileData {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::file_hosting::{
|
use crate::file_hosting::{DeleteFileData, FileHost, FileHostingError, UploadFileData};
|
||||||
DeleteFileData, FileHost, FileHostingError, UploadFileData,
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -33,23 +31,12 @@ impl S3Host {
|
|||||||
endpoint: url.to_string(),
|
endpoint: url.to_string(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Credentials::new(
|
Credentials::new(Some(access_token), Some(secret), None, None, None).map_err(|_| {
|
||||||
Some(access_token),
|
FileHostingError::S3Error("Error while creating credentials".to_string())
|
||||||
Some(secret),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
FileHostingError::S3Error(
|
|
||||||
"Error while creating credentials".to_string(),
|
|
||||||
)
|
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
FileHostingError::S3Error(
|
FileHostingError::S3Error("Error while creating Bucket instance".to_string())
|
||||||
"Error while creating Bucket instance".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(S3Host { bucket })
|
Ok(S3Host { bucket })
|
||||||
@@ -68,16 +55,10 @@ impl FileHost for S3Host {
|
|||||||
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
||||||
|
|
||||||
self.bucket
|
self.bucket
|
||||||
.put_object_with_content_type(
|
.put_object_with_content_type(format!("/{file_name}"), &file_bytes, content_type)
|
||||||
format!("/{file_name}"),
|
|
||||||
&file_bytes,
|
|
||||||
content_type,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
FileHostingError::S3Error(
|
FileHostingError::S3Error("Error while uploading file to S3".to_string())
|
||||||
"Error while uploading file to S3".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(UploadFileData {
|
Ok(UploadFileData {
|
||||||
@@ -101,9 +82,7 @@ impl FileHost for S3Host {
|
|||||||
.delete_object(format!("/{file_name}"))
|
.delete_object(format!("/{file_name}"))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
FileHostingError::S3Error(
|
FileHostingError::S3Error("Error while deleting file from S3".to_string())
|
||||||
"Error while deleting file from S3".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(DeleteFileData {
|
Ok(DeleteFileData {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
pub async fn test_database(
|
pub async fn test_database(postgres: web::Data<PgPool>) -> Result<(), sqlx::Error> {
|
||||||
postgres: web::Data<PgPool>,
|
|
||||||
) -> Result<(), sqlx::Error> {
|
|
||||||
let mut transaction = postgres.acquire().await?;
|
let mut transaction = postgres.acquire().await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"
|
"
|
||||||
|
|||||||
153
src/main.rs
153
src/main.rs
@@ -35,8 +35,7 @@ pub struct Pepper {
|
|||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
.init();
|
|
||||||
|
|
||||||
if check_env_vars() {
|
if check_env_vars() {
|
||||||
error!("Some environment variables are missing!");
|
error!("Some environment variables are missing!");
|
||||||
@@ -75,40 +74,37 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.await
|
.await
|
||||||
.expect("Database connection failed");
|
.expect("Database connection failed");
|
||||||
|
|
||||||
let storage_backend =
|
let storage_backend = dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
||||||
dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
|
||||||
|
|
||||||
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
|
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> = match storage_backend.as_str() {
|
||||||
match storage_backend.as_str() {
|
"backblaze" => Arc::new(
|
||||||
"backblaze" => Arc::new(
|
file_hosting::BackblazeHost::new(
|
||||||
file_hosting::BackblazeHost::new(
|
&dotenvy::var("BACKBLAZE_KEY_ID").unwrap(),
|
||||||
&dotenvy::var("BACKBLAZE_KEY_ID").unwrap(),
|
&dotenvy::var("BACKBLAZE_KEY").unwrap(),
|
||||||
&dotenvy::var("BACKBLAZE_KEY").unwrap(),
|
&dotenvy::var("BACKBLAZE_BUCKET_ID").unwrap(),
|
||||||
&dotenvy::var("BACKBLAZE_BUCKET_ID").unwrap(),
|
)
|
||||||
)
|
.await,
|
||||||
.await,
|
),
|
||||||
),
|
"s3" => Arc::new(
|
||||||
"s3" => Arc::new(
|
S3Host::new(
|
||||||
S3Host::new(
|
&dotenvy::var("S3_BUCKET_NAME").unwrap(),
|
||||||
&dotenvy::var("S3_BUCKET_NAME").unwrap(),
|
&dotenvy::var("S3_REGION").unwrap(),
|
||||||
&dotenvy::var("S3_REGION").unwrap(),
|
&dotenvy::var("S3_URL").unwrap(),
|
||||||
&dotenvy::var("S3_URL").unwrap(),
|
&dotenvy::var("S3_ACCESS_TOKEN").unwrap(),
|
||||||
&dotenvy::var("S3_ACCESS_TOKEN").unwrap(),
|
&dotenvy::var("S3_SECRET").unwrap(),
|
||||||
&dotenvy::var("S3_SECRET").unwrap(),
|
)
|
||||||
)
|
.unwrap(),
|
||||||
.unwrap(),
|
),
|
||||||
),
|
"local" => Arc::new(file_hosting::MockHost::new()),
|
||||||
"local" => Arc::new(file_hosting::MockHost::new()),
|
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
||||||
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let mut scheduler = scheduler::Scheduler::new();
|
let mut scheduler = scheduler::Scheduler::new();
|
||||||
|
|
||||||
// The interval in seconds at which the local database is indexed
|
// The interval in seconds at which the local database is indexed
|
||||||
// for searching. Defaults to 1 hour if unset.
|
// for searching. Defaults to 1 hour if unset.
|
||||||
let local_index_interval = std::time::Duration::from_secs(
|
let local_index_interval =
|
||||||
parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600),
|
std::time::Duration::from_secs(parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600));
|
||||||
);
|
|
||||||
|
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let search_config_ref = search_config.clone();
|
let search_config_ref = search_config.clone();
|
||||||
@@ -118,8 +114,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
async move {
|
async move {
|
||||||
info!("Indexing local database");
|
info!("Indexing local database");
|
||||||
let settings = IndexingSettings { index_local: true };
|
let settings = IndexingSettings { index_local: true };
|
||||||
let result =
|
let result = index_projects(pool_ref, settings, &search_config_ref).await;
|
||||||
index_projects(pool_ref, settings, &search_config_ref).await;
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
warn!("Local project indexing failed: {:?}", e);
|
warn!("Local project indexing failed: {:?}", e);
|
||||||
}
|
}
|
||||||
@@ -170,14 +165,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Scheduled.as_str(),
|
crate::models::projects::ProjectStatus::Scheduled.as_str(),
|
||||||
)
|
)
|
||||||
.execute(&pool_ref)
|
.execute(&pool_ref)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Err(e) = projects_results {
|
if let Err(e) = projects_results {
|
||||||
warn!(
|
warn!("Syncing scheduled releases for projects failed: {:?}", e);
|
||||||
"Syncing scheduled releases for projects failed: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let versions_results = sqlx::query!(
|
let versions_results = sqlx::query!(
|
||||||
@@ -188,21 +180,18 @@ async fn main() -> std::io::Result<()> {
|
|||||||
",
|
",
|
||||||
crate::models::projects::VersionStatus::Scheduled.as_str(),
|
crate::models::projects::VersionStatus::Scheduled.as_str(),
|
||||||
)
|
)
|
||||||
.execute(&pool_ref)
|
.execute(&pool_ref)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Err(e) = versions_results {
|
if let Err(e) = versions_results {
|
||||||
warn!(
|
warn!("Syncing scheduled releases for versions failed: {:?}", e);
|
||||||
"Syncing scheduled releases for versions failed: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Finished releasing scheduled versions/projects");
|
info!("Finished releasing scheduled versions/projects");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reminding moderators to review projects which have been in the queue longer than 24hr
|
// Reminding moderators to review projects which have been in the queue longer than 40hr
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
let webhook_message_sent = Arc::new(Mutex::new(Vec::<(
|
let webhook_message_sent = Arc::new(Mutex::new(Vec::<(
|
||||||
database::models::ProjectId,
|
database::models::ProjectId,
|
||||||
@@ -212,7 +201,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
scheduler.run(std::time::Duration::from_secs(10 * 60), move || {
|
scheduler.run(std::time::Duration::from_secs(10 * 60), move || {
|
||||||
let pool_ref = pool_ref.clone();
|
let pool_ref = pool_ref.clone();
|
||||||
let webhook_message_sent_ref = webhook_message_sent.clone();
|
let webhook_message_sent_ref = webhook_message_sent.clone();
|
||||||
info!("Checking reviewed projects submitted more than 24hrs ago");
|
info!("Checking reviewed projects submitted more than 40hrs ago");
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let do_steps = async {
|
let do_steps = async {
|
||||||
@@ -221,7 +210,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let project_ids = sqlx::query!(
|
let project_ids = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id FROM mods
|
SELECT id FROM mods
|
||||||
WHERE status = $1 AND queued < NOW() - INTERVAL '1 day'
|
WHERE status = $1 AND queued < NOW() - INTERVAL '40 hours'
|
||||||
ORDER BY updated ASC
|
ORDER BY updated ASC
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Processing.as_str(),
|
crate::models::projects::ProjectStatus::Processing.as_str(),
|
||||||
@@ -247,7 +236,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
project.into(),
|
project.into(),
|
||||||
&pool_ref,
|
&pool_ref,
|
||||||
webhook_url,
|
webhook_url,
|
||||||
Some("<@&783155186491195394> This project has been in the queue for over 24 hours!".to_string()),
|
Some("<@&783155186491195394> This project has been in the queue for over 40 hours!".to_string()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
@@ -261,12 +250,12 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
if let Err(e) = do_steps.await {
|
if let Err(e) = do_steps.await {
|
||||||
warn!(
|
warn!(
|
||||||
"Checking reviewed projects submitted more than 24hrs ago failed: {:?}",
|
"Checking reviewed projects submitted more than 40hrs ago failed: {:?}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Finished checking reviewed projects submitted more than 24hrs ago");
|
info!("Finished checking reviewed projects submitted more than 40hrs ago");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -291,8 +280,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let ip_salt = Pepper {
|
let ip_salt = Pepper {
|
||||||
pepper: models::ids::Base62Id(models::ids::random_base62(11))
|
pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(),
|
||||||
.to_string(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new()));
|
let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new()));
|
||||||
@@ -317,48 +305,43 @@ async fn main() -> std::io::Result<()> {
|
|||||||
RateLimiter::new(MemoryStoreActor::from(store.clone()).start())
|
RateLimiter::new(MemoryStoreActor::from(store.clone()).start())
|
||||||
.with_identifier(|req| {
|
.with_identifier(|req| {
|
||||||
let connection_info = req.connection_info();
|
let connection_info = req.connection_info();
|
||||||
let ip = String::from(
|
let ip =
|
||||||
if parse_var("CLOUDFLARE_INTEGRATION")
|
String::from(if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||||
.unwrap_or(false)
|
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||||
{
|
header.to_str().map_err(|_| ARError::Identification)?
|
||||||
if let Some(header) =
|
|
||||||
req.headers().get("CF-Connecting-IP")
|
|
||||||
{
|
|
||||||
header
|
|
||||||
.to_str()
|
|
||||||
.map_err(|_| ARError::Identification)?
|
|
||||||
} else {
|
} else {
|
||||||
connection_info
|
connection_info.peer_addr().ok_or(ARError::Identification)?
|
||||||
.peer_addr()
|
|
||||||
.ok_or(ARError::Identification)?
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
connection_info
|
connection_info.peer_addr().ok_or(ARError::Identification)?
|
||||||
.peer_addr()
|
});
|
||||||
.ok_or(ARError::Identification)?
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(ip)
|
Ok(ip)
|
||||||
})
|
})
|
||||||
.with_interval(std::time::Duration::from_secs(60))
|
.with_interval(std::time::Duration::from_secs(60))
|
||||||
.with_max_requests(300)
|
.with_max_requests(300)
|
||||||
.with_ignore_key(
|
.with_ignore_key(dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok()),
|
||||||
dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok(),
|
)
|
||||||
),
|
.app_data(
|
||||||
|
web::FormConfig::default().error_handler(|err, _req| {
|
||||||
|
routes::ApiError::Validation(err.to_string()).into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.app_data(
|
||||||
|
web::PathConfig::default().error_handler(|err, _req| {
|
||||||
|
routes::ApiError::Validation(err.to_string()).into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.app_data(
|
||||||
|
web::QueryConfig::default().error_handler(|err, _req| {
|
||||||
|
routes::ApiError::Validation(err.to_string()).into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.app_data(
|
||||||
|
web::JsonConfig::default().error_handler(|err, _req| {
|
||||||
|
routes::ApiError::Validation(err.to_string()).into()
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.app_data(web::FormConfig::default().error_handler(|err, _req| {
|
|
||||||
routes::ApiError::Validation(err.to_string()).into()
|
|
||||||
}))
|
|
||||||
.app_data(web::PathConfig::default().error_handler(|err, _req| {
|
|
||||||
routes::ApiError::Validation(err.to_string()).into()
|
|
||||||
}))
|
|
||||||
.app_data(web::QueryConfig::default().error_handler(|err, _req| {
|
|
||||||
routes::ApiError::Validation(err.to_string()).into()
|
|
||||||
}))
|
|
||||||
.app_data(web::JsonConfig::default().error_handler(|err, _req| {
|
|
||||||
routes::ApiError::Validation(err.to_string()).into()
|
|
||||||
}))
|
|
||||||
.app_data(web::Data::new(pool.clone()))
|
.app_data(web::Data::new(pool.clone()))
|
||||||
.app_data(web::Data::new(file_host.clone()))
|
.app_data(web::Data::new(file_host.clone()))
|
||||||
.app_data(web::Data::new(search_config.clone()))
|
.app_data(web::Data::new(search_config.clone()))
|
||||||
|
|||||||
@@ -131,10 +131,7 @@ pub mod base62_impl {
|
|||||||
impl<'de> Visitor<'de> for Base62Visitor {
|
impl<'de> Visitor<'de> for Base62Visitor {
|
||||||
type Value = Base62Id;
|
type Value = Base62Id;
|
||||||
|
|
||||||
fn expecting(
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
&self,
|
|
||||||
formatter: &mut std::fmt::Formatter,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a base62 string id")
|
formatter.write_str("a base62 string id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +187,7 @@ pub mod base62_impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We don't want this panicking or wrapping on integer overflow
|
// We don't want this panicking or wrapping on integer overflow
|
||||||
if let Some(n) =
|
if let Some(n) = num.checked_mul(62).and_then(|n| n.checked_add(next_digit)) {
|
||||||
num.checked_mul(62).and_then(|n| n.checked_add(next_digit))
|
|
||||||
{
|
|
||||||
num = n;
|
num = n;
|
||||||
} else {
|
} else {
|
||||||
return Err(DecodingError::Overflow);
|
return Err(DecodingError::Overflow);
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ use super::ids::Base62Id;
|
|||||||
use super::users::UserId;
|
use super::users::UserId;
|
||||||
use crate::database::models::notification_item::Notification as DBNotification;
|
use crate::database::models::notification_item::Notification as DBNotification;
|
||||||
use crate::database::models::notification_item::NotificationAction as DBNotificationAction;
|
use crate::database::models::notification_item::NotificationAction as DBNotificationAction;
|
||||||
use crate::models::ids::{
|
use crate::models::ids::{ProjectId, ReportId, TeamId, ThreadId, ThreadMessageId, VersionId};
|
||||||
ProjectId, ReportId, TeamId, ThreadId, ThreadMessageId, VersionId,
|
|
||||||
};
|
|
||||||
use crate::models::projects::ProjectStatus;
|
use crate::models::projects::ProjectStatus;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -91,27 +89,18 @@ impl From<DBNotification> for Notification {
|
|||||||
} => (
|
} => (
|
||||||
Some("team_invite".to_string()),
|
Some("team_invite".to_string()),
|
||||||
"You have been invited to join a team!".to_string(),
|
"You have been invited to join a team!".to_string(),
|
||||||
format!(
|
format!("An invite has been sent for you to be {} of a team", role),
|
||||||
"An invite has been sent for you to be {} of a team",
|
|
||||||
role
|
|
||||||
),
|
|
||||||
format!("/project/{}", project_id),
|
format!("/project/{}", project_id),
|
||||||
vec![
|
vec![
|
||||||
NotificationAction {
|
NotificationAction {
|
||||||
title: "Accept".to_string(),
|
title: "Accept".to_string(),
|
||||||
action_route: (
|
action_route: ("POST".to_string(), format!("team/{team_id}/join")),
|
||||||
"POST".to_string(),
|
|
||||||
format!("team/{team_id}/join"),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
NotificationAction {
|
NotificationAction {
|
||||||
title: "Deny".to_string(),
|
title: "Deny".to_string(),
|
||||||
action_route: (
|
action_route: (
|
||||||
"DELETE".to_string(),
|
"DELETE".to_string(),
|
||||||
format!(
|
format!("team/{team_id}/members/{}", UserId::from(notif.user_id)),
|
||||||
"team/{team_id}/members/{}",
|
|
||||||
UserId::from(notif.user_id)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ pub struct PackFile {
|
|||||||
pub file_size: u32,
|
pub file_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_download_url(
|
fn validate_download_url(values: &[String]) -> Result<(), validator::ValidationError> {
|
||||||
values: &[String],
|
|
||||||
) -> Result<(), validator::ValidationError> {
|
|
||||||
for value in values {
|
for value in values {
|
||||||
let url = url::Url::parse(value)
|
let url = url::Url::parse(value)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -42,8 +40,7 @@ fn validate_download_url(
|
|||||||
return Err(validator::ValidationError::new("invalid URL"));
|
return Err(validator::ValidationError::new("invalid URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS")
|
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").unwrap_or_default();
|
||||||
.unwrap_or_default();
|
|
||||||
if !domains.contains(
|
if !domains.contains(
|
||||||
&url.domain()
|
&url.domain()
|
||||||
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
||||||
|
|||||||
@@ -534,16 +534,10 @@ impl From<QueryVersion> for Version {
|
|||||||
version_id: d.version_id.map(|i| VersionId(i.0 as u64)),
|
version_id: d.version_id.map(|i| VersionId(i.0 as u64)),
|
||||||
project_id: d.project_id.map(|i| ProjectId(i.0 as u64)),
|
project_id: d.project_id.map(|i| ProjectId(i.0 as u64)),
|
||||||
file_name: d.file_name,
|
file_name: d.file_name,
|
||||||
dependency_type: DependencyType::from_str(
|
dependency_type: DependencyType::from_str(d.dependency_type.as_str()),
|
||||||
d.dependency_type.as_str(),
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
game_versions: data
|
game_versions: data.game_versions.into_iter().map(GameVersion).collect(),
|
||||||
.game_versions
|
|
||||||
.into_iter()
|
|
||||||
.map(GameVersion)
|
|
||||||
.collect(),
|
|
||||||
loaders: data.loaders.into_iter().map(Loader).collect(),
|
loaders: data.loaders.into_iter().map(Loader).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,36 +68,25 @@ impl PayoutsQueue {
|
|||||||
.form(&form)
|
.form(&form)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ApiError::Payments("Error while authenticating with PayPal".to_string()))?
|
||||||
ApiError::Payments(
|
|
||||||
"Error while authenticating with PayPal".to_string(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments(
|
||||||
"Error while authenticating with PayPal (deser error)"
|
"Error while authenticating with PayPal (deser error)".to_string(),
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.credential_expires =
|
self.credential_expires = Utc::now() + Duration::seconds(credential.expires_in);
|
||||||
Utc::now() + Duration::seconds(credential.expires_in);
|
|
||||||
self.credential = credential;
|
self.credential = credential;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_payout(
|
pub async fn send_payout(&mut self, mut payout: PayoutItem) -> Result<Decimal, ApiError> {
|
||||||
&mut self,
|
|
||||||
mut payout: PayoutItem,
|
|
||||||
) -> Result<Decimal, ApiError> {
|
|
||||||
if self.credential_expires < Utc::now() {
|
if self.credential_expires < Utc::now() {
|
||||||
self.refresh_token().await.map_err(|_| {
|
self.refresh_token().await.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments("Error while authenticating with PayPal".to_string())
|
||||||
"Error while authenticating with PayPal".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +98,7 @@ impl PayoutsQueue {
|
|||||||
std::cmp::min(
|
std::cmp::min(
|
||||||
std::cmp::max(
|
std::cmp::max(
|
||||||
Decimal::ONE / Decimal::from(4),
|
Decimal::ONE / Decimal::from(4),
|
||||||
(Decimal::from(2) / Decimal::ONE_HUNDRED)
|
(Decimal::from(2) / Decimal::ONE_HUNDRED) * payout.amount.value,
|
||||||
* payout.amount.value,
|
|
||||||
),
|
),
|
||||||
Decimal::from(20),
|
Decimal::from(20),
|
||||||
)
|
)
|
||||||
@@ -151,9 +139,7 @@ impl PayoutsQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let body: PayPalError = res.json().await.map_err(|_| {
|
let body: PayPalError = res.json().await.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments("Error while registering payment in PayPal!".to_string())
|
||||||
"Error while registering payment in PayPal!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
return Err(ApiError::Payments(format!(
|
return Err(ApiError::Payments(format!(
|
||||||
@@ -190,8 +176,7 @@ impl PayoutsQueue {
|
|||||||
"Authorization",
|
"Authorization",
|
||||||
format!(
|
format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
self.credential.token_type,
|
self.credential.token_type, self.credential.access_token
|
||||||
self.credential.access_token
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.send()
|
.send()
|
||||||
@@ -199,9 +184,7 @@ impl PayoutsQueue {
|
|||||||
{
|
{
|
||||||
if let Ok(res) = res.json::<PayoutData>().await {
|
if let Ok(res) = res.json::<PayoutData>().await {
|
||||||
if let Some(data) = res.items.first() {
|
if let Some(data) = res.items.first() {
|
||||||
if (fee - data.payout_item_fee.value)
|
if (fee - data.payout_item_fee.value) > Decimal::ZERO {
|
||||||
> Decimal::ZERO
|
|
||||||
{
|
|
||||||
return Ok(fee - data.payout_item_fee.value);
|
return Ok(fee - data.payout_item_fee.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,27 +35,18 @@ impl ResponseError for ARError {
|
|||||||
reset,
|
reset,
|
||||||
} => {
|
} => {
|
||||||
let mut response = actix_web::HttpResponse::TooManyRequests();
|
let mut response = actix_web::HttpResponse::TooManyRequests();
|
||||||
response.insert_header((
|
response.insert_header(("x-ratelimit-limit", max_requests.to_string()));
|
||||||
"x-ratelimit-limit",
|
response.insert_header(("x-ratelimit-remaining", remaining.to_string()));
|
||||||
max_requests.to_string(),
|
response.insert_header(("x-ratelimit-reset", reset.to_string()));
|
||||||
));
|
|
||||||
response.insert_header((
|
|
||||||
"x-ratelimit-remaining",
|
|
||||||
remaining.to_string(),
|
|
||||||
));
|
|
||||||
response
|
|
||||||
.insert_header(("x-ratelimit-reset", reset.to_string()));
|
|
||||||
response.json(ApiError {
|
response.json(ApiError {
|
||||||
error: "ratelimit_error",
|
error: "ratelimit_error",
|
||||||
description: &self.to_string(),
|
description: &self.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => actix_web::HttpResponse::build(self.status_code()).json(
|
_ => actix_web::HttpResponse::build(self.status_code()).json(ApiError {
|
||||||
ApiError {
|
error: "ratelimit_error",
|
||||||
error: "ratelimit_error",
|
description: &self.to_string(),
|
||||||
description: &self.to_string(),
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ impl MemoryStore {
|
|||||||
pub fn with_capacity(capacity: usize) -> Self {
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
debug!("Creating new MemoryStore");
|
debug!("Creating new MemoryStore");
|
||||||
MemoryStore {
|
MemoryStore {
|
||||||
inner: Arc::new(
|
inner: Arc::new(DashMap::<String, (usize, Duration)>::with_capacity(
|
||||||
DashMap::<String, (usize, Duration)>::with_capacity(capacity),
|
capacity,
|
||||||
),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,18 +74,10 @@ impl Supervised for MemoryStoreActor {
|
|||||||
|
|
||||||
impl Handler<ActorMessage> for MemoryStoreActor {
|
impl Handler<ActorMessage> for MemoryStoreActor {
|
||||||
type Result = ActorResponse;
|
type Result = ActorResponse;
|
||||||
fn handle(
|
fn handle(&mut self, msg: ActorMessage, ctx: &mut Self::Context) -> Self::Result {
|
||||||
&mut self,
|
|
||||||
msg: ActorMessage,
|
|
||||||
ctx: &mut Self::Context,
|
|
||||||
) -> Self::Result {
|
|
||||||
match msg {
|
match msg {
|
||||||
ActorMessage::Set { key, value, expiry } => {
|
ActorMessage::Set { key, value, expiry } => {
|
||||||
debug!(
|
debug!("Inserting key {} with expiry {}", &key, &expiry.as_secs());
|
||||||
"Inserting key {} with expiry {}",
|
|
||||||
&key,
|
|
||||||
&expiry.as_secs()
|
|
||||||
);
|
|
||||||
let future_key = String::from(&key);
|
let future_key = String::from(&key);
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
let now = now.duration_since(UNIX_EPOCH).unwrap();
|
let now = now.duration_since(UNIX_EPOCH).unwrap();
|
||||||
@@ -93,10 +85,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
ctx.notify_later(ActorMessage::Remove(future_key), expiry);
|
ctx.notify_later(ActorMessage::Remove(future_key), expiry);
|
||||||
ActorResponse::Set(Box::pin(future::ready(Ok(()))))
|
ActorResponse::Set(Box::pin(future::ready(Ok(()))))
|
||||||
}
|
}
|
||||||
ActorMessage::Update { key, value } => match self
|
ActorMessage::Update { key, value } => match self.inner.get_mut(&key) {
|
||||||
.inner
|
|
||||||
.get_mut(&key)
|
|
||||||
{
|
|
||||||
Some(mut c) => {
|
Some(mut c) => {
|
||||||
let val_mut: &mut (usize, Duration) = c.value_mut();
|
let val_mut: &mut (usize, Duration) = c.value_mut();
|
||||||
if val_mut.0 > value {
|
if val_mut.0 > value {
|
||||||
@@ -107,22 +96,18 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
let new_val = val_mut.0;
|
let new_val = val_mut.0;
|
||||||
ActorResponse::Update(Box::pin(future::ready(Ok(new_val))))
|
ActorResponse::Update(Box::pin(future::ready(Ok(new_val))))
|
||||||
}
|
}
|
||||||
None => ActorResponse::Update(Box::pin(future::ready(Err(
|
None => ActorResponse::Update(Box::pin(future::ready(Err(ARError::ReadWrite(
|
||||||
ARError::ReadWrite(
|
"memory store: read failed!".to_string(),
|
||||||
"memory store: read failed!".to_string(),
|
))))),
|
||||||
),
|
|
||||||
)))),
|
|
||||||
},
|
},
|
||||||
ActorMessage::Get(key) => {
|
ActorMessage::Get(key) => {
|
||||||
if self.inner.contains_key(&key) {
|
if self.inner.contains_key(&key) {
|
||||||
let val = match self.inner.get(&key) {
|
let val = match self.inner.get(&key) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Get(Box::pin(future::ready(
|
return ActorResponse::Get(Box::pin(future::ready(Err(
|
||||||
Err(ARError::ReadWrite(
|
ARError::ReadWrite("memory store: read failed!".to_string()),
|
||||||
"memory store: read failed!".to_string(),
|
))))
|
||||||
)),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = val.value().0;
|
let val = val.value().0;
|
||||||
@@ -135,17 +120,14 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
let c = match self.inner.get(&key) {
|
let c = match self.inner.get(&key) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Expire(Box::pin(future::ready(
|
return ActorResponse::Expire(Box::pin(future::ready(Err(
|
||||||
Err(ARError::ReadWrite(
|
ARError::ReadWrite("memory store: read failed!".to_string()),
|
||||||
"memory store: read failed!".to_string(),
|
))))
|
||||||
)),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let dur = c.value().1;
|
let dur = c.value().1;
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||||
let res =
|
let res = dur.checked_sub(now).unwrap_or_else(|| Duration::new(0, 0));
|
||||||
dur.checked_sub(now).unwrap_or_else(|| Duration::new(0, 0));
|
|
||||||
ActorResponse::Expire(Box::pin(future::ready(Ok(res))))
|
ActorResponse::Expire(Box::pin(future::ready(Ok(res))))
|
||||||
}
|
}
|
||||||
ActorMessage::Remove(key) => {
|
ActorMessage::Remove(key) => {
|
||||||
@@ -153,11 +135,9 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
let val = match self.inner.remove::<String>(&key) {
|
let val = match self.inner.remove::<String>(&key) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Remove(Box::pin(future::ready(
|
return ActorResponse::Remove(Box::pin(future::ready(Err(
|
||||||
Err(ARError::ReadWrite(
|
ARError::ReadWrite("memory store: remove failed!".to_string()),
|
||||||
"memory store: remove failed!".to_string(),
|
))))
|
||||||
)),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = val.1;
|
let val = val.1;
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
type RateLimiterIdentifier =
|
type RateLimiterIdentifier = Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>;
|
||||||
Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>;
|
|
||||||
|
|
||||||
pub struct RateLimiter<T>
|
pub struct RateLimiter<T>
|
||||||
where
|
where
|
||||||
@@ -42,8 +41,7 @@ where
|
|||||||
pub fn new(store: Addr<T>) -> Self {
|
pub fn new(store: Addr<T>) -> Self {
|
||||||
let identifier = |req: &ServiceRequest| {
|
let identifier = |req: &ServiceRequest| {
|
||||||
let connection_info = req.connection_info();
|
let connection_info = req.connection_info();
|
||||||
let ip =
|
let ip = connection_info.peer_addr().ok_or(ARError::Identification)?;
|
||||||
connection_info.peer_addr().ok_or(ARError::Identification)?;
|
|
||||||
Ok(String::from(ip))
|
Ok(String::from(ip))
|
||||||
};
|
};
|
||||||
RateLimiter {
|
RateLimiter {
|
||||||
@@ -74,9 +72,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Function to get the identifier for the client request
|
/// Function to get the identifier for the client request
|
||||||
pub fn with_identifier<
|
pub fn with_identifier<F: Fn(&ServiceRequest) -> Result<String, ARError> + 'static>(
|
||||||
F: Fn(&ServiceRequest) -> Result<String, ARError> + 'static,
|
|
||||||
>(
|
|
||||||
mut self,
|
mut self,
|
||||||
identifier: F,
|
identifier: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -89,8 +85,7 @@ impl<T, S, B> Transform<S, ServiceRequest> for RateLimiter<T>
|
|||||||
where
|
where
|
||||||
T: Handler<ActorMessage> + Send + Sync + 'static,
|
T: Handler<ActorMessage> + Send + Sync + 'static,
|
||||||
T::Context: ToEnvelope<T, ActorMessage>,
|
T::Context: ToEnvelope<T, ActorMessage>,
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError>
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError> + 'static,
|
||||||
+ 'static,
|
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
{
|
{
|
||||||
@@ -130,21 +125,16 @@ where
|
|||||||
impl<T, S, B> Service<ServiceRequest> for RateLimitMiddleware<S, T>
|
impl<T, S, B> Service<ServiceRequest> for RateLimitMiddleware<S, T>
|
||||||
where
|
where
|
||||||
T: Handler<ActorMessage> + 'static,
|
T: Handler<ActorMessage> + 'static,
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError>
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError> + 'static,
|
||||||
+ 'static,
|
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
T::Context: ToEnvelope<T, ActorMessage>,
|
T::Context: ToEnvelope<T, ActorMessage>,
|
||||||
{
|
{
|
||||||
type Response = ServiceResponse<B>;
|
type Response = ServiceResponse<B>;
|
||||||
type Error = S::Error;
|
type Error = S::Error;
|
||||||
type Future =
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
|
||||||
|
|
||||||
fn poll_ready(
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
&self,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.service.borrow_mut().poll_ready(cx)
|
self.service.borrow_mut().poll_ready(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,15 +168,9 @@ where
|
|||||||
if let Some(c) = opt {
|
if let Some(c) = opt {
|
||||||
// Existing entry in store
|
// Existing entry in store
|
||||||
let expiry = store
|
let expiry = store
|
||||||
.send(ActorMessage::Expire(String::from(
|
.send(ActorMessage::Expire(String::from(&identifier)))
|
||||||
&identifier,
|
|
||||||
)))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ARError::ReadWrite("Setting timeout".to_string()))?;
|
||||||
ARError::ReadWrite(
|
|
||||||
"Setting timeout".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let reset: Duration = match expiry {
|
let reset: Duration = match expiry {
|
||||||
ActorResponse::Expire(dur) => dur.await?,
|
ActorResponse::Expire(dur) => dur.await?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@@ -208,9 +192,7 @@ where
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ARError::ReadWrite(
|
ARError::ReadWrite("Decrementing ratelimit".to_string())
|
||||||
"Decrementing ratelimit".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
let updated_value: usize = match res {
|
let updated_value: usize = match res {
|
||||||
ActorResponse::Update(c) => c.await?,
|
ActorResponse::Update(c) => c.await?,
|
||||||
@@ -223,23 +205,15 @@ where
|
|||||||
// Safe unwraps, since usize is always convertible to string
|
// Safe unwraps, since usize is always convertible to string
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-limit"),
|
HeaderName::from_static("x-ratelimit-limit"),
|
||||||
HeaderValue::from_str(
|
HeaderValue::from_str(max_requests.to_string().as_str())?,
|
||||||
max_requests.to_string().as_str(),
|
|
||||||
)?,
|
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static(
|
HeaderName::from_static("x-ratelimit-remaining"),
|
||||||
"x-ratelimit-remaining",
|
HeaderValue::from_str(updated_value.to_string().as_str())?,
|
||||||
),
|
|
||||||
HeaderValue::from_str(
|
|
||||||
updated_value.to_string().as_str(),
|
|
||||||
)?,
|
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-reset"),
|
HeaderName::from_static("x-ratelimit-reset"),
|
||||||
HeaderValue::from_str(
|
HeaderValue::from_str(reset.as_secs().to_string().as_str())?,
|
||||||
reset.as_secs().to_string().as_str(),
|
|
||||||
)?,
|
|
||||||
);
|
);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -253,11 +227,7 @@ where
|
|||||||
expiry: interval,
|
expiry: interval,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ARError::ReadWrite("Creating store entry".to_string()))?;
|
||||||
ARError::ReadWrite(
|
|
||||||
"Creating store entry".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
match res {
|
match res {
|
||||||
ActorResponse::Set(c) => c.await?,
|
ActorResponse::Set(c) => c.await?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@@ -268,24 +238,15 @@ where
|
|||||||
// Safe unwraps, since usize is always convertible to string
|
// Safe unwraps, since usize is always convertible to string
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-limit"),
|
HeaderName::from_static("x-ratelimit-limit"),
|
||||||
HeaderValue::from_str(
|
HeaderValue::from_str(max_requests.to_string().as_str()).unwrap(),
|
||||||
max_requests.to_string().as_str(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-remaining"),
|
HeaderName::from_static("x-ratelimit-remaining"),
|
||||||
HeaderValue::from_str(
|
HeaderValue::from_str(current_value.to_string().as_str()).unwrap(),
|
||||||
current_value.to_string().as_str(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-reset"),
|
HeaderName::from_static("x-ratelimit-reset"),
|
||||||
HeaderValue::from_str(
|
HeaderValue::from_str(interval.as_secs().to_string().as_str()).unwrap(),
|
||||||
interval.as_secs().to_string().as_str(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,11 +68,8 @@ pub async fn maven_metadata(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id = params.into_inner().0;
|
let project_id = params.into_inner().0;
|
||||||
let project_data = database::models::Project::get_from_slug_or_project_id(
|
let project_data =
|
||||||
&project_id,
|
database::models::Project::get_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let data = if let Some(data) = project_data {
|
let data = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -150,9 +147,7 @@ fn find_file<'a>(
|
|||||||
version: &'a QueryVersion,
|
version: &'a QueryVersion,
|
||||||
file: &str,
|
file: &str,
|
||||||
) -> Option<&'a QueryFile> {
|
) -> Option<&'a QueryFile> {
|
||||||
if let Some(selected_file) =
|
if let Some(selected_file) = version.files.iter().find(|x| x.filename == file) {
|
||||||
version.files.iter().find(|x| x.filename == file)
|
|
||||||
{
|
|
||||||
return Some(selected_file);
|
return Some(selected_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +188,7 @@ pub async fn version_file(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
let project_data =
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||||
&project_id,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let project = if let Some(data) = project_data {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -229,11 +220,9 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) = database::models::Version::get_full(
|
let version = if let Some(version) =
|
||||||
database::models::ids::VersionId(vid.id),
|
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||||
&**pool,
|
.await?
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
@@ -263,9 +252,7 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok()
|
||||||
.content_type("text/xml")
|
.content_type("text/xml")
|
||||||
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
|
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
|
||||||
} else if let Some(selected_file) =
|
} else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) {
|
||||||
find_file(&project_id, &project, &version, &file)
|
|
||||||
{
|
|
||||||
return Ok(HttpResponse::TemporaryRedirect()
|
return Ok(HttpResponse::TemporaryRedirect()
|
||||||
.append_header(("location", &*selected_file.url))
|
.append_header(("location", &*selected_file.url))
|
||||||
.body(""));
|
.body(""));
|
||||||
@@ -282,11 +269,7 @@ pub async fn version_file_sha1(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
let project_data =
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||||
&project_id,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let project = if let Some(data) = project_data {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -318,11 +301,9 @@ pub async fn version_file_sha1(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) = database::models::Version::get_full(
|
let version = if let Some(version) =
|
||||||
database::models::ids::VersionId(vid.id),
|
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||||
&**pool,
|
.await?
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
@@ -343,11 +324,7 @@ pub async fn version_file_sha512(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
let project_data =
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?;
|
||||||
&project_id,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let project = if let Some(data) = project_data {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -379,11 +356,9 @@ pub async fn version_file_sha512(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) = database::models::Version::get_full(
|
let version = if let Some(version) =
|
||||||
database::models::ids::VersionId(vid.id),
|
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
||||||
&**pool,
|
.await?
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -97,30 +97,28 @@ impl actix_web::ResponseError for ApiError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::build(self.status_code()).json(
|
HttpResponse::build(self.status_code()).json(crate::models::error::ApiError {
|
||||||
crate::models::error::ApiError {
|
error: match self {
|
||||||
error: match self {
|
ApiError::Env(..) => "environment_error",
|
||||||
ApiError::Env(..) => "environment_error",
|
ApiError::SqlxDatabase(..) => "database_error",
|
||||||
ApiError::SqlxDatabase(..) => "database_error",
|
ApiError::Database(..) => "database_error",
|
||||||
ApiError::Database(..) => "database_error",
|
ApiError::Authentication(..) => "unauthorized",
|
||||||
ApiError::Authentication(..) => "unauthorized",
|
ApiError::CustomAuthentication(..) => "unauthorized",
|
||||||
ApiError::CustomAuthentication(..) => "unauthorized",
|
ApiError::Xml(..) => "xml_error",
|
||||||
ApiError::Xml(..) => "xml_error",
|
ApiError::Json(..) => "json_error",
|
||||||
ApiError::Json(..) => "json_error",
|
ApiError::Search(..) => "search_error",
|
||||||
ApiError::Search(..) => "search_error",
|
ApiError::Indexing(..) => "indexing_error",
|
||||||
ApiError::Indexing(..) => "indexing_error",
|
ApiError::FileHosting(..) => "file_hosting_error",
|
||||||
ApiError::FileHosting(..) => "file_hosting_error",
|
ApiError::InvalidInput(..) => "invalid_input",
|
||||||
ApiError::InvalidInput(..) => "invalid_input",
|
ApiError::Validation(..) => "invalid_input",
|
||||||
ApiError::Validation(..) => "invalid_input",
|
ApiError::Analytics(..) => "analytics_error",
|
||||||
ApiError::Analytics(..) => "analytics_error",
|
ApiError::Crypto(..) => "crypto_error",
|
||||||
ApiError::Crypto(..) => "crypto_error",
|
ApiError::Payments(..) => "payments_error",
|
||||||
ApiError::Payments(..) => "payments_error",
|
ApiError::DiscordError(..) => "discord_error",
|
||||||
ApiError::DiscordError(..) => "discord_error",
|
ApiError::Decoding(..) => "decoding_error",
|
||||||
ApiError::Decoding(..) => "decoding_error",
|
ApiError::ImageError(..) => "invalid_image",
|
||||||
ApiError::ImageError(..) => "invalid_image",
|
|
||||||
},
|
|
||||||
description: &self.to_string(),
|
|
||||||
},
|
},
|
||||||
)
|
description: &self.to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models::projects::VersionType;
|
use crate::models::projects::VersionType;
|
||||||
use crate::util::auth::{
|
use crate::util::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
|
||||||
filter_authorized_versions, get_user_from_headers, is_authorized,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
|
|
||||||
@@ -26,10 +24,9 @@ pub async fn forge_updates(
|
|||||||
|
|
||||||
let (id,) = info.into_inner();
|
let (id,) = info.into_inner();
|
||||||
|
|
||||||
let project =
|
let project = database::models::Project::get_from_slug_or_project_id(&id, &**pool)
|
||||||
database::models::Project::get_from_slug_or_project_id(&id, &**pool)
|
.await?
|
||||||
.await?
|
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
||||||
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -48,11 +45,9 @@ pub async fn forge_updates(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let versions =
|
let versions = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let mut versions =
|
let mut versions = filter_authorized_versions(versions, &user_option, &pool).await?;
|
||||||
filter_authorized_versions(versions, &user_option, &pool).await?;
|
|
||||||
|
|
||||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
||||||
|
|
||||||
|
|||||||
@@ -37,26 +37,22 @@ pub async fn count_download(
|
|||||||
download_body: web::Json<DownloadBody>,
|
download_body: web::Json<DownloadBody>,
|
||||||
download_queue: web::Data<Arc<DownloadQueue>>,
|
download_queue: web::Data<Arc<DownloadQueue>>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id: crate::database::models::ids::ProjectId =
|
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();
|
||||||
download_body.project_id.into();
|
|
||||||
|
|
||||||
let id_option = crate::models::ids::base62_impl::parse_base62(
|
let id_option = crate::models::ids::base62_impl::parse_base62(&download_body.version_name)
|
||||||
&download_body.version_name,
|
.ok()
|
||||||
)
|
.map(|x| x as i64);
|
||||||
.ok()
|
|
||||||
.map(|x| x as i64);
|
|
||||||
|
|
||||||
let (version_id, project_id, file_type) = if let Some(version) =
|
let (version_id, project_id, file_type) = if let Some(version) = sqlx::query!(
|
||||||
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, file_type FROM files f
|
||||||
INNER JOIN versions v ON v.id = f.version_id
|
INNER JOIN versions v ON v.id = f.version_id
|
||||||
WHERE f.url = $1
|
WHERE f.url = $1
|
||||||
",
|
",
|
||||||
download_body.url,
|
download_body.url,
|
||||||
)
|
)
|
||||||
.fetch_optional(pool.as_ref())
|
.fetch_optional(pool.as_ref())
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
(version.id, version.mod_id, version.file_type)
|
(version.id, version.mod_id, version.file_type)
|
||||||
} else if let Some(version) = sqlx::query!(
|
} else if let Some(version) = sqlx::query!(
|
||||||
@@ -143,17 +139,11 @@ pub async fn process_payout(
|
|||||||
)])
|
)])
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ApiError::Analytics("Error while fetching payout multipliers!".to_string()))?
|
||||||
ApiError::Analytics(
|
|
||||||
"Error while fetching payout multipliers!".to_string(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::Analytics(
|
ApiError::Analytics("Error while deserializing payout multipliers!".to_string())
|
||||||
"Error while deserializing payout multipliers!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
struct Project {
|
struct Project {
|
||||||
@@ -176,26 +166,33 @@ pub async fn process_payout(
|
|||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
WHERE m.id = ANY($1) AND m.monetization_status = $2
|
WHERE m.id = ANY($1) AND m.monetization_status = $2
|
||||||
",
|
",
|
||||||
&multipliers.values.keys().flat_map(|x| x.parse::<i64>().ok()).collect::<Vec<i64>>(),
|
&multipliers
|
||||||
|
.values
|
||||||
|
.keys()
|
||||||
|
.flat_map(|x| x.parse::<i64>().ok())
|
||||||
|
.collect::<Vec<i64>>(),
|
||||||
MonetizationStatus::Monetized.as_str(),
|
MonetizationStatus::Monetized.as_str(),
|
||||||
)
|
)
|
||||||
.fetch_many(&mut *transaction)
|
.fetch_many(&mut *transaction)
|
||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
if let Some(project) = projects_map.get_mut(&row.id) {
|
if let Some(project) = projects_map.get_mut(&row.id) {
|
||||||
project.team_members.push((row.user_id, row.payouts_split));
|
project.team_members.push((row.user_id, row.payouts_split));
|
||||||
} else {
|
} else {
|
||||||
projects_map.insert(row.id, Project {
|
projects_map.insert(
|
||||||
|
row.id,
|
||||||
|
Project {
|
||||||
project_type: row.project_type,
|
project_type: row.project_type,
|
||||||
team_members: vec![(row.user_id, row.payouts_split)],
|
team_members: vec![(row.user_id, row.payouts_split)],
|
||||||
split_team_members: Default::default()
|
split_team_members: Default::default(),
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
futures::future::ready(Ok(()))
|
futures::future::ready(Ok(()))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Specific Payout Conditions (ex: modpack payout split)
|
// Specific Payout Conditions (ex: modpack payout split)
|
||||||
let mut projects_split_dependencies = Vec::new();
|
let mut projects_split_dependencies = Vec::new();
|
||||||
@@ -208,8 +205,7 @@ pub async fn process_payout(
|
|||||||
|
|
||||||
if !projects_split_dependencies.is_empty() {
|
if !projects_split_dependencies.is_empty() {
|
||||||
// (dependent_id, (dependency_id, times_depended))
|
// (dependent_id, (dependency_id, times_depended))
|
||||||
let mut project_dependencies: HashMap<i64, Vec<(i64, i64)>> =
|
let mut project_dependencies: HashMap<i64, Vec<(i64, i64)>> = HashMap::new();
|
||||||
HashMap::new();
|
|
||||||
// dependency_ids to fetch team members from
|
// dependency_ids to fetch team members from
|
||||||
let mut fetch_team_members: Vec<i64> = Vec::new();
|
let mut fetch_team_members: Vec<i64> = Vec::new();
|
||||||
|
|
||||||
@@ -229,14 +225,11 @@ pub async fn process_payout(
|
|||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
fetch_team_members.push(row.id);
|
fetch_team_members.push(row.id);
|
||||||
|
|
||||||
if let Some(project) = project_dependencies.get_mut(&row.mod_id)
|
if let Some(project) = project_dependencies.get_mut(&row.mod_id) {
|
||||||
{
|
|
||||||
project.push((row.id, row.times_depended.unwrap_or(0)));
|
project.push((row.id, row.times_depended.unwrap_or(0)));
|
||||||
} else {
|
} else {
|
||||||
project_dependencies.insert(
|
project_dependencies
|
||||||
row.mod_id,
|
.insert(row.mod_id, vec![(row.id, row.times_depended.unwrap_or(0))]);
|
||||||
vec![(row.id, row.times_depended.unwrap_or(0))],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,8 +238,7 @@ pub async fn process_payout(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// (project_id, (user_id, payouts_split))
|
// (project_id, (user_id, payouts_split))
|
||||||
let mut team_members: HashMap<i64, Vec<(i64, Decimal)>> =
|
let mut team_members: HashMap<i64, Vec<(i64, Decimal)>> = HashMap::new();
|
||||||
HashMap::new();
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -263,8 +255,7 @@ pub async fn process_payout(
|
|||||||
if let Some(project) = team_members.get_mut(&row.id) {
|
if let Some(project) = team_members.get_mut(&row.id) {
|
||||||
project.push((row.user_id, row.payouts_split));
|
project.push((row.user_id, row.payouts_split));
|
||||||
} else {
|
} else {
|
||||||
team_members
|
team_members.insert(row.id, vec![(row.user_id, row.payouts_split)]);
|
||||||
.insert(row.id, vec![(row.user_id, row.payouts_split)]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,17 +272,14 @@ pub async fn process_payout(
|
|||||||
if dep_sum > 0 {
|
if dep_sum > 0 {
|
||||||
for dependency in dependencies {
|
for dependency in dependencies {
|
||||||
let project_multiplier: Decimal =
|
let project_multiplier: Decimal =
|
||||||
Decimal::from(dependency.1)
|
Decimal::from(dependency.1) / Decimal::from(dep_sum);
|
||||||
/ Decimal::from(dep_sum);
|
|
||||||
|
|
||||||
if let Some(members) = team_members.get(&dependency.0) {
|
if let Some(members) = team_members.get(&dependency.0) {
|
||||||
let members_sum: Decimal =
|
let members_sum: Decimal = members.iter().map(|x| x.1).sum();
|
||||||
members.iter().map(|x| x.1).sum();
|
|
||||||
|
|
||||||
if members_sum > Decimal::ZERO {
|
if members_sum > Decimal::ZERO {
|
||||||
for member in members {
|
for member in members {
|
||||||
let member_multiplier: Decimal =
|
let member_multiplier: Decimal = member.1 / members_sum;
|
||||||
member.1 / members_sum;
|
|
||||||
project.split_team_members.push((
|
project.split_team_members.push((
|
||||||
member.0,
|
member.0,
|
||||||
member_multiplier * project_multiplier,
|
member_multiplier * project_multiplier,
|
||||||
@@ -315,10 +303,8 @@ pub async fn process_payout(
|
|||||||
let split_given = Decimal::ONE / Decimal::from(5);
|
let split_given = Decimal::ONE / Decimal::from(5);
|
||||||
let split_retention = Decimal::from(4) / Decimal::from(5);
|
let split_retention = Decimal::from(4) / Decimal::from(5);
|
||||||
|
|
||||||
let sum_splits: Decimal =
|
let sum_splits: Decimal = project.team_members.iter().map(|x| x.1).sum();
|
||||||
project.team_members.iter().map(|x| x.1).sum();
|
let sum_tm_splits: Decimal = project.split_team_members.iter().map(|x| x.1).sum();
|
||||||
let sum_tm_splits: Decimal =
|
|
||||||
project.split_team_members.iter().map(|x| x.1).sum();
|
|
||||||
|
|
||||||
if sum_splits > Decimal::ZERO {
|
if sum_splits > Decimal::ZERO {
|
||||||
for (user_id, split) in project.team_members {
|
for (user_id, split) in project.team_members {
|
||||||
@@ -342,8 +328,8 @@ pub async fn process_payout(
|
|||||||
payout,
|
payout,
|
||||||
start
|
start
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -378,8 +364,8 @@ pub async fn process_payout(
|
|||||||
payout,
|
payout,
|
||||||
start
|
start
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -58,12 +58,8 @@ impl actix_web::ResponseError for AuthorizationError {
|
|||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
AuthorizationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
AuthorizationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
AuthorizationError::SqlxDatabase(..) => {
|
AuthorizationError::SqlxDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
AuthorizationError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}
|
|
||||||
AuthorizationError::Database(..) => {
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
}
|
|
||||||
AuthorizationError::SerDe(..) => StatusCode::BAD_REQUEST,
|
AuthorizationError::SerDe(..) => StatusCode::BAD_REQUEST,
|
||||||
AuthorizationError::Github(..) => StatusCode::FAILED_DEPENDENCY,
|
AuthorizationError::Github(..) => StatusCode::FAILED_DEPENDENCY,
|
||||||
AuthorizationError::InvalidCredentials => StatusCode::UNAUTHORIZED,
|
AuthorizationError::InvalidCredentials => StatusCode::UNAUTHORIZED,
|
||||||
@@ -84,9 +80,7 @@ impl actix_web::ResponseError for AuthorizationError {
|
|||||||
AuthorizationError::Github(..) => "github_error",
|
AuthorizationError::Github(..) => "github_error",
|
||||||
AuthorizationError::InvalidCredentials => "invalid_credentials",
|
AuthorizationError::InvalidCredentials => "invalid_credentials",
|
||||||
AuthorizationError::Decoding(..) => "decoding_error",
|
AuthorizationError::Decoding(..) => "decoding_error",
|
||||||
AuthorizationError::Authentication(..) => {
|
AuthorizationError::Authentication(..) => "authentication_error",
|
||||||
"authentication_error"
|
|
||||||
}
|
|
||||||
AuthorizationError::Url => "url_error",
|
AuthorizationError::Url => "url_error",
|
||||||
AuthorizationError::Banned => "user_banned",
|
AuthorizationError::Banned => "user_banned",
|
||||||
},
|
},
|
||||||
@@ -119,16 +113,12 @@ pub async fn init(
|
|||||||
Query(info): Query<AuthorizationInit>,
|
Query(info): Query<AuthorizationInit>,
|
||||||
client: Data<PgPool>,
|
client: Data<PgPool>,
|
||||||
) -> Result<HttpResponse, AuthorizationError> {
|
) -> Result<HttpResponse, AuthorizationError> {
|
||||||
let url =
|
let url = url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?;
|
||||||
url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?;
|
|
||||||
|
|
||||||
let allowed_callback_urls =
|
let allowed_callback_urls = parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||||
parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
|
||||||
|
|
||||||
let domain = url.domain().ok_or(AuthorizationError::Url)?;
|
let domain = url.domain().ok_or(AuthorizationError::Url)?;
|
||||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x))
|
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x)) && domain != "modrinth.com" {
|
||||||
&& domain != "modrinth.com"
|
|
||||||
{
|
|
||||||
return Err(AuthorizationError::Url);
|
return Err(AuthorizationError::Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,8 +205,7 @@ pub async fn auth_callback(
|
|||||||
|
|
||||||
let user = get_github_user_from_token(&token.access_token).await?;
|
let user = get_github_user_from_token(&token.access_token).await?;
|
||||||
|
|
||||||
let user_result =
|
let user_result = User::get_from_github_id(user.id, &mut *transaction).await?;
|
||||||
User::get_from_github_id(user.id, &mut *transaction).await?;
|
|
||||||
match user_result {
|
match user_result {
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None => {
|
None => {
|
||||||
@@ -231,9 +220,7 @@ pub async fn auth_callback(
|
|||||||
return Err(AuthorizationError::Banned);
|
return Err(AuthorizationError::Banned);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_id =
|
let user_id = crate::database::models::generate_user_id(&mut transaction).await?;
|
||||||
crate::database::models::generate_user_id(&mut transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut username_increment: i32 = 0;
|
let mut username_increment: i32 = 0;
|
||||||
let mut username = None;
|
let mut username = None;
|
||||||
|
|||||||
@@ -54,17 +54,11 @@ pub async fn init_checkout(
|
|||||||
])
|
])
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ApiError::Payments("Error while creating checkout session!".to_string()))?
|
||||||
ApiError::Payments(
|
|
||||||
"Error while creating checkout session!".to_string(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.json::<Session>()
|
.json::<Session>()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments("Error while deserializing checkout response!".to_string())
|
||||||
"Error while deserializing checkout response!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(json!(
|
Ok(HttpResponse::Ok().json(json!(
|
||||||
@@ -92,11 +86,7 @@ pub async fn init_customer_portal(
|
|||||||
.fetch_optional(&**pool)
|
.fetch_optional(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.and_then(|x| x.stripe_customer_id)
|
.and_then(|x| x.stripe_customer_id)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| ApiError::InvalidInput("User is not linked to stripe account!".to_string()))?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"User is not linked to stripe account!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
@@ -117,17 +107,11 @@ pub async fn init_customer_portal(
|
|||||||
])
|
])
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| ApiError::Payments("Error while creating billing session!".to_string()))?
|
||||||
ApiError::Payments(
|
|
||||||
"Error while creating billing session!".to_string(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.json::<Session>()
|
.json::<Session>()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments("Error while deserializing billing response!".to_string())
|
||||||
"Error while deserializing billing response!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(json!(
|
Ok(HttpResponse::Ok().json(json!(
|
||||||
@@ -166,27 +150,25 @@ pub async fn handle_stripe_webhook(
|
|||||||
if let Some(signature) = signature {
|
if let Some(signature) = signature {
|
||||||
type HmacSha256 = Hmac<sha2::Sha256>;
|
type HmacSha256 = Hmac<sha2::Sha256>;
|
||||||
|
|
||||||
let mut key = HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes()).map_err(|_| {
|
let mut key =
|
||||||
ApiError::Crypto(
|
HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes())
|
||||||
"Unable to initialize HMAC instance due to invalid key length!".to_string(),
|
.map_err(|_| {
|
||||||
)
|
ApiError::Crypto(
|
||||||
})?;
|
"Unable to initialize HMAC instance due to invalid key length!"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
key.update(format!("{timestamp}.{body}").as_bytes());
|
key.update(format!("{timestamp}.{body}").as_bytes());
|
||||||
|
|
||||||
key.verify(&signature).map_err(|_| {
|
key.verify(&signature).map_err(|_| {
|
||||||
ApiError::Crypto(
|
ApiError::Crypto("Unable to verify webhook signature!".to_string())
|
||||||
"Unable to verify webhook signature!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if timestamp < (Utc::now() - Duration::minutes(5)).timestamp()
|
if timestamp < (Utc::now() - Duration::minutes(5)).timestamp()
|
||||||
|| timestamp
|
|| timestamp > (Utc::now() + Duration::minutes(5)).timestamp()
|
||||||
> (Utc::now() + Duration::minutes(5)).timestamp()
|
|
||||||
{
|
{
|
||||||
return Err(ApiError::Crypto(
|
return Err(ApiError::Crypto("Webhook signature expired!".to_string()));
|
||||||
"Webhook signature expired!".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::Crypto("Missing signature!".to_string()));
|
return Err(ApiError::Crypto("Missing signature!".to_string()));
|
||||||
@@ -256,8 +238,7 @@ pub async fn handle_stripe_webhook(
|
|||||||
// TODO: Currently hardcoded to midas-only. When we add more stuff should include price IDs
|
// TODO: Currently hardcoded to midas-only. When we add more stuff should include price IDs
|
||||||
match &*webhook.type_ {
|
match &*webhook.type_ {
|
||||||
"checkout.session.completed" => {
|
"checkout.session.completed" => {
|
||||||
let session: CheckoutSession =
|
let session: CheckoutSession = serde_json::from_value(webhook.data.object)?;
|
||||||
serde_json::from_value(webhook.data.object)?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -276,8 +257,7 @@ pub async fn handle_stripe_webhook(
|
|||||||
|
|
||||||
if let Some(item) = invoice.lines.data.first() {
|
if let Some(item) = invoice.lines.data.first() {
|
||||||
let expires: DateTime<Utc> = DateTime::from_utc(
|
let expires: DateTime<Utc> = DateTime::from_utc(
|
||||||
NaiveDateTime::from_timestamp_opt(item.period.end, 0)
|
NaiveDateTime::from_timestamp_opt(item.period.end, 0).unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
Utc,
|
Utc,
|
||||||
) + Duration::days(1);
|
) + Duration::days(1);
|
||||||
|
|
||||||
@@ -323,8 +303,7 @@ pub async fn handle_stripe_webhook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"customer.subscription.deleted" => {
|
"customer.subscription.deleted" => {
|
||||||
let session: Subscription =
|
let session: Subscription = serde_json::from_value(webhook.data.object)?;
|
||||||
serde_json::from_value(webhook.data.object)?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -334,8 +313,8 @@ pub async fn handle_stripe_webhook(
|
|||||||
",
|
",
|
||||||
session.customer,
|
session.customer,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,18 +46,15 @@ pub async fn get_projects(
|
|||||||
count.count as i64
|
count.count as i64
|
||||||
)
|
)
|
||||||
.fetch_many(&**pool)
|
.fetch_many(&**pool)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ProjectId(m.id))) })
|
||||||
Ok(e.right().map(|m| database::models::ProjectId(m.id)))
|
|
||||||
})
|
|
||||||
.try_collect::<Vec<database::models::ProjectId>>()
|
.try_collect::<Vec<database::models::ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let projects: Vec<_> =
|
let projects: Vec<_> = database::Project::get_many_full(&project_ids, &**pool)
|
||||||
database::Project::get_many_full(&project_ids, &**pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(crate::models::projects::Project::from)
|
||||||
.map(crate::models::projects::Project::from)
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ use crate::models::ids::NotificationId;
|
|||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::get_user_from_headers;
|
use crate::util::auth::get_user_from_headers;
|
||||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(notifications_get);
|
cfg.service(notifications_get);
|
||||||
cfg.service(notifications_delete);
|
cfg.service(notifications_delete);
|
||||||
|
cfg.service(notifications_read);
|
||||||
|
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("notification")
|
web::scope("notification")
|
||||||
.service(notification_get)
|
.service(notification_get)
|
||||||
|
.service(notifications_read)
|
||||||
.service(notification_delete),
|
.service(notification_delete),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -31,7 +33,6 @@ pub async fn notifications_get(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
// TODO: this is really confusingly named.
|
|
||||||
use database::models::notification_item::Notification as DBNotification;
|
use database::models::notification_item::Notification as DBNotification;
|
||||||
use database::models::NotificationId as DBNotificationId;
|
use database::models::NotificationId as DBNotificationId;
|
||||||
|
|
||||||
@@ -42,11 +43,8 @@ pub async fn notifications_get(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let notifications_data: Vec<DBNotification> =
|
let notifications_data: Vec<DBNotification> =
|
||||||
database::models::notification_item::Notification::get_many(
|
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||||
¬ification_ids,
|
.await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let notifications: Vec<Notification> = notifications_data
|
let notifications: Vec<Notification> = notifications_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -68,11 +66,7 @@ pub async fn notification_get(
|
|||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let notification_data =
|
let notification_data =
|
||||||
database::models::notification_item::Notification::get(
|
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||||
id.into(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(data) = notification_data {
|
if let Some(data) = notification_data {
|
||||||
if user.id == data.user_id.into() || user.role.is_admin() {
|
if user.id == data.user_id.into() || user.role.is_admin() {
|
||||||
@@ -85,6 +79,39 @@ pub async fn notification_get(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[patch("{id}")]
|
||||||
|
pub async fn notification_read(
|
||||||
|
req: HttpRequest,
|
||||||
|
info: web::Path<(NotificationId,)>,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
|
let notification_data =
|
||||||
|
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||||
|
|
||||||
|
if let Some(data) = notification_data {
|
||||||
|
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
|
database::models::notification_item::Notification::read(id.into(), &mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
} else {
|
||||||
|
Err(ApiError::CustomAuthentication(
|
||||||
|
"You are not authorized to read this notification!".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[delete("{id}")]
|
#[delete("{id}")]
|
||||||
pub async fn notification_delete(
|
pub async fn notification_delete(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@@ -96,29 +123,21 @@ pub async fn notification_delete(
|
|||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let notification_data =
|
let notification_data =
|
||||||
database::models::notification_item::Notification::get(
|
database::models::notification_item::Notification::get(id.into(), &**pool).await?;
|
||||||
id.into(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(data) = notification_data {
|
if let Some(data) = notification_data {
|
||||||
if data.user_id == user.id.into() || user.role.is_admin() {
|
if data.user_id == user.id.into() || user.role.is_admin() {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
database::models::notification_item::Notification::remove(
|
database::models::notification_item::Notification::remove(id.into(), &mut transaction)
|
||||||
id.into(),
|
.await?;
|
||||||
&mut transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthentication(
|
Err(ApiError::CustomAuthentication(
|
||||||
"You are not authorized to delete this notification!"
|
"You are not authorized to delete this notification!".to_string(),
|
||||||
.to_string(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -126,6 +145,41 @@ pub async fn notification_delete(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[patch("notifications")]
|
||||||
|
pub async fn notifications_read(
|
||||||
|
req: HttpRequest,
|
||||||
|
web::Query(ids): web::Query<NotificationIds>,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.into())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
|
let notifications_data =
|
||||||
|
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||||
|
|
||||||
|
for notification in notifications_data {
|
||||||
|
if notification.user_id == user.id.into() || user.role.is_admin() {
|
||||||
|
notifications.push(notification.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
database::models::notification_item::Notification::read_many(¬ifications, &mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
}
|
||||||
|
|
||||||
#[delete("notifications")]
|
#[delete("notifications")]
|
||||||
pub async fn notifications_delete(
|
pub async fn notifications_delete(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@@ -134,23 +188,18 @@ pub async fn notifications_delete(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let notification_ids =
|
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
||||||
serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
|
.into_iter()
|
||||||
.into_iter()
|
.map(|x| x.into())
|
||||||
.map(|x| x.into())
|
.collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let notifications_data =
|
let notifications_data =
|
||||||
database::models::notification_item::Notification::get_many(
|
database::models::notification_item::Notification::get_many(¬ification_ids, &**pool)
|
||||||
¬ification_ids,
|
.await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut notifications: Vec<database::models::ids::NotificationId> =
|
let mut notifications: Vec<database::models::ids::NotificationId> = Vec::new();
|
||||||
Vec::new();
|
|
||||||
|
|
||||||
for notification in notifications_data {
|
for notification in notifications_data {
|
||||||
if notification.user_id == user.id.into() || user.role.is_admin() {
|
if notification.user_id == user.id.into() || user.role.is_admin() {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use crate::database::models::thread_item::ThreadBuilder;
|
|||||||
use crate::file_hosting::{FileHost, FileHostingError};
|
use crate::file_hosting::{FileHost, FileHostingError};
|
||||||
use crate::models::error::ApiError;
|
use crate::models::error::ApiError;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus,
|
DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, SideType, VersionId,
|
||||||
SideType, VersionId, VersionStatus,
|
VersionStatus,
|
||||||
};
|
};
|
||||||
use crate::models::threads::ThreadType;
|
use crate::models::threads::ThreadType;
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
@@ -79,14 +79,10 @@ impl actix_web::ResponseError for CreateError {
|
|||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
CreateError::SqlxDatabaseError(..) => {
|
CreateError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
}
|
|
||||||
CreateError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
CreateError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
CreateError::IndexingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
CreateError::IndexingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
CreateError::FileHostingError(..) => {
|
CreateError::FileHostingError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
}
|
|
||||||
CreateError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
CreateError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
||||||
@@ -97,9 +93,7 @@ impl actix_web::ResponseError for CreateError {
|
|||||||
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
||||||
CreateError::CustomAuthenticationError(..) => {
|
CreateError::CustomAuthenticationError(..) => StatusCode::UNAUTHORIZED,
|
||||||
StatusCode::UNAUTHORIZED
|
|
||||||
}
|
|
||||||
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
||||||
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST,
|
CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST,
|
||||||
@@ -347,21 +341,17 @@ async fn project_create_inner(
|
|||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
|
|
||||||
// The currently logged in user
|
// The currently logged in user
|
||||||
let current_user =
|
let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||||
get_user_from_headers(req.headers(), &mut *transaction).await?;
|
|
||||||
|
|
||||||
let project_id: ProjectId =
|
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();
|
||||||
models::generate_project_id(transaction).await?.into();
|
|
||||||
|
|
||||||
let project_create_data;
|
let project_create_data;
|
||||||
let mut versions;
|
let mut versions;
|
||||||
let mut versions_map = std::collections::HashMap::new();
|
let mut versions_map = std::collections::HashMap::new();
|
||||||
let mut gallery_urls = Vec::new();
|
let mut gallery_urls = Vec::new();
|
||||||
|
|
||||||
let all_game_versions =
|
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||||
let all_loaders =
|
|
||||||
models::categories::Loader::list(&mut *transaction).await?;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// The first multipart field must be named "data" and contain a
|
// The first multipart field must be named "data" and contain a
|
||||||
@@ -378,9 +368,9 @@ async fn project_create_inner(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let content_disposition = field.content_disposition();
|
let content_disposition = field.content_disposition();
|
||||||
let name = content_disposition.get_name().ok_or_else(|| {
|
let name = content_disposition
|
||||||
CreateError::MissingValueError(String::from("Missing content name"))
|
.get_name()
|
||||||
})?;
|
.ok_or_else(|| CreateError::MissingValueError(String::from("Missing content name")))?;
|
||||||
|
|
||||||
if name != "data" {
|
if name != "data" {
|
||||||
return Err(CreateError::InvalidInput(String::from(
|
return Err(CreateError::InvalidInput(String::from(
|
||||||
@@ -390,22 +380,19 @@ async fn project_create_inner(
|
|||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
data.extend_from_slice(
|
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||||
&chunk.map_err(CreateError::MultipartError)?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
||||||
|
|
||||||
create_data.validate().map_err(|err| {
|
create_data
|
||||||
CreateError::InvalidInput(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let slug_project_id_option: Option<ProjectId> =
|
let slug_project_id_option: Option<ProjectId> =
|
||||||
serde_json::from_str(&format!("\"{}\"", create_data.slug)).ok();
|
serde_json::from_str(&format!("\"{}\"", create_data.slug)).ok();
|
||||||
|
|
||||||
if let Some(slug_project_id) = slug_project_id_option {
|
if let Some(slug_project_id) = slug_project_id_option {
|
||||||
let slug_project_id: models::ids::ProjectId =
|
let slug_project_id: models::ids::ProjectId = slug_project_id.into();
|
||||||
slug_project_id.into();
|
|
||||||
let results = sqlx::query!(
|
let results = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
||||||
@@ -492,9 +479,7 @@ async fn project_create_inner(
|
|||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
|
|
||||||
let name = content_disposition.get_name().ok_or_else(|| {
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
CreateError::MissingValueError(
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
"Missing content name".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (file_name, file_extension) =
|
let (file_name, file_extension) =
|
||||||
@@ -528,9 +513,7 @@ async fn project_create_inner(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(item) =
|
if let Some(item) = gallery_items.iter().find(|x| x.item == name) {
|
||||||
gallery_items.iter().find(|x| x.item == name)
|
|
||||||
{
|
|
||||||
let data = read_from_field(
|
let data = read_from_field(
|
||||||
&mut field,
|
&mut field,
|
||||||
5 * (1 << 20),
|
5 * (1 << 20),
|
||||||
@@ -540,22 +523,13 @@ async fn project_create_inner(
|
|||||||
|
|
||||||
let hash = sha1::Sha1::from(&data).hexdigest();
|
let hash = sha1::Sha1::from(&data).hexdigest();
|
||||||
let (_, file_extension) =
|
let (_, file_extension) =
|
||||||
super::version_creation::get_name_ext(
|
super::version_creation::get_name_ext(&content_disposition)?;
|
||||||
&content_disposition,
|
let content_type = crate::util::ext::get_image_content_type(file_extension)
|
||||||
)?;
|
|
||||||
let content_type =
|
|
||||||
crate::util::ext::get_image_content_type(
|
|
||||||
file_extension,
|
|
||||||
)
|
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::InvalidIconFormat(
|
CreateError::InvalidIconFormat(file_extension.to_string())
|
||||||
file_extension.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let url = format!(
|
let url = format!("data/{project_id}/images/{hash}.{file_extension}");
|
||||||
"data/{project_id}/images/{hash}.{file_extension}"
|
|
||||||
);
|
|
||||||
let upload_data = file_host
|
let upload_data = file_host
|
||||||
.upload_file(content_type, &url, data.freeze())
|
.upload_file(content_type, &url, data.freeze())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -588,8 +562,7 @@ async fn project_create_inner(
|
|||||||
|
|
||||||
// `index` is always valid for these lists
|
// `index` is always valid for these lists
|
||||||
let created_version = versions.get_mut(index).unwrap();
|
let created_version = versions.get_mut(index).unwrap();
|
||||||
let version_data =
|
let version_data = project_create_data.initial_versions.get(index).unwrap();
|
||||||
project_create_data.initial_versions.get(index).unwrap();
|
|
||||||
|
|
||||||
// Upload the new jar file
|
// Upload the new jar file
|
||||||
super::version_creation::upload_file(
|
super::version_creation::upload_file(
|
||||||
@@ -642,8 +615,7 @@ async fn project_create_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the list of category names to actual categories
|
// Convert the list of category names to actual categories
|
||||||
let mut categories =
|
let mut categories = Vec::with_capacity(project_create_data.categories.len());
|
||||||
Vec::with_capacity(project_create_data.categories.len());
|
|
||||||
for category in &project_create_data.categories {
|
for category in &project_create_data.categories {
|
||||||
let id = models::categories::Category::get_id_project(
|
let id = models::categories::Category::get_id_project(
|
||||||
category,
|
category,
|
||||||
@@ -706,9 +678,7 @@ async fn project_create_inner(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::InvalidInput(
|
CreateError::InvalidInput("Client side type specified does not exist.".to_string())
|
||||||
"Client side type specified does not exist.".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let server_side_id = models::categories::SideType::get_id(
|
let server_side_id = models::categories::SideType::get_id(
|
||||||
@@ -717,35 +687,27 @@ async fn project_create_inner(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::InvalidInput(
|
CreateError::InvalidInput("Server side type specified does not exist.".to_string())
|
||||||
"Server side type specified does not exist.".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let license_id = spdx::Expression::parse(
|
let license_id =
|
||||||
&project_create_data.license_id,
|
spdx::Expression::parse(&project_create_data.license_id).map_err(|err| {
|
||||||
)
|
CreateError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||||
.map_err(|err| {
|
})?;
|
||||||
CreateError::InvalidInput(format!(
|
|
||||||
"Invalid SPDX license identifier: {err}"
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut donation_urls = vec![];
|
let mut donation_urls = vec![];
|
||||||
|
|
||||||
if let Some(urls) = &project_create_data.donation_urls {
|
if let Some(urls) = &project_create_data.donation_urls {
|
||||||
for url in urls {
|
for url in urls {
|
||||||
let platform_id = models::categories::DonationPlatform::get_id(
|
let platform_id =
|
||||||
&url.id,
|
models::categories::DonationPlatform::get_id(&url.id, &mut *transaction)
|
||||||
&mut *transaction,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
CreateError::InvalidInput(format!(
|
||||||
.ok_or_else(|| {
|
"Donation platform {} does not exist.",
|
||||||
CreateError::InvalidInput(format!(
|
url.id.clone()
|
||||||
"Donation platform {} does not exist.",
|
))
|
||||||
url.id.clone()
|
})?;
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
donation_urls.push(models::project_item::DonationUrl {
|
donation_urls.push(models::project_item::DonationUrl {
|
||||||
platform_id,
|
platform_id,
|
||||||
@@ -856,16 +818,10 @@ async fn project_create_inner(
|
|||||||
let _project_id = project_builder.insert(&mut *transaction).await?;
|
let _project_id = project_builder.insert(&mut *transaction).await?;
|
||||||
|
|
||||||
if status == ProjectStatus::Processing {
|
if status == ProjectStatus::Processing {
|
||||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK")
|
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
||||||
{
|
crate::util::webhook::send_discord_webhook(response.id, pool, webhook_url, None)
|
||||||
crate::util::webhook::send_discord_webhook(
|
.await
|
||||||
response.id,
|
.ok();
|
||||||
pool,
|
|
||||||
webhook_url,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,13 +844,12 @@ async fn create_initial_version(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
version_data.validate().map_err(|err| {
|
version_data
|
||||||
CreateError::ValidationError(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
// Randomly generate a new id to be used for the version
|
// Randomly generate a new id to be used for the version
|
||||||
let version_id: VersionId =
|
let version_id: VersionId = models::generate_version_id(transaction).await?.into();
|
||||||
models::generate_version_id(transaction).await?.into();
|
|
||||||
|
|
||||||
let game_versions = version_data
|
let game_versions = version_data
|
||||||
.game_versions
|
.game_versions
|
||||||
@@ -963,15 +918,8 @@ async fn process_icon_upload(
|
|||||||
mut field: Field,
|
mut field: Field,
|
||||||
cdn_url: &str,
|
cdn_url: &str,
|
||||||
) -> Result<(String, Option<u32>), CreateError> {
|
) -> Result<(String, Option<u32>), CreateError> {
|
||||||
if let Some(content_type) =
|
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
|
||||||
crate::util::ext::get_image_content_type(file_extension)
|
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
|
||||||
{
|
|
||||||
let data = read_from_field(
|
|
||||||
&mut field,
|
|
||||||
262144,
|
|
||||||
"Icons must be smaller than 256KiB",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let color = crate::util::img::get_color_from_img(&data)?;
|
let color = crate::util::img::get_color_from_img(&data)?;
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ use crate::models;
|
|||||||
use crate::models::ids::base62_impl::parse_base62;
|
use crate::models::ids::base62_impl::parse_base62;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus,
|
DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||||
SearchRequest, SideType,
|
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::models::threads::MessageBody;
|
use crate::models::threads::MessageBody;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||||
use crate::util::auth::{
|
use crate::util::auth::{filter_authorized_projects, get_user_from_headers, is_authorized};
|
||||||
filter_authorized_projects, get_user_from_headers, is_authorized,
|
|
||||||
};
|
|
||||||
use crate::util::routes::read_from_payload;
|
use crate::util::routes::read_from_payload;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
@@ -78,30 +75,30 @@ pub async fn random_projects_get(
|
|||||||
web::Query(count): web::Query<RandomProjects>,
|
web::Query(count): web::Query<RandomProjects>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
count.validate().map_err(|err| {
|
count
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let project_ids = sqlx::query!(
|
let project_ids = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id FROM mods TABLESAMPLE SYSTEM_ROWS($1) WHERE status = ANY($2)
|
SELECT id FROM mods TABLESAMPLE SYSTEM_ROWS($1) WHERE status = ANY($2)
|
||||||
",
|
",
|
||||||
count.count as i32,
|
count.count as i32,
|
||||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_searchable()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
)
|
.filter(|x| x.is_searchable())
|
||||||
.fetch_many(&**pool)
|
.map(|x| x.to_string())
|
||||||
.try_filter_map(|e| async {
|
.collect::<Vec<String>>(),
|
||||||
Ok(e.right().map(|m| database::models::ids::ProjectId(m.id)))
|
)
|
||||||
})
|
.fetch_many(&**pool)
|
||||||
.try_collect::<Vec<_>>()
|
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) })
|
||||||
.await?;
|
.try_collect::<Vec<_>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let projects_data =
|
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool)
|
||||||
database::models::Project::get_many_full(&project_ids, &**pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(Project::from)
|
||||||
.map(Project::from)
|
.collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects_data))
|
Ok(HttpResponse::Ok().json(projects_data))
|
||||||
}
|
}
|
||||||
@@ -123,13 +120,11 @@ pub async fn projects_get(
|
|||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let projects_data =
|
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let projects =
|
let projects = filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
||||||
filter_authorized_projects(projects_data, &user_option, &pool).await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
}
|
}
|
||||||
@@ -143,10 +138,7 @@ pub async fn project_get(
|
|||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_data =
|
let project_data =
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -229,10 +221,7 @@ pub async fn dependency_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -259,12 +248,13 @@ pub async fn dependency_list(
|
|||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|x| {
|
Ok(e.right().map(|x| {
|
||||||
(
|
(
|
||||||
x.dependency_id
|
x.dependency_id.map(database::models::VersionId),
|
||||||
.map(database::models::VersionId),
|
if x.mod_id == Some(0) {
|
||||||
if x.mod_id == Some(0) { None } else { x.mod_id
|
None
|
||||||
.map(database::models::ProjectId) },
|
} else {
|
||||||
x.mod_dependency_id
|
x.mod_id.map(database::models::ProjectId)
|
||||||
.map(database::models::ProjectId),
|
},
|
||||||
|
x.mod_dependency_id.map(database::models::ProjectId),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -430,15 +420,13 @@ pub async fn project_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
new_project.validate().map_err(|err| {
|
new_project
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let result = database::models::Project::get_full_from_slug_or_project_id(
|
let result =
|
||||||
&string, &**pool,
|
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(project_item) = result {
|
if let Some(project_item) = result {
|
||||||
let id = project_item.inner.id;
|
let id = project_item.inner.id;
|
||||||
@@ -456,8 +444,7 @@ pub async fn project_edit(
|
|||||||
} else if let Some(ref member) = team_member {
|
} else if let Some(ref member) = team_member {
|
||||||
permissions = Some(member.permissions)
|
permissions = Some(member.permissions)
|
||||||
} else if user.role.is_mod() {
|
} else if user.role.is_mod() {
|
||||||
permissions =
|
permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||||
Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
|
||||||
} else {
|
} else {
|
||||||
permissions = None
|
permissions = None
|
||||||
}
|
}
|
||||||
@@ -518,12 +505,10 @@ pub async fn project_edit(
|
|||||||
if !(user.role.is_mod()
|
if !(user.role.is_mod()
|
||||||
|| !project_item.inner.status.is_approved()
|
|| !project_item.inner.status.is_approved()
|
||||||
&& status == &ProjectStatus::Processing
|
&& status == &ProjectStatus::Processing
|
||||||
|| project_item.inner.status.is_approved()
|
|| project_item.inner.status.is_approved() && status.can_be_requested())
|
||||||
&& status.can_be_requested())
|
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to set this status!"
|
"You don't have permission to set this status!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,9 +530,7 @@ pub async fn project_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Ok(webhook_url) =
|
if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") {
|
||||||
dotenvy::var("MODERATION_DISCORD_WEBHOOK")
|
|
||||||
{
|
|
||||||
crate::util::webhook::send_discord_webhook(
|
crate::util::webhook::send_discord_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
@@ -559,9 +542,7 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.is_approved()
|
if status.is_approved() && !project_item.inner.status.is_approved() {
|
||||||
&& !project_item.inner.status.is_approved()
|
|
||||||
{
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE mods
|
UPDATE mods
|
||||||
@@ -575,9 +556,7 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status.is_searchable() && !project_item.inner.webhook_sent {
|
if status.is_searchable() && !project_item.inner.webhook_sent {
|
||||||
if let Ok(webhook_url) =
|
if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") {
|
||||||
dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
|
||||||
{
|
|
||||||
crate::util::webhook::send_discord_webhook(
|
crate::util::webhook::send_discord_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
@@ -607,8 +586,7 @@ pub async fn project_edit(
|
|||||||
FROM team_members tm
|
FROM team_members tm
|
||||||
WHERE tm.team_id = $1 AND tm.accepted
|
WHERE tm.team_id = $1 AND tm.accepted
|
||||||
",
|
",
|
||||||
project_item.inner.team_id
|
project_item.inner.team_id as database::models::ids::TeamId
|
||||||
as database::models::ids::TeamId
|
|
||||||
)
|
)
|
||||||
.fetch_many(&mut *transaction)
|
.fetch_many(&mut *transaction)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
@@ -653,9 +631,7 @@ pub async fn project_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if project_item.inner.status.is_searchable()
|
if project_item.inner.status.is_searchable() && !status.is_searchable() {
|
||||||
&& !status.is_searchable()
|
|
||||||
{
|
|
||||||
delete_from_index(id.into(), config).await?;
|
delete_from_index(id.into(), config).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,17 +702,14 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
for category in categories {
|
for category in categories {
|
||||||
let category_id =
|
let category_id =
|
||||||
database::models::categories::Category::get_id(
|
database::models::categories::Category::get_id(category, &mut *transaction)
|
||||||
category,
|
.await?
|
||||||
&mut *transaction,
|
.ok_or_else(|| {
|
||||||
)
|
ApiError::InvalidInput(format!(
|
||||||
.await?
|
"Category {} does not exist.",
|
||||||
.ok_or_else(|| {
|
category.clone()
|
||||||
ApiError::InvalidInput(format!(
|
))
|
||||||
"Category {} does not exist.",
|
})?;
|
||||||
category.clone()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -761,17 +734,14 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
for category in categories {
|
for category in categories {
|
||||||
let category_id =
|
let category_id =
|
||||||
database::models::categories::Category::get_id(
|
database::models::categories::Category::get_id(category, &mut *transaction)
|
||||||
category,
|
.await?
|
||||||
&mut *transaction,
|
.ok_or_else(|| {
|
||||||
)
|
ApiError::InvalidInput(format!(
|
||||||
.await?
|
"Category {} does not exist.",
|
||||||
.ok_or_else(|| {
|
category.clone()
|
||||||
ApiError::InvalidInput(format!(
|
))
|
||||||
"Category {} does not exist.",
|
})?;
|
||||||
category.clone()
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -899,8 +869,7 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let slug_project_id_option: Option<u64> =
|
let slug_project_id_option: Option<u64> = parse_base62(slug).ok();
|
||||||
parse_base62(slug).ok();
|
|
||||||
if let Some(slug_project_id) = slug_project_id_option {
|
if let Some(slug_project_id) = slug_project_id_option {
|
||||||
let results = sqlx::query!(
|
let results = sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -913,8 +882,7 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
if results.exists.unwrap_or(true) {
|
if results.exists.unwrap_or(true) {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Slug collides with other project's id!"
|
"Slug collides with other project's id!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -933,8 +901,7 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
if results.exists.unwrap_or(true) {
|
if results.exists.unwrap_or(true) {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Slug collides with other project's id!"
|
"Slug collides with other project's id!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,13 +927,12 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let side_type_id =
|
let side_type_id = database::models::categories::SideType::get_id(
|
||||||
database::models::categories::SideType::get_id(
|
new_side.as_str(),
|
||||||
new_side.as_str(),
|
&mut *transaction,
|
||||||
&mut *transaction,
|
)
|
||||||
)
|
.await?
|
||||||
.await?
|
.expect("No database entry found for side type");
|
||||||
.expect("No database entry found for side type");
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -989,13 +955,12 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let side_type_id =
|
let side_type_id = database::models::categories::SideType::get_id(
|
||||||
database::models::categories::SideType::get_id(
|
new_side.as_str(),
|
||||||
new_side.as_str(),
|
&mut *transaction,
|
||||||
&mut *transaction,
|
)
|
||||||
)
|
.await?
|
||||||
.await?
|
.expect("No database entry found for side type");
|
||||||
.expect("No database entry found for side type");
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -1025,9 +990,7 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
spdx::Expression::parse(&license).map_err(|err| {
|
spdx::Expression::parse(&license).map_err(|err| {
|
||||||
ApiError::InvalidInput(format!(
|
ApiError::InvalidInput(format!("Invalid SPDX license identifier: {err}"))
|
||||||
"Invalid SPDX license identifier: {err}"
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@@ -1062,18 +1025,17 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for donation in donations {
|
for donation in donations {
|
||||||
let platform_id =
|
let platform_id = database::models::categories::DonationPlatform::get_id(
|
||||||
database::models::categories::DonationPlatform::get_id(
|
&donation.id,
|
||||||
&donation.id,
|
&mut *transaction,
|
||||||
&mut *transaction,
|
)
|
||||||
)
|
.await?
|
||||||
.await?
|
.ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
ApiError::InvalidInput(format!(
|
||||||
ApiError::InvalidInput(format!(
|
"Platform {} does not exist.",
|
||||||
"Platform {} does not exist.",
|
donation.id.clone()
|
||||||
donation.id.clone()
|
))
|
||||||
))
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -1090,9 +1052,7 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(moderation_message) = &new_project.moderation_message {
|
if let Some(moderation_message) = &new_project.moderation_message {
|
||||||
if !user.role.is_mod()
|
if !user.role.is_mod() && project_item.inner.status != ProjectStatus::Approved {
|
||||||
&& project_item.inner.status != ProjectStatus::Approved
|
|
||||||
{
|
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the moderation message of this project!"
|
"You do not have the permissions to edit the moderation message of this project!"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@@ -1112,12 +1072,8 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(moderation_message_body) =
|
if let Some(moderation_message_body) = &new_project.moderation_message_body {
|
||||||
&new_project.moderation_message_body
|
if !user.role.is_mod() && project_item.inner.status != ProjectStatus::Approved {
|
||||||
{
|
|
||||||
if !user.role.is_mod()
|
|
||||||
&& project_item.inner.status != ProjectStatus::Approved
|
|
||||||
{
|
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the moderation message body of this project!"
|
"You do not have the permissions to edit the moderation message body of this project!"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@@ -1158,8 +1114,7 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(monetization_status) = &new_project.monetization_status
|
if let Some(monetization_status) = &new_project.monetization_status {
|
||||||
{
|
|
||||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the monetization status of this project!"
|
"You do not have the permissions to edit the monetization status of this project!"
|
||||||
@@ -1167,8 +1122,7 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*monetization_status
|
if (*monetization_status == MonetizationStatus::ForceDemonetized
|
||||||
== MonetizationStatus::ForceDemonetized
|
|
||||||
|| project_item.inner.monetization_status
|
|| project_item.inner.monetization_status
|
||||||
== MonetizationStatus::ForceDemonetized)
|
== MonetizationStatus::ForceDemonetized)
|
||||||
&& !user.role.is_mod()
|
&& !user.role.is_mod()
|
||||||
@@ -1276,9 +1230,9 @@ pub async fn projects_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
bulk_edit_project.validate().map_err(|err| {
|
bulk_edit_project
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let project_ids: Vec<database::models::ids::ProjectId> =
|
let project_ids: Vec<database::models::ids::ProjectId> =
|
||||||
serde_json::from_str::<Vec<ProjectId>>(&ids.ids)?
|
serde_json::from_str::<Vec<ProjectId>>(&ids.ids)?
|
||||||
@@ -1286,8 +1240,7 @@ pub async fn projects_edit(
|
|||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let projects_data =
|
let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = project_ids
|
if let Some(id) = project_ids
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1303,15 +1256,11 @@ pub async fn projects_edit(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.inner.team_id)
|
.map(|x| x.inner.team_id)
|
||||||
.collect::<Vec<database::models::TeamId>>();
|
.collect::<Vec<database::models::TeamId>>();
|
||||||
let team_members = database::models::TeamMember::get_from_team_full_many(
|
let team_members =
|
||||||
&team_ids, &**pool,
|
database::models::TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let categories =
|
let categories = database::models::categories::Category::list(&**pool).await?;
|
||||||
database::models::categories::Category::list(&**pool).await?;
|
let donation_platforms = database::models::categories::DonationPlatform::list(&**pool).await?;
|
||||||
let donation_platforms =
|
|
||||||
database::models::categories::DonationPlatform::list(&**pool).await?;
|
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
@@ -1322,9 +1271,10 @@ pub async fn projects_edit(
|
|||||||
.find(|x| x.team_id == project.inner.team_id)
|
.find(|x| x.team_id == project.inner.team_id)
|
||||||
{
|
{
|
||||||
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(format!(
|
||||||
format!("You do not have the permissions to bulk edit project {}!", project.inner.title),
|
"You do not have the permissions to bulk edit project {}!",
|
||||||
));
|
project.inner.title
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
} else if project.inner.status.is_hidden() {
|
} else if project.inner.status.is_hidden() {
|
||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
@@ -1336,18 +1286,15 @@ pub async fn projects_edit(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut set_categories =
|
let mut set_categories = if let Some(categories) = bulk_edit_project.categories.clone() {
|
||||||
if let Some(categories) = bulk_edit_project.categories.clone() {
|
categories
|
||||||
categories
|
} else {
|
||||||
} else {
|
project.categories.clone()
|
||||||
project.categories.clone()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(delete_categories) = &bulk_edit_project.remove_categories {
|
if let Some(delete_categories) = &bulk_edit_project.remove_categories {
|
||||||
for category in delete_categories {
|
for category in delete_categories {
|
||||||
if let Some(pos) =
|
if let Some(pos) = set_categories.iter().position(|x| x == category) {
|
||||||
set_categories.iter().position(|x| x == category)
|
|
||||||
{
|
|
||||||
set_categories.remove(pos);
|
set_categories.remove(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1394,34 +1341,27 @@ pub async fn projects_edit(
|
|||||||
project.inner.id as database::models::ids::ProjectId,
|
project.inner.id as database::models::ids::ProjectId,
|
||||||
category_id as database::models::ids::CategoryId,
|
category_id as database::models::ids::CategoryId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut set_additional_categories = if let Some(categories) =
|
let mut set_additional_categories =
|
||||||
bulk_edit_project.additional_categories.clone()
|
if let Some(categories) = bulk_edit_project.additional_categories.clone() {
|
||||||
{
|
categories
|
||||||
categories
|
} else {
|
||||||
} else {
|
project.additional_categories.clone()
|
||||||
project.additional_categories.clone()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(delete_categories) =
|
if let Some(delete_categories) = &bulk_edit_project.remove_additional_categories {
|
||||||
&bulk_edit_project.remove_additional_categories
|
|
||||||
{
|
|
||||||
for category in delete_categories {
|
for category in delete_categories {
|
||||||
if let Some(pos) =
|
if let Some(pos) = set_additional_categories.iter().position(|x| x == category) {
|
||||||
set_additional_categories.iter().position(|x| x == category)
|
|
||||||
{
|
|
||||||
set_additional_categories.remove(pos);
|
set_additional_categories.remove(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(add_categories) =
|
if let Some(add_categories) = &bulk_edit_project.add_additional_categories {
|
||||||
&bulk_edit_project.add_additional_categories
|
|
||||||
{
|
|
||||||
for category in add_categories {
|
for category in add_categories {
|
||||||
if set_additional_categories.len() < 256 {
|
if set_additional_categories.len() < 256 {
|
||||||
set_additional_categories.push(category.clone());
|
set_additional_categories.push(category.clone());
|
||||||
@@ -1476,16 +1416,14 @@ pub async fn projects_edit(
|
|||||||
url: d.url,
|
url: d.url,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let mut set_donation_links = if let Some(donation_links) =
|
let mut set_donation_links =
|
||||||
bulk_edit_project.donation_urls.clone()
|
if let Some(donation_links) = bulk_edit_project.donation_urls.clone() {
|
||||||
{
|
donation_links
|
||||||
donation_links
|
} else {
|
||||||
} else {
|
project_donations.clone()
|
||||||
project_donations.clone()
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(delete_donations) = &bulk_edit_project.remove_donation_urls
|
if let Some(delete_donations) = &bulk_edit_project.remove_donation_urls {
|
||||||
{
|
|
||||||
for donation in delete_donations {
|
for donation in delete_donations {
|
||||||
if let Some(pos) = set_donation_links
|
if let Some(pos) = set_donation_links
|
||||||
.iter()
|
.iter()
|
||||||
@@ -1532,8 +1470,8 @@ pub async fn projects_edit(
|
|||||||
platform_id as database::models::ids::DonationPlatformId,
|
platform_id as database::models::ids::DonationPlatformId,
|
||||||
donation.url
|
donation.url
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1616,8 +1554,7 @@ pub async fn project_schedule(
|
|||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You cannot schedule a project to be released in the past!"
|
"You cannot schedule a project to be released in the past!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1628,10 +1565,7 @@ pub async fn project_schedule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(project_item) = result {
|
if let Some(project_item) = result {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -1684,22 +1618,15 @@ pub async fn project_icon_edit(
|
|||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) =
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
crate::util::ext::get_image_content_type(&ext.ext)
|
|
||||||
{
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
database::models::Project::get_from_slug_or_project_id(
|
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
@@ -1711,15 +1638,12 @@ pub async fn project_icon_edit(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::Database)?
|
.map_err(ApiError::Database)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this project's icon."
|
"You don't have permission to edit this project's icon.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1732,12 +1656,8 @@ pub async fn project_icon_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = read_from_payload(
|
let bytes =
|
||||||
&mut payload,
|
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||||
262144,
|
|
||||||
"Icons must be smaller than 256KiB",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let color = crate::util::img::get_color_from_img(&bytes)?;
|
let color = crate::util::img::get_color_from_img(&bytes)?;
|
||||||
|
|
||||||
@@ -1787,15 +1707,11 @@ pub async fn delete_project_icon(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -1806,15 +1722,12 @@ pub async fn delete_project_icon(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::Database)?
|
.map_err(ApiError::Database)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this project's icon."
|
"You don't have permission to edit this project's icon.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1866,32 +1779,24 @@ pub async fn add_gallery_item(
|
|||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) =
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
crate::util::ext::get_image_content_type(&ext.ext)
|
item.validate()
|
||||||
{
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
item.validate().map_err(|err| {
|
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item =
|
||||||
database::models::Project::get_full_from_slug_or_project_id(
|
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if project_item.gallery_items.len() > 64 {
|
if project_item.gallery_items.len() > 64 {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You have reached the maximum of gallery images to upload."
|
"You have reached the maximum of gallery images to upload.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1904,15 +1809,12 @@ pub async fn add_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::Database)?
|
.map_err(ApiError::Database)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this project's gallery."
|
"You don't have permission to edit this project's gallery.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2013,19 +1915,14 @@ pub async fn edit_gallery_item(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
item.validate().map_err(|err| {
|
item.validate()
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -2036,15 +1933,12 @@ pub async fn edit_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::Database)?
|
.map_err(ApiError::Database)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this project's gallery."
|
"You don't have permission to edit this project's gallery.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2157,15 +2051,11 @@ pub async fn delete_gallery_item(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item = database::models::Project::get_from_slug_or_project_id(
|
let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -2176,15 +2066,12 @@ pub async fn delete_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::Database)?
|
.map_err(ApiError::Database)?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this project's gallery."
|
"You don't have permission to edit this project's gallery.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2241,30 +2128,23 @@ pub async fn project_delete(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project = database::models::Project::get_from_slug_or_project_id(
|
let project = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !user.role.is_admin() {
|
if !user.role.is_admin() {
|
||||||
let team_member =
|
let team_member = database::models::TeamMember::get_from_user_id_project(
|
||||||
database::models::TeamMember::get_from_user_id_project(
|
project.id,
|
||||||
project.id,
|
user.id.into(),
|
||||||
user.id.into(),
|
&**pool,
|
||||||
&**pool,
|
)
|
||||||
)
|
.await
|
||||||
.await
|
.map_err(ApiError::Database)?
|
||||||
.map_err(ApiError::Database)?
|
.ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
ApiError::InvalidInput(
|
})?;
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !team_member
|
if !team_member
|
||||||
.permissions
|
.permissions
|
||||||
@@ -2278,9 +2158,7 @@ pub async fn project_delete(
|
|||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let result =
|
let result = database::models::Project::remove_full(project.id, &mut transaction).await?;
|
||||||
database::models::Project::remove_full(project.id, &mut transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
@@ -2302,15 +2180,11 @@ pub async fn project_follow(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let user_id: database::models::ids::UserId = user.id.into();
|
let user_id: database::models::ids::UserId = user.id.into();
|
||||||
let project_id: database::models::ids::ProjectId = result.id;
|
let project_id: database::models::ids::ProjectId = result.id;
|
||||||
@@ -2375,15 +2249,11 @@ pub async fn project_unfollow(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool)
|
||||||
&string, &**pool,
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
.await?
|
ApiError::InvalidInput("The specified project does not exist!".to_string())
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The specified project does not exist!".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let user_id: database::models::ids::UserId = user.id.into();
|
let user_id: database::models::ids::UserId = user.id.into();
|
||||||
let project_id = result.id;
|
let project_id = result.id;
|
||||||
@@ -2439,8 +2309,7 @@ pub async fn delete_from_index(
|
|||||||
id: ProjectId,
|
id: ProjectId,
|
||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
) -> Result<(), meilisearch_sdk::errors::Error> {
|
) -> Result<(), meilisearch_sdk::errors::Error> {
|
||||||
let client =
|
let client = meilisearch_sdk::client::Client::new(&*config.address, &*config.key);
|
||||||
meilisearch_sdk::client::Client::new(&*config.address, &*config.key);
|
|
||||||
|
|
||||||
let indexes: IndexesResults = client.get_indexes().await?;
|
let indexes: IndexesResults = client.get_indexes().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
use crate::database::models::thread_item::{
|
use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
|
||||||
ThreadBuilder, ThreadMessageBuilder,
|
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
|
||||||
};
|
|
||||||
use crate::models::ids::{
|
|
||||||
base62_impl::parse_base62, ProjectId, UserId, VersionId,
|
|
||||||
};
|
|
||||||
use crate::models::reports::{ItemType, Report};
|
use crate::models::reports::{ItemType, Report};
|
||||||
use crate::models::threads::{MessageBody, ThreadType};
|
use crate::models::threads::{MessageBody, ThreadType};
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::{
|
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||||
check_is_moderator_from_headers, get_user_from_headers,
|
|
||||||
};
|
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -41,31 +35,24 @@ pub async fn report_create(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user =
|
let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||||
get_user_from_headers(req.headers(), &mut *transaction).await?;
|
|
||||||
|
|
||||||
let mut bytes = web::BytesMut::new();
|
let mut bytes = web::BytesMut::new();
|
||||||
while let Some(item) = body.next().await {
|
while let Some(item) = body.next().await {
|
||||||
bytes.extend_from_slice(&item.map_err(|_| {
|
bytes.extend_from_slice(&item.map_err(|_| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("Error while parsing request payload!".to_string())
|
||||||
"Error while parsing request payload!".to_string(),
|
|
||||||
)
|
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
let new_report: CreateReport = serde_json::from_slice(bytes.as_ref())?;
|
let new_report: CreateReport = serde_json::from_slice(bytes.as_ref())?;
|
||||||
|
|
||||||
let id =
|
let id = crate::database::models::generate_report_id(&mut transaction).await?;
|
||||||
crate::database::models::generate_report_id(&mut transaction).await?;
|
|
||||||
let report_type = crate::database::models::categories::ReportType::get_id(
|
let report_type = crate::database::models::categories::ReportType::get_id(
|
||||||
&new_report.report_type,
|
&new_report.report_type,
|
||||||
&mut *transaction,
|
&mut *transaction,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ApiError::InvalidInput(format!(
|
ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type))
|
||||||
"Invalid report type: {}",
|
|
||||||
new_report.report_type
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let thread_id = ThreadBuilder {
|
let thread_id = ThreadBuilder {
|
||||||
@@ -92,8 +79,7 @@ pub async fn report_create(
|
|||||||
|
|
||||||
match new_report.item_type {
|
match new_report.item_type {
|
||||||
ItemType::Project => {
|
ItemType::Project => {
|
||||||
let project_id =
|
let project_id = ProjectId(parse_base62(new_report.item_id.as_str())?);
|
||||||
ProjectId(parse_base62(new_report.item_id.as_str())?);
|
|
||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"SELECT EXISTS(SELECT 1 FROM mods WHERE id = $1)",
|
"SELECT EXISTS(SELECT 1 FROM mods WHERE id = $1)",
|
||||||
@@ -112,8 +98,7 @@ pub async fn report_create(
|
|||||||
report.project_id = Some(project_id.into())
|
report.project_id = Some(project_id.into())
|
||||||
}
|
}
|
||||||
ItemType::Version => {
|
ItemType::Version => {
|
||||||
let version_id =
|
let version_id = VersionId(parse_base62(new_report.item_id.as_str())?);
|
||||||
VersionId(parse_base62(new_report.item_id.as_str())?);
|
|
||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)",
|
"SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)",
|
||||||
@@ -236,11 +221,8 @@ pub async fn reports(
|
|||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
let query_reports = crate::database::models::report_item::Report::get_many(
|
let query_reports =
|
||||||
&report_ids,
|
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut reports = Vec::new();
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
@@ -260,8 +242,7 @@ pub async fn report_get(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report =
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(report) = report {
|
if let Some(report) = report {
|
||||||
if !user.role.is_mod() && report.reporter != user.id.into() {
|
if !user.role.is_mod() && report.reporter != user.id.into() {
|
||||||
@@ -291,8 +272,7 @@ pub async fn report_edit(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id = info.into_inner().0.into();
|
let id = info.into_inner().0.into();
|
||||||
|
|
||||||
let report =
|
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
|
||||||
crate::database::models::report_item::Report::get(id, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(report) = report {
|
if let Some(report) = report {
|
||||||
if !user.role.is_mod() && report.user_id != Some(user.id.into()) {
|
if !user.role.is_mod() && report.user_id != Some(user.id.into()) {
|
||||||
@@ -380,9 +360,7 @@ pub async fn report_delete(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_report(
|
fn to_report(x: crate::database::models::report_item::QueryReport) -> Result<Report, ApiError> {
|
||||||
x: crate::database::models::report_item::QueryReport,
|
|
||||||
) -> Result<Report, ApiError> {
|
|
||||||
let mut item_id = "".to_string();
|
let mut item_id = "".to_string();
|
||||||
let mut item_type = ItemType::Unknown;
|
let mut item_type = ItemType::Unknown;
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("statistics")]
|
#[get("statistics")]
|
||||||
pub async fn get_stats(
|
pub async fn get_stats(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let projects = sqlx::query!(
|
let projects = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT COUNT(id)
|
SELECT COUNT(id)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database::models;
|
use crate::database::models;
|
||||||
use crate::database::models::categories::{
|
use crate::database::models::categories::{DonationPlatform, ProjectType, ReportType, SideType};
|
||||||
DonationPlatform, ProjectType, ReportType, SideType,
|
|
||||||
};
|
|
||||||
use actix_web::{get, web, HttpResponse};
|
use actix_web::{get, web, HttpResponse};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use models::categories::{Category, GameVersion, Loader};
|
use models::categories::{Category, GameVersion, Loader};
|
||||||
@@ -34,9 +32,7 @@ pub struct CategoryData {
|
|||||||
// TODO: searching / filtering? Could be used to implement a live
|
// TODO: searching / filtering? Could be used to implement a live
|
||||||
// searching category list
|
// searching category list
|
||||||
#[get("category")]
|
#[get("category")]
|
||||||
pub async fn category_list(
|
pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let results = Category::list(&**pool)
|
let results = Category::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -59,9 +55,7 @@ pub struct LoaderData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("loader")]
|
#[get("loader")]
|
||||||
pub async fn loader_list(
|
pub async fn loader_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let mut results = Loader::list(&**pool)
|
let mut results = Loader::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -97,11 +91,8 @@ pub async fn game_version_list(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
query: web::Query<GameVersionQuery>,
|
query: web::Query<GameVersionQuery>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let results: Vec<GameVersionQueryData> = if query.type_.is_some()
|
let results: Vec<GameVersionQueryData> = if query.type_.is_some() || query.major.is_some() {
|
||||||
|| query.major.is_some()
|
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool).await?
|
||||||
{
|
|
||||||
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool)
|
|
||||||
.await?
|
|
||||||
} else {
|
} else {
|
||||||
GameVersion::list(&**pool).await?
|
GameVersion::list(&**pool).await?
|
||||||
}
|
}
|
||||||
@@ -145,9 +136,7 @@ pub struct LicenseText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("license/{id}")]
|
#[get("license/{id}")]
|
||||||
pub async fn license_text(
|
pub async fn license_text(params: web::Path<(String,)>) -> Result<HttpResponse, ApiError> {
|
||||||
params: web::Path<(String,)>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let license_id = params.into_inner().0;
|
let license_id = params.into_inner().0;
|
||||||
|
|
||||||
if license_id == *crate::models::projects::DEFAULT_LICENSE_ID {
|
if license_id == *crate::models::projects::DEFAULT_LICENSE_ID {
|
||||||
@@ -176,41 +165,32 @@ pub struct DonationPlatformQueryData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("donation_platform")]
|
#[get("donation_platform")]
|
||||||
pub async fn donation_platform_list(
|
pub async fn donation_platform_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
let results: Vec<DonationPlatformQueryData> = DonationPlatform::list(&**pool)
|
||||||
) -> Result<HttpResponse, ApiError> {
|
.await?
|
||||||
let results: Vec<DonationPlatformQueryData> =
|
.into_iter()
|
||||||
DonationPlatform::list(&**pool)
|
.map(|x| DonationPlatformQueryData {
|
||||||
.await?
|
short: x.short,
|
||||||
.into_iter()
|
name: x.name,
|
||||||
.map(|x| DonationPlatformQueryData {
|
})
|
||||||
short: x.short,
|
.collect();
|
||||||
name: x.name,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("report_type")]
|
#[get("report_type")]
|
||||||
pub async fn report_type_list(
|
pub async fn report_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let results = ReportType::list(&**pool).await?;
|
let results = ReportType::list(&**pool).await?;
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("project_type")]
|
#[get("project_type")]
|
||||||
pub async fn project_type_list(
|
pub async fn project_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let results = ProjectType::list(&**pool).await?;
|
let results = ProjectType::list(&**pool).await?;
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("side_type")]
|
#[get("side_type")]
|
||||||
pub async fn side_type_list(
|
pub async fn side_type_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
let results = SideType::list(&**pool).await?;
|
let results = SideType::list(&**pool).await?;
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,33 +33,23 @@ pub async fn team_members_get_project(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let project_data =
|
let project_data =
|
||||||
crate::database::models::Project::get_from_slug_or_project_id(
|
crate::database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(project) = project_data {
|
if let Some(project) = project_data {
|
||||||
let members_data =
|
let members_data = TeamMember::get_from_team_full(project.team_id, &**pool).await?;
|
||||||
TeamMember::get_from_team_full(project.team_id, &**pool).await?;
|
|
||||||
|
|
||||||
let current_user =
|
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
get_user_from_headers(req.headers(), &**pool).await.ok();
|
|
||||||
|
|
||||||
if let Some(user) = current_user {
|
if let Some(user) = current_user {
|
||||||
let team_member = TeamMember::get_from_user_id(
|
let team_member =
|
||||||
project.team_id,
|
TeamMember::get_from_user_id(project.team_id, user.id.into(), &**pool)
|
||||||
user.id.into(),
|
.await
|
||||||
&**pool,
|
.map_err(ApiError::Database)?;
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::Database)?;
|
|
||||||
|
|
||||||
if team_member.is_some() {
|
if team_member.is_some() {
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|data| {
|
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
||||||
crate::models::teams::TeamMember::from(data, false)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok().json(team_members));
|
return Ok(HttpResponse::Ok().json(team_members));
|
||||||
@@ -85,16 +75,14 @@ pub async fn team_members_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let members_data =
|
let members_data = TeamMember::get_from_team_full(id.into(), &**pool).await?;
|
||||||
TeamMember::get_from_team_full(id.into(), &**pool).await?;
|
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(user) = ¤t_user {
|
if let Some(user) = ¤t_user {
|
||||||
let team_member =
|
let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
||||||
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
.await
|
||||||
.await
|
.map_err(ApiError::Database)?;
|
||||||
.map_err(ApiError::Database)?;
|
|
||||||
|
|
||||||
if team_member.is_some() {
|
if team_member.is_some() {
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
@@ -139,8 +127,7 @@ pub async fn teams_get(
|
|||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
.collect::<Vec<crate::database::models::ids::TeamId>>();
|
||||||
|
|
||||||
let teams_data =
|
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
||||||
TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
let accepted = if let Some(user) = current_user {
|
let accepted = if let Some(user) = current_user {
|
||||||
@@ -159,9 +146,8 @@ pub async fn teams_get(
|
|||||||
|
|
||||||
for (id, member_data) in &teams_groups {
|
for (id, member_data) in &teams_groups {
|
||||||
if accepted.contains(&id) {
|
if accepted.contains(&id) {
|
||||||
let team_members = member_data.map(|data| {
|
let team_members =
|
||||||
crate::models::teams::TeamMember::from(data, false)
|
member_data.map(|data| crate::models::teams::TeamMember::from(data, false));
|
||||||
});
|
|
||||||
|
|
||||||
teams.push(team_members.collect());
|
teams.push(team_members.collect());
|
||||||
|
|
||||||
@@ -187,12 +173,8 @@ pub async fn join_team(
|
|||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let member = TeamMember::get_from_user_id_pending(
|
let member =
|
||||||
team_id,
|
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
||||||
current_user.id.into(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(member) = member {
|
if let Some(member) = member {
|
||||||
if member.accepted {
|
if member.accepted {
|
||||||
@@ -258,20 +240,17 @@ pub async fn add_team_member(
|
|||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let member =
|
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||||
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
.await?
|
||||||
.await?
|
.ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
ApiError::CustomAuthentication(
|
||||||
ApiError::CustomAuthentication(
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
"You don't have permission to edit members of this team"
|
)
|
||||||
.to_string(),
|
})?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to invite users to this team"
|
"You don't have permission to invite users to this team".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !member.permissions.contains(new_member.permissions) {
|
if !member.permissions.contains(new_member.permissions) {
|
||||||
@@ -286,9 +265,7 @@ pub async fn add_team_member(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_member.payouts_split < Decimal::ZERO
|
if new_member.payouts_split < Decimal::ZERO || new_member.payouts_split > Decimal::from(5000) {
|
||||||
|| new_member.payouts_split > Decimal::from(5000)
|
|
||||||
{
|
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Payouts split must be between 0 and 5000!".to_string(),
|
"Payouts split must be between 0 and 5000!".to_string(),
|
||||||
));
|
));
|
||||||
@@ -308,21 +285,16 @@ pub async fn add_team_member(
|
|||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"There is already a pending member request for this user"
|
"There is already a pending member request for this user".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::database::models::User::get(member.user_id, &**pool)
|
crate::database::models::User::get(member.user_id, &**pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| ApiError::InvalidInput("An invalid User ID specified".to_string()))?;
|
||||||
ApiError::InvalidInput("An invalid User ID specified".to_string())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let new_id =
|
let new_id = crate::database::models::ids::generate_team_member_id(&mut transaction).await?;
|
||||||
crate::database::models::ids::generate_team_member_id(&mut transaction)
|
|
||||||
.await?;
|
|
||||||
TeamMember {
|
TeamMember {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
team_id,
|
team_id,
|
||||||
@@ -383,24 +355,20 @@ pub async fn edit_team_member(
|
|||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let member =
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
.await?
|
||||||
.await?
|
.ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
ApiError::CustomAuthentication(
|
||||||
ApiError::CustomAuthentication(
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
"You don't have permission to edit members of this team"
|
)
|
||||||
.to_string(),
|
})?;
|
||||||
)
|
let edit_member_db = TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||||
})?;
|
.await?
|
||||||
let edit_member_db =
|
.ok_or_else(|| {
|
||||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
ApiError::CustomAuthentication(
|
||||||
.await?
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::CustomAuthentication(
|
})?;
|
||||||
"You don't have permission to edit members of this team"
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
@@ -408,30 +376,26 @@ pub async fn edit_team_member(
|
|||||||
&& (edit_member.role.is_some() || edit_member.permissions.is_some())
|
&& (edit_member.role.is_some() || edit_member.permissions.is_some())
|
||||||
{
|
{
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"The owner's permission and role of a team cannot be edited"
|
"The owner's permission and role of a team cannot be edited".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit members of this team"
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_permissions) = edit_member.permissions {
|
if let Some(new_permissions) = edit_member.permissions {
|
||||||
if !member.permissions.contains(new_permissions) {
|
if !member.permissions.contains(new_permissions) {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"The new permissions have permissions that you don't have"
|
"The new permissions have permissions that you don't have".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payouts_split) = edit_member.payouts_split {
|
if let Some(payouts_split) = edit_member.payouts_split {
|
||||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000)
|
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000) {
|
||||||
{
|
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Payouts split must be between 0 and 5000!".to_string(),
|
"Payouts split must be between 0 and 5000!".to_string(),
|
||||||
));
|
));
|
||||||
@@ -478,38 +442,26 @@ pub async fn transfer_ownership(
|
|||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
if !current_user.role.is_admin() {
|
if !current_user.role.is_admin() {
|
||||||
let member = TeamMember::get_from_user_id(
|
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
|
||||||
id.into(),
|
.await?
|
||||||
current_user.id.into(),
|
.ok_or_else(|| {
|
||||||
&**pool,
|
ApiError::CustomAuthentication(
|
||||||
)
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
.await?
|
)
|
||||||
.ok_or_else(|| {
|
})?;
|
||||||
ApiError::CustomAuthentication(
|
|
||||||
"You don't have permission to edit members of this team"
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if member.role != crate::models::teams::OWNER_ROLE {
|
if member.role != crate::models::teams::OWNER_ROLE {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit the ownership of this team"
|
"You don't have permission to edit the ownership of this team".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_member = TeamMember::get_from_user_id(
|
let new_member = TeamMember::get_from_user_id(id.into(), new_owner.user_id.into(), &**pool)
|
||||||
id.into(),
|
.await?
|
||||||
new_owner.user_id.into(),
|
.ok_or_else(|| {
|
||||||
&**pool,
|
ApiError::InvalidInput("The new owner specified does not exist".to_string())
|
||||||
)
|
})?;
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::InvalidInput(
|
|
||||||
"The new owner specified does not exist".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !new_member.accepted {
|
if !new_member.accepted {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -559,18 +511,15 @@ pub async fn remove_team_member(
|
|||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let member =
|
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
.await?
|
||||||
.await?
|
.ok_or_else(|| {
|
||||||
.ok_or_else(|| {
|
ApiError::CustomAuthentication(
|
||||||
ApiError::CustomAuthentication(
|
"You don't have permission to edit members of this team".to_string(),
|
||||||
"You don't have permission to edit members of this team"
|
)
|
||||||
.to_string(),
|
})?;
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let delete_member =
|
let delete_member = TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
||||||
TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(delete_member) = delete_member {
|
if let Some(delete_member) = delete_member {
|
||||||
if delete_member.role == crate::models::teams::OWNER_ROLE {
|
if delete_member.role == crate::models::teams::OWNER_ROLE {
|
||||||
@@ -586,8 +535,7 @@ pub async fn remove_team_member(
|
|||||||
// Members other than the owner can either leave the team, or be
|
// Members other than the owner can either leave the team, or be
|
||||||
// removed by a member with the REMOVE_MEMBER permission.
|
// removed by a member with the REMOVE_MEMBER permission.
|
||||||
if delete_member.user_id == member.user_id
|
if delete_member.user_id == member.user_id
|
||||||
|| (member.permissions.contains(Permissions::REMOVE_MEMBER)
|
|| (member.permissions.contains(Permissions::REMOVE_MEMBER) && member.accepted)
|
||||||
&& member.accepted)
|
|
||||||
{
|
{
|
||||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||||
} else {
|
} else {
|
||||||
@@ -596,8 +544,7 @@ pub async fn remove_team_member(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if delete_member.user_id == member.user_id
|
} else if delete_member.user_id == member.user_id
|
||||||
|| (member.permissions.contains(Permissions::MANAGE_INVITES)
|
|| (member.permissions.contains(Permissions::MANAGE_INVITES) && member.accepted)
|
||||||
&& member.accepted)
|
|
||||||
{
|
{
|
||||||
// This is a pending invite rather than a member, so the
|
// This is a pending invite rather than a member, so the
|
||||||
// user being invited or team members with the MANAGE_INVITES
|
// user being invited or team members with the MANAGE_INVITES
|
||||||
@@ -605,8 +552,7 @@ pub async fn remove_team_member(
|
|||||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have permission to cancel a team invite"
|
"You do not have permission to cancel a team invite".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ use crate::database::models::thread_item::ThreadMessageBuilder;
|
|||||||
use crate::models::ids::{ReportId, ThreadMessageId};
|
use crate::models::ids::{ReportId, ThreadMessageId};
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::projects::{ProjectId, ProjectStatus};
|
use crate::models::projects::{ProjectId, ProjectStatus};
|
||||||
use crate::models::threads::{
|
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadMessage, ThreadType};
|
||||||
MessageBody, Thread, ThreadId, ThreadMessage, ThreadType,
|
|
||||||
};
|
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::{
|
use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers};
|
||||||
check_is_moderator_from_headers, get_user_from_headers,
|
|
||||||
};
|
|
||||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -42,13 +38,13 @@ pub async fn is_authorized_thread(
|
|||||||
Ok(match thread.type_ {
|
Ok(match thread.type_ {
|
||||||
ThreadType::Report => {
|
ThreadType::Report => {
|
||||||
let report_exists = sqlx::query!(
|
let report_exists = sqlx::query!(
|
||||||
"SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)",
|
"SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)",
|
||||||
thread.id as database::models::ids::ThreadId,
|
thread.id as database::models::ids::ThreadId,
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?
|
.await?
|
||||||
.exists;
|
.exists;
|
||||||
|
|
||||||
report_exists.unwrap_or(false)
|
report_exists.unwrap_or(false)
|
||||||
}
|
}
|
||||||
@@ -80,8 +76,7 @@ pub async fn filter_authorized_threads(
|
|||||||
|
|
||||||
for thread in threads {
|
for thread in threads {
|
||||||
if user.role.is_mod()
|
if user.role.is_mod()
|
||||||
|| (thread.type_ == ThreadType::DirectMessage
|
|| (thread.type_ == ThreadType::DirectMessage && thread.members.contains(&user_id))
|
||||||
&& thread.members.contains(&user_id))
|
|
||||||
{
|
{
|
||||||
return_threads.push(thread);
|
return_threads.push(thread);
|
||||||
} else {
|
} else {
|
||||||
@@ -106,23 +101,23 @@ pub async fn filter_authorized_threads(
|
|||||||
&*project_thread_ids,
|
&*project_thread_ids,
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_many(&***pool)
|
.fetch_many(&***pool)
|
||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
check_threads.retain(|x| {
|
check_threads.retain(|x| {
|
||||||
let bool = Some(x.id.0) == row.thread_id;
|
let bool = Some(x.id.0) == row.thread_id;
|
||||||
|
|
||||||
if bool {
|
if bool {
|
||||||
return_threads.push(x.clone());
|
return_threads.push(x.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
!bool
|
!bool
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::future::ready(Ok(()))
|
futures::future::ready(Ok(()))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let report_thread_ids = check_threads
|
let report_thread_ids = check_threads
|
||||||
@@ -176,12 +171,11 @@ pub async fn filter_authorized_threads(
|
|||||||
.collect::<Vec<database::models::UserId>>(),
|
.collect::<Vec<database::models::UserId>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let users: Vec<User> =
|
let users: Vec<User> = database::models::User::get_many(&user_ids, &***pool)
|
||||||
database::models::User::get_many(&user_ids, &***pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(From::from)
|
||||||
.map(From::from)
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut final_threads = Vec::new();
|
let mut final_threads = Vec::new();
|
||||||
|
|
||||||
@@ -210,11 +204,7 @@ pub async fn filter_authorized_threads(
|
|||||||
Ok(final_threads)
|
Ok(final_threads)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_thread(
|
fn convert_thread(data: database::models::Thread, users: Vec<User>, user: &User) -> Thread {
|
||||||
data: database::models::Thread,
|
|
||||||
users: Vec<User>,
|
|
||||||
user: &User,
|
|
||||||
) -> Thread {
|
|
||||||
let thread_type = data.type_;
|
let thread_type = data.type_;
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
@@ -279,16 +269,13 @@ pub async fn thread_get(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let users: Vec<User> =
|
let users: Vec<User> = database::models::User::get_many(authors, &**pool)
|
||||||
database::models::User::get_many(authors, &**pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(From::from)
|
||||||
.map(From::from)
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
return Ok(
|
return Ok(HttpResponse::Ok().json(convert_thread(data, users, &user)));
|
||||||
HttpResponse::Ok().json(convert_thread(data, users, &user))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(HttpResponse::NotFound().body(""))
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
@@ -313,8 +300,7 @@ pub async fn threads_get(
|
|||||||
.map(|x| x.into())
|
.map(|x| x.into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let threads_data =
|
let threads_data = database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
||||||
database::models::Thread::get_many(&thread_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
||||||
|
|
||||||
@@ -356,17 +342,13 @@ pub async fn thread_send_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(replying_to) = replying_to {
|
if let Some(replying_to) = replying_to {
|
||||||
let thread_message = database::models::ThreadMessage::get(
|
let thread_message =
|
||||||
(*replying_to).into(),
|
database::models::ThreadMessage::get((*replying_to).into(), &**pool).await?;
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(thread_message) = thread_message {
|
if let Some(thread_message) = thread_message {
|
||||||
if thread_message.thread_id != string {
|
if thread_message.thread_id != string {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Message replied to is from another thread!"
|
"Message replied to is from another thread!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -394,17 +376,13 @@ pub async fn thread_send_message(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if report.as_ref().map(|x| x.closed).unwrap_or(false)
|
if report.as_ref().map(|x| x.closed).unwrap_or(false) && !user.role.is_mod() {
|
||||||
&& !user.role.is_mod()
|
|
||||||
{
|
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You may not reply to a closed report".to_string(),
|
"You may not reply to a closed report".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mod_notif, (user_notif, team_id)) = if thread.type_
|
let (mod_notif, (user_notif, team_id)) = if thread.type_ == ThreadType::Project {
|
||||||
== ThreadType::Project
|
|
||||||
{
|
|
||||||
let record = sqlx::query!(
|
let record = sqlx::query!(
|
||||||
"SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1",
|
"SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1",
|
||||||
thread.id as database::models::ids::ThreadId,
|
thread.id as database::models::ids::ThreadId,
|
||||||
@@ -422,7 +400,17 @@ pub async fn thread_send_message(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(false, (thread.type_ == ThreadType::Report, None))
|
(
|
||||||
|
!user.role.is_mod(),
|
||||||
|
(
|
||||||
|
thread.type_ == ThreadType::Report
|
||||||
|
&& !report
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.reporter == user.id.into())
|
||||||
|
.unwrap_or(false),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
@@ -498,14 +486,11 @@ pub async fn moderation_inbox(
|
|||||||
"
|
"
|
||||||
)
|
)
|
||||||
.fetch_many(&**pool)
|
.fetch_many(&**pool)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ThreadId(m.id))) })
|
||||||
Ok(e.right().map(|m| database::models::ThreadId(m.id)))
|
|
||||||
})
|
|
||||||
.try_collect::<Vec<database::models::ThreadId>>()
|
.try_collect::<Vec<database::models::ThreadId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let threads_data =
|
let threads_data = database::models::Thread::get_many(&ids, &**pool).await?;
|
||||||
database::models::Thread::get_many(&ids, &**pool).await?;
|
|
||||||
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
let threads = filter_authorized_threads(threads_data, &user, &pool).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(threads))
|
Ok(HttpResponse::Ok().json(threads))
|
||||||
@@ -546,11 +531,7 @@ pub async fn message_delete(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let result = database::models::ThreadMessage::get(
|
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;
|
||||||
info.into_inner().0.into(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(thread) = result {
|
if let Some(thread) = result {
|
||||||
if !user.role.is_mod() && thread.author_id != Some(user.id.into()) {
|
if !user.role.is_mod() && thread.author_id != Some(user.id.into()) {
|
||||||
@@ -560,11 +541,7 @@ pub async fn message_delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
database::models::ThreadMessage::remove_full(
|
database::models::ThreadMessage::remove_full(thread.id, &mut transaction).await?;
|
||||||
thread.id,
|
|
||||||
&mut transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ use crate::database::models::User;
|
|||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::Notification;
|
||||||
use crate::models::projects::Project;
|
use crate::models::projects::Project;
|
||||||
use crate::models::users::{
|
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
|
||||||
Badges, RecipientType, RecipientWallet, Role, UserId,
|
|
||||||
};
|
|
||||||
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::util::auth::get_user_from_headers;
|
use crate::util::auth::get_user_from_headers;
|
||||||
@@ -24,6 +22,7 @@ use validator::Validate;
|
|||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(user_auth_get);
|
cfg.service(user_auth_get);
|
||||||
|
cfg.service(user_data_get);
|
||||||
cfg.service(users_get);
|
cfg.service(users_get);
|
||||||
|
|
||||||
cfg.service(
|
cfg.service(
|
||||||
@@ -45,8 +44,44 @@ pub async fn user_auth_get(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok().json(get_user_from_headers(req.headers(), &**pool).await?))
|
||||||
.json(get_user_from_headers(req.headers(), &**pool).await?))
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct UserData {
|
||||||
|
pub notifs_count: u64,
|
||||||
|
pub followed_projects: Vec<crate::models::ids::ProjectId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("user_data")]
|
||||||
|
pub async fn user_data_get(
|
||||||
|
req: HttpRequest,
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
|
let data = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT COUNT(DISTINCT n.id) notifs_count, ARRAY_AGG(mf.mod_id) followed_projects FROM notifications n
|
||||||
|
LEFT OUTER JOIN mod_follows mf ON mf.follower_id = $1
|
||||||
|
WHERE user_id = $1 AND read = FALSE
|
||||||
|
",
|
||||||
|
user.id.0 as i64
|
||||||
|
).fetch_optional(&**pool).await?;
|
||||||
|
|
||||||
|
if let Some(data) = data {
|
||||||
|
Ok(HttpResponse::Ok().json(UserData {
|
||||||
|
notifs_count: data.notifs_count.map(|x| x as u64).unwrap_or(0),
|
||||||
|
followed_projects: data
|
||||||
|
.followed_projects
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| crate::models::ids::ProjectId(x as u64))
|
||||||
|
.collect(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -66,8 +101,7 @@ pub async fn users_get(
|
|||||||
|
|
||||||
let users_data = User::get_many(&user_ids, &**pool).await?;
|
let users_data = User::get_many(&user_ids, &**pool).await?;
|
||||||
|
|
||||||
let users: Vec<crate::models::users::User> =
|
let users: Vec<crate::models::users::User> = users_data.into_iter().map(From::from).collect();
|
||||||
users_data.into_iter().map(From::from).collect();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(users))
|
Ok(HttpResponse::Ok().json(users))
|
||||||
}
|
}
|
||||||
@@ -78,8 +112,7 @@ pub async fn user_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let id_option: Option<UserId> =
|
let id_option: Option<UserId> = serde_json::from_str(&format!("\"{string}\"")).ok();
|
||||||
serde_json::from_str(&format!("\"{string}\"")).ok();
|
|
||||||
|
|
||||||
let mut user_data;
|
let mut user_data;
|
||||||
|
|
||||||
@@ -109,8 +142,7 @@ pub async fn projects_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
let user_id: UserId = id.into();
|
||||||
@@ -121,13 +153,12 @@ pub async fn projects_list(
|
|||||||
|
|
||||||
let project_data = User::get_projects(id, &**pool).await?;
|
let project_data = User::get_projects(id, &**pool).await?;
|
||||||
|
|
||||||
let response: Vec<_> =
|
let response: Vec<_> = crate::database::Project::get_many_full(&project_data, &**pool)
|
||||||
crate::database::Project::get_many_full(&project_data, &**pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.filter(|x| can_view_private || x.inner.status.is_searchable())
|
||||||
.filter(|x| can_view_private || x.inner.status.is_searchable())
|
.map(Project::from)
|
||||||
.map(Project::from)
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
} else {
|
} else {
|
||||||
@@ -192,12 +223,11 @@ pub async fn user_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
new_user.validate().map_err(|err| {
|
new_user
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
let user_id: UserId = id.into();
|
||||||
@@ -320,23 +350,21 @@ pub async fn user_edit(
|
|||||||
|
|
||||||
if let Some(payout_data) = &new_user.payout_data {
|
if let Some(payout_data) = &new_user.payout_data {
|
||||||
if let Some(payout_data) = payout_data {
|
if let Some(payout_data) = payout_data {
|
||||||
if payout_data.payout_wallet_type
|
if payout_data.payout_wallet_type == RecipientType::UserHandle
|
||||||
== RecipientType::UserHandle
|
|
||||||
&& payout_data.payout_wallet == RecipientWallet::Paypal
|
&& payout_data.payout_wallet == RecipientWallet::Paypal
|
||||||
{
|
{
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You cannot use a paypal wallet with a user handle!"
|
"You cannot use a paypal wallet with a user handle!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !match payout_data.payout_wallet_type {
|
if !match payout_data.payout_wallet_type {
|
||||||
RecipientType::Email => validator::validate_email(
|
RecipientType::Email => {
|
||||||
&payout_data.payout_address,
|
validator::validate_email(&payout_data.payout_address)
|
||||||
),
|
}
|
||||||
RecipientType::Phone => validator::validate_phone(
|
RecipientType::Phone => {
|
||||||
&payout_data.payout_address,
|
validator::validate_phone(&payout_data.payout_address)
|
||||||
),
|
}
|
||||||
RecipientType::UserHandle => true,
|
RecipientType::UserHandle => true,
|
||||||
} {
|
} {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -350,8 +378,8 @@ pub async fn user_edit(
|
|||||||
",
|
",
|
||||||
id as crate::database::models::ids::UserId,
|
id as crate::database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_one(&mut *transaction)
|
.fetch_one(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if results.exists.unwrap_or(false) {
|
if results.exists.unwrap_or(false) {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -371,8 +399,8 @@ pub async fn user_edit(
|
|||||||
payout_data.payout_address,
|
payout_data.payout_address,
|
||||||
id as crate::database::models::ids::UserId,
|
id as crate::database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -382,8 +410,8 @@ pub async fn user_edit(
|
|||||||
",
|
",
|
||||||
id as crate::database::models::ids::UserId,
|
id as crate::database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,20 +441,15 @@ pub async fn user_icon_edit(
|
|||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) =
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
|
||||||
crate::util::ext::get_image_content_type(&ext.ext)
|
|
||||||
{
|
|
||||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if user.id != id.into() && !user.role.is_mod() {
|
if user.id != id.into() && !user.role.is_mod() {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to edit this user's icon."
|
"You don't have permission to edit this user's icon.".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,12 +475,8 @@ pub async fn user_icon_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = read_from_payload(
|
let bytes =
|
||||||
&mut payload,
|
read_from_payload(&mut payload, 2097152, "Icons must be smaller than 2MiB").await?;
|
||||||
2097152,
|
|
||||||
"Icons must be smaller than 2MiB",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||||
let upload_data = file_host
|
let upload_data = file_host
|
||||||
@@ -509,8 +528,7 @@ pub async fn user_delete(
|
|||||||
removal_type: web::Query<RemovalType>,
|
removal_type: web::Query<RemovalType>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_admin() && user.id != id.into() {
|
if !user.role.is_admin() && user.id != id.into() {
|
||||||
@@ -546,11 +564,7 @@ pub async fn user_follows(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
&info.into_inner().0,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_admin() && user.id != id.into() {
|
if !user.role.is_admin() && user.id != id.into() {
|
||||||
@@ -576,12 +590,11 @@ pub async fn user_follows(
|
|||||||
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let projects: Vec<_> =
|
let projects: Vec<_> = crate::database::Project::get_many_full(&project_ids, &**pool)
|
||||||
crate::database::Project::get_many_full(&project_ids, &**pool)
|
.await?
|
||||||
.await?
|
.into_iter()
|
||||||
.into_iter()
|
.map(Project::from)
|
||||||
.map(Project::from)
|
.collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
} else {
|
} else {
|
||||||
@@ -596,11 +609,7 @@ pub async fn user_notifications(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
&info.into_inner().0,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_admin() && user.id != id.into() {
|
if !user.role.is_admin() && user.id != id.into() {
|
||||||
@@ -638,14 +647,12 @@ pub async fn user_payouts(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_admin() && user.id != id.into() {
|
if !user.role.is_admin() && user.id != id.into() {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have permission to see the payouts of this user!"
|
"You do not have permission to see the payouts of this user!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,22 +724,18 @@ pub async fn user_payouts_request(
|
|||||||
let mut payouts_queue = payouts_queue.lock().await;
|
let mut payouts_queue = payouts_queue.lock().await;
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
||||||
User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_admin() && user.id != id.into() {
|
if !user.role.is_admin() && user.id != id.into() {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have permission to request payouts of this user!"
|
"You do not have permission to request payouts of this user!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payouts_data) = user.payout_data {
|
if let Some(payouts_data) = user.payout_data {
|
||||||
if let Some(payout_address) = payouts_data.payout_address {
|
if let Some(payout_address) = payouts_data.payout_address {
|
||||||
if let Some(payout_wallet_type) =
|
if let Some(payout_wallet_type) = payouts_data.payout_wallet_type {
|
||||||
payouts_data.payout_wallet_type
|
|
||||||
{
|
|
||||||
if let Some(payout_wallet) = payouts_data.payout_wallet {
|
if let Some(payout_wallet) = payouts_data.payout_wallet {
|
||||||
return if data.amount < payouts_data.balance {
|
return if data.amount < payouts_data.balance {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
@@ -744,10 +747,15 @@ pub async fn user_payouts_request(
|
|||||||
value: data.amount,
|
value: data.amount,
|
||||||
},
|
},
|
||||||
receiver: payout_address,
|
receiver: payout_address,
|
||||||
note: "Payment from Modrinth creator monetization program".to_string(),
|
note: "Payment from Modrinth creator monetization program"
|
||||||
|
.to_string(),
|
||||||
recipient_type: payout_wallet_type.to_string().to_uppercase(),
|
recipient_type: payout_wallet_type.to_string().to_uppercase(),
|
||||||
recipient_wallet: payout_wallet.as_str_api().to_string(),
|
recipient_wallet: payout_wallet.as_str_api().to_string(),
|
||||||
sender_item_id: format!("{}-{}", UserId::from(id), Utc::now().timestamp()),
|
sender_item_id: format!(
|
||||||
|
"{}-{}",
|
||||||
|
UserId::from(id),
|
||||||
|
Utc::now().timestamp()
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -760,8 +768,8 @@ pub async fn user_payouts_request(
|
|||||||
data.amount,
|
data.amount,
|
||||||
"success"
|
"success"
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -780,8 +788,7 @@ pub async fn user_payouts_request(
|
|||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::InvalidInput(
|
Err(ApiError::InvalidInput(
|
||||||
"You do not have enough funds to make this payout!"
|
"You do not have enough funds to make this payout!".to_string(),
|
||||||
.to_string(),
|
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ use crate::file_hosting::FileHost;
|
|||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::pack::PackFileHash;
|
use crate::models::pack::PackFileHash;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId,
|
Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, Version, VersionFile,
|
||||||
Version, VersionFile, VersionId, VersionStatus, VersionType,
|
VersionId, VersionStatus, VersionType,
|
||||||
};
|
};
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::util::auth::get_user_from_headers;
|
use crate::util::auth::get_user_from_headers;
|
||||||
@@ -97,11 +97,8 @@ pub async fn version_create(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
let undo_result = super::project_creation::undo_uploads(
|
let undo_result =
|
||||||
&***file_host,
|
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||||
&uploaded_files,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let rollback_result = transaction.rollback().await;
|
let rollback_result = transaction.rollback().await;
|
||||||
|
|
||||||
undo_result?;
|
undo_result?;
|
||||||
@@ -127,10 +124,8 @@ async fn version_create_inner(
|
|||||||
let mut initial_version_data = None;
|
let mut initial_version_data = None;
|
||||||
let mut version_builder = None;
|
let mut version_builder = None;
|
||||||
|
|
||||||
let all_game_versions =
|
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
||||||
let all_loaders =
|
|
||||||
models::categories::Loader::list(&mut *transaction).await?;
|
|
||||||
|
|
||||||
let user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
let user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||||
|
|
||||||
@@ -145,9 +140,7 @@ async fn version_create_inner(
|
|||||||
let result = async {
|
let result = async {
|
||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
let name = content_disposition.get_name().ok_or_else(|| {
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
CreateError::MissingValueError(
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
"Missing content name".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if name == "data" {
|
if name == "data" {
|
||||||
@@ -156,11 +149,9 @@ async fn version_create_inner(
|
|||||||
data.extend_from_slice(&chunk?);
|
data.extend_from_slice(&chunk?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_create_data: InitialVersionData =
|
let version_create_data: InitialVersionData = serde_json::from_slice(&data)?;
|
||||||
serde_json::from_slice(&data)?;
|
|
||||||
initial_version_data = Some(version_create_data);
|
initial_version_data = Some(version_create_data);
|
||||||
let version_create_data =
|
let version_create_data = initial_version_data.as_ref().unwrap();
|
||||||
initial_version_data.as_ref().unwrap();
|
|
||||||
if version_create_data.project_id.is_none() {
|
if version_create_data.project_id.is_none() {
|
||||||
return Err(CreateError::MissingValueError(
|
return Err(CreateError::MissingValueError(
|
||||||
"Missing project id".to_string(),
|
"Missing project id".to_string(),
|
||||||
@@ -168,9 +159,7 @@ async fn version_create_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
version_create_data.validate().map_err(|err| {
|
version_create_data.validate().map_err(|err| {
|
||||||
CreateError::ValidationError(validation_errors_to_string(
|
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||||
err, None,
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !version_create_data.status.can_be_requested() {
|
if !version_create_data.status.can_be_requested() {
|
||||||
@@ -179,8 +168,7 @@ async fn version_create_inner(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_id: models::ProjectId =
|
let project_id: models::ProjectId = version_create_data.project_id.unwrap().into();
|
||||||
version_create_data.project_id.unwrap().into();
|
|
||||||
|
|
||||||
// Ensure that the project this version is being added to exists
|
// Ensure that the project this version is being added to exists
|
||||||
let results = sqlx::query!(
|
let results = sqlx::query!(
|
||||||
@@ -206,8 +194,7 @@ async fn version_create_inner(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::CustomAuthenticationError(
|
CreateError::CustomAuthenticationError(
|
||||||
"You don't have permission to upload this version!"
|
"You don't have permission to upload this version!".to_string(),
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -216,13 +203,11 @@ async fn version_create_inner(
|
|||||||
.contains(Permissions::UPLOAD_VERSION)
|
.contains(Permissions::UPLOAD_VERSION)
|
||||||
{
|
{
|
||||||
return Err(CreateError::CustomAuthenticationError(
|
return Err(CreateError::CustomAuthenticationError(
|
||||||
"You don't have permission to upload this version!"
|
"You don't have permission to upload this version!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_id: VersionId =
|
let version_id: VersionId = models::generate_version_id(transaction).await?.into();
|
||||||
models::generate_version_id(transaction).await?.into();
|
|
||||||
|
|
||||||
let project_type = sqlx::query!(
|
let project_type = sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -243,13 +228,10 @@ async fn version_create_inner(
|
|||||||
all_game_versions
|
all_game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|y| y.version == x.0)
|
.find(|y| y.version == x.0)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| CreateError::InvalidGameVersion(x.0.clone()))
|
||||||
CreateError::InvalidGameVersion(x.0.clone())
|
|
||||||
})
|
|
||||||
.map(|y| y.id)
|
.map(|y| y.id)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<models::GameVersionId>, CreateError>>(
|
.collect::<Result<Vec<models::GameVersionId>, CreateError>>()?;
|
||||||
)?;
|
|
||||||
|
|
||||||
let loaders = version_create_data
|
let loaders = version_create_data
|
||||||
.loaders
|
.loaders
|
||||||
@@ -258,13 +240,9 @@ async fn version_create_inner(
|
|||||||
all_loaders
|
all_loaders
|
||||||
.iter()
|
.iter()
|
||||||
.find(|y| {
|
.find(|y| {
|
||||||
y.loader == x.0
|
y.loader == x.0 && y.supported_project_types.contains(&project_type)
|
||||||
&& y.supported_project_types
|
|
||||||
.contains(&project_type)
|
|
||||||
})
|
|
||||||
.ok_or_else(|| {
|
|
||||||
CreateError::InvalidLoader(x.0.clone())
|
|
||||||
})
|
})
|
||||||
|
.ok_or_else(|| CreateError::InvalidLoader(x.0.clone()))
|
||||||
.map(|y| y.id)
|
.map(|y| y.id)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<models::LoaderId>, CreateError>>()?;
|
.collect::<Result<Vec<models::LoaderId>, CreateError>>()?;
|
||||||
@@ -286,17 +264,12 @@ async fn version_create_inner(
|
|||||||
author_id: user.id.into(),
|
author_id: user.id.into(),
|
||||||
name: version_create_data.version_title.clone(),
|
name: version_create_data.version_title.clone(),
|
||||||
version_number: version_create_data.version_number.clone(),
|
version_number: version_create_data.version_number.clone(),
|
||||||
changelog: version_create_data
|
changelog: version_create_data.version_body.clone().unwrap_or_default(),
|
||||||
.version_body
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
dependencies,
|
dependencies,
|
||||||
game_versions,
|
game_versions,
|
||||||
loaders,
|
loaders,
|
||||||
version_type: version_create_data
|
version_type: version_create_data.release_channel.to_string(),
|
||||||
.release_channel
|
|
||||||
.to_string(),
|
|
||||||
featured: version_create_data.featured,
|
featured: version_create_data.featured,
|
||||||
status: version_create_data.status,
|
status: version_create_data.status,
|
||||||
requested_status: None,
|
requested_status: None,
|
||||||
@@ -306,9 +279,7 @@ async fn version_create_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let version = version_builder.as_mut().ok_or_else(|| {
|
let version = version_builder.as_mut().ok_or_else(|| {
|
||||||
CreateError::InvalidInput(String::from(
|
CreateError::InvalidInput(String::from("`data` field must come before file fields"))
|
||||||
"`data` field must come before file fields",
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let project_type = sqlx::query!(
|
let project_type = sqlx::query!(
|
||||||
@@ -323,12 +294,9 @@ async fn version_create_inner(
|
|||||||
.await?
|
.await?
|
||||||
.name;
|
.name;
|
||||||
|
|
||||||
let version_data =
|
let version_data = initial_version_data
|
||||||
initial_version_data.clone().ok_or_else(|| {
|
.clone()
|
||||||
CreateError::InvalidInput(
|
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||||
"`data` field is required".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
upload_file(
|
upload_file(
|
||||||
&mut field,
|
&mut field,
|
||||||
@@ -365,12 +333,10 @@ async fn version_create_inner(
|
|||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_data = initial_version_data.ok_or_else(|| {
|
let version_data = initial_version_data
|
||||||
CreateError::InvalidInput("`data` field is required".to_string())
|
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||||
})?;
|
let builder = version_builder
|
||||||
let builder = version_builder.ok_or_else(|| {
|
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||||
CreateError::InvalidInput("`data` field is required".to_string())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if builder.files.is_empty() {
|
if builder.files.is_empty() {
|
||||||
return Err(CreateError::InvalidInput(
|
return Err(CreateError::InvalidInput(
|
||||||
@@ -388,9 +354,7 @@ async fn version_create_inner(
|
|||||||
builder.project_id as crate::database::models::ids::ProjectId
|
builder.project_id as crate::database::models::ids::ProjectId
|
||||||
)
|
)
|
||||||
.fetch_many(&mut *transaction)
|
.fetch_many(&mut *transaction)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async { Ok(e.right().map(|m| models::ids::UserId(m.follower_id))) })
|
||||||
Ok(e.right().map(|m| models::ids::UserId(m.follower_id)))
|
|
||||||
})
|
|
||||||
.try_collect::<Vec<models::ids::UserId>>()
|
.try_collect::<Vec<models::ids::UserId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -453,8 +417,7 @@ async fn version_create_inner(
|
|||||||
let project_id = builder.project_id;
|
let project_id = builder.project_id;
|
||||||
builder.insert(transaction).await?;
|
builder.insert(transaction).await?;
|
||||||
|
|
||||||
models::Project::update_game_versions(project_id, &mut *transaction)
|
models::Project::update_game_versions(project_id, &mut *transaction).await?;
|
||||||
.await?;
|
|
||||||
models::Project::update_loaders(project_id, &mut *transaction).await?;
|
models::Project::update_loaders(project_id, &mut *transaction).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
@@ -486,11 +449,8 @@ pub async fn upload_file_to_version(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
let undo_result = super::project_creation::undo_uploads(
|
let undo_result =
|
||||||
&***file_host,
|
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
||||||
&uploaded_files,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let rollback_result = transaction.rollback().await;
|
let rollback_result = transaction.rollback().await;
|
||||||
|
|
||||||
undo_result?;
|
undo_result?;
|
||||||
@@ -541,8 +501,7 @@ async fn upload_file_to_version_inner(
|
|||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::CustomAuthenticationError(
|
CreateError::CustomAuthenticationError(
|
||||||
"You don't have permission to upload files to this version!"
|
"You don't have permission to upload files to this version!".to_string(),
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -551,8 +510,7 @@ async fn upload_file_to_version_inner(
|
|||||||
.contains(Permissions::UPLOAD_VERSION)
|
.contains(Permissions::UPLOAD_VERSION)
|
||||||
{
|
{
|
||||||
return Err(CreateError::CustomAuthenticationError(
|
return Err(CreateError::CustomAuthenticationError(
|
||||||
"You don't have permission to upload files to this version!"
|
"You don't have permission to upload files to this version!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,8 +529,7 @@ async fn upload_file_to_version_inner(
|
|||||||
.await?
|
.await?
|
||||||
.name;
|
.name;
|
||||||
|
|
||||||
let all_game_versions =
|
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
||||||
models::categories::GameVersion::list(&mut *transaction).await?;
|
|
||||||
|
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
@@ -585,9 +542,7 @@ async fn upload_file_to_version_inner(
|
|||||||
let result = async {
|
let result = async {
|
||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
let name = content_disposition.get_name().ok_or_else(|| {
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
CreateError::MissingValueError(
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
"Missing content name".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if name == "data" {
|
if name == "data" {
|
||||||
@@ -602,9 +557,7 @@ async fn upload_file_to_version_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_data = initial_file_data.as_ref().ok_or_else(|| {
|
let file_data = initial_file_data.as_ref().ok_or_else(|| {
|
||||||
CreateError::InvalidInput(String::from(
|
CreateError::InvalidInput(String::from("`data` field must come before file fields"))
|
||||||
"`data` field must come before file fields",
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut dependencies = version
|
let mut dependencies = version
|
||||||
@@ -703,9 +656,7 @@ pub async fn upload_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
let content_type = crate::util::ext::project_file_type(file_extension)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||||
CreateError::InvalidFileType(file_extension.to_string())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let data = read_from_field(
|
let data = read_from_field(
|
||||||
field, 500 * (1 << 20),
|
field, 500 * (1 << 20),
|
||||||
@@ -731,8 +682,7 @@ pub async fn upload_file(
|
|||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
return Err(CreateError::InvalidInput(
|
return Err(CreateError::InvalidInput(
|
||||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,23 +711,20 @@ pub async fn upload_file(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let res = sqlx::query!(
|
let res = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h
|
SELECT v.id version_id, v.mod_id project_id, h.hash hash FROM hashes h
|
||||||
INNER JOIN files f on h.file_id = f.id
|
INNER JOIN files f on h.file_id = f.id
|
||||||
INNER JOIN versions v on f.version_id = v.id
|
INNER JOIN versions v on f.version_id = v.id
|
||||||
WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)
|
WHERE h.algorithm = 'sha1' AND h.hash = ANY($1)
|
||||||
",
|
",
|
||||||
&*hashes
|
&*hashes
|
||||||
)
|
)
|
||||||
.fetch_all(&mut *transaction).await?;
|
.fetch_all(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
for file in &format.files {
|
for file in &format.files {
|
||||||
if let Some(dep) = res.iter().find(|x| {
|
if let Some(dep) = res.iter().find(|x| {
|
||||||
Some(&*x.hash)
|
Some(&*x.hash) == file.hashes.get(&PackFileHash::Sha1).map(|x| x.as_bytes())
|
||||||
== file
|
|
||||||
.hashes
|
|
||||||
.get(&PackFileHash::Sha1)
|
|
||||||
.map(|x| x.as_bytes())
|
|
||||||
}) {
|
}) {
|
||||||
dependencies.push(DependencyBuilder {
|
dependencies.push(DependencyBuilder {
|
||||||
project_id: Some(models::ProjectId(dep.project_id)),
|
project_id: Some(models::ProjectId(dep.project_id)),
|
||||||
@@ -828,8 +775,7 @@ pub async fn upload_file(
|
|||||||
version_id,
|
version_id,
|
||||||
urlencoding::encode(file_name)
|
urlencoding::encode(file_name)
|
||||||
);
|
);
|
||||||
let file_path =
|
let file_path = format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||||
format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
|
||||||
|
|
||||||
let upload_data = file_host
|
let upload_data = file_host
|
||||||
.upload_file(content_type, &file_path, data)
|
.upload_file(content_type, &file_path, data)
|
||||||
@@ -849,8 +795,7 @@ pub async fn upload_file(
|
|||||||
.any(|y| y.hash == sha1_bytes || y.hash == sha512_bytes)
|
.any(|y| y.hash == sha1_bytes || y.hash == sha512_bytes)
|
||||||
}) {
|
}) {
|
||||||
return Err(CreateError::InvalidInput(
|
return Err(CreateError::InvalidInput(
|
||||||
"Duplicate files are not allowed to be uploaded to Modrinth!"
|
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,9 +833,9 @@ pub async fn upload_file(
|
|||||||
pub fn get_name_ext(
|
pub fn get_name_ext(
|
||||||
content_disposition: &actix_web::http::header::ContentDisposition,
|
content_disposition: &actix_web::http::header::ContentDisposition,
|
||||||
) -> Result<(&str, &str), CreateError> {
|
) -> Result<(&str, &str), CreateError> {
|
||||||
let file_name = content_disposition.get_filename().ok_or_else(|| {
|
let file_name = content_disposition
|
||||||
CreateError::MissingValueError("Missing content file name".to_string())
|
.get_filename()
|
||||||
})?;
|
.ok_or_else(|| CreateError::MissingValueError("Missing content file name".to_string()))?;
|
||||||
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
||||||
file_name.get((last_period + 1)..).unwrap_or("")
|
file_name.get((last_period + 1)..).unwrap_or("")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -84,8 +84,7 @@ pub async fn get_version_from_hash(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|x| database::models::VersionId(x.version_id))
|
.map(|x| database::models::VersionId(x.version_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let versions_data =
|
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(first) = versions_data.first() {
|
if let Some(first) = versions_data.first() {
|
||||||
if hash_query.multiple {
|
if hash_query.multiple {
|
||||||
@@ -96,8 +95,7 @@ pub async fn get_version_from_hash(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok().json(models::projects::Version::from(first.clone())))
|
||||||
.json(models::projects::Version::from(first.clone())))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(HttpResponse::NotFound().body(""))
|
Ok(HttpResponse::NotFound().body(""))
|
||||||
@@ -128,10 +126,16 @@ pub async fn download_version(
|
|||||||
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||||
ORDER BY v.date_published ASC
|
ORDER BY v.date_published ASC
|
||||||
",
|
",
|
||||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hash.as_bytes(),
|
hash.as_bytes(),
|
||||||
hash_query.algorithm,
|
hash_query.algorithm,
|
||||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -178,28 +182,25 @@ pub async fn delete_file(
|
|||||||
|| Some(x.version_id) == hash_query.version_id.map(|x| x.0 as i64)
|
|| Some(x.version_id) == hash_query.version_id.map(|x| x.0 as i64)
|
||||||
}) {
|
}) {
|
||||||
if !user.role.is_admin() {
|
if !user.role.is_admin() {
|
||||||
let team_member =
|
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||||
database::models::TeamMember::get_from_user_id_version(
|
database::models::ids::VersionId(row.version_id),
|
||||||
database::models::ids::VersionId(row.version_id),
|
user.id.into(),
|
||||||
user.id.into(),
|
&**pool,
|
||||||
&**pool,
|
)
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::Database)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::CustomAuthentication(
|
||||||
|
"You don't have permission to delete this file!".to_string(),
|
||||||
)
|
)
|
||||||
.await
|
})?;
|
||||||
.map_err(ApiError::Database)?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::CustomAuthentication(
|
|
||||||
"You don't have permission to delete this file!"
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if !team_member
|
if !team_member
|
||||||
.permissions
|
.permissions
|
||||||
.contains(Permissions::DELETE_VERSION)
|
.contains(Permissions::DELETE_VERSION)
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to delete this file!"
|
"You don't have permission to delete this file!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,8 +221,7 @@ pub async fn delete_file(
|
|||||||
|
|
||||||
if files.len() < 2 {
|
if files.len() < 2 {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Versions must have at least one file uploaded to them"
|
"Versions must have at least one file uploaded to them".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,9 +324,7 @@ pub async fn get_update_from_hash(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(version_id) = version_ids.first() {
|
if let Some(version_id) = version_ids.first() {
|
||||||
let version_data =
|
let version_data = database::models::Version::get_full(*version_id, &**pool).await?;
|
||||||
database::models::Version::get_full(*version_id, &**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
ok_or_not_found::<QueryVersion, Version>(version_data)
|
ok_or_not_found::<QueryVersion, Version>(version_data)
|
||||||
} else {
|
} else {
|
||||||
@@ -364,10 +362,16 @@ pub async fn get_versions_from_hashes(
|
|||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
",
|
",
|
||||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
file_data.algorithm,
|
file_data.algorithm,
|
||||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&**pool)
|
.fetch_all(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -376,8 +380,7 @@ pub async fn get_versions_from_hashes(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|x| database::models::VersionId(x.version_id))
|
.map(|x| database::models::VersionId(x.version_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let versions_data =
|
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let response: Result<HashMap<String, Version>, ApiError> = result
|
let response: Result<HashMap<String, Version>, ApiError> = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -388,10 +391,7 @@ pub async fn get_versions_from_hashes(
|
|||||||
.find(|x| x.inner.id.0 == row.version_id)
|
.find(|x| x.inner.id.0 == row.version_id)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
||||||
Ok((
|
Ok((parsed_hash, crate::models::projects::Version::from(v)))
|
||||||
parsed_hash,
|
|
||||||
crate::models::projects::Version::from(v),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::Database(DatabaseError::Other(format!(
|
Err(ApiError::Database(DatabaseError::Other(format!(
|
||||||
"Could not parse hash for version {}",
|
"Could not parse hash for version {}",
|
||||||
@@ -423,20 +423,25 @@ pub async fn get_projects_from_hashes(
|
|||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
",
|
",
|
||||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
file_data.algorithm,
|
file_data.algorithm,
|
||||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_all(&**pool)
|
.fetch_all(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let project_ids = result
|
let project_ids = result
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| database::models::ProjectId(x.project_id))
|
.map(|x| database::models::ProjectId(x.project_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let versions_data =
|
let versions_data = database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
||||||
database::models::Project::get_many_full(&project_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let response: Result<HashMap<String, Project>, ApiError> = result
|
let response: Result<HashMap<String, Project>, ApiError> = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -447,10 +452,7 @@ pub async fn get_projects_from_hashes(
|
|||||||
.find(|x| x.inner.id.0 == row.project_id)
|
.find(|x| x.inner.id.0 == row.project_id)
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
if let Ok(parsed_hash) = String::from_utf8(row.hash) {
|
||||||
Ok((
|
Ok((parsed_hash, crate::models::projects::Project::from(v)))
|
||||||
parsed_hash,
|
|
||||||
crate::models::projects::Project::from(v),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::Database(DatabaseError::Other(format!(
|
Err(ApiError::Database(DatabaseError::Other(format!(
|
||||||
"Could not parse hash for version {}",
|
"Could not parse hash for version {}",
|
||||||
@@ -538,20 +540,26 @@ pub async fn update_files(
|
|||||||
INNER JOIN mods m on v.mod_id = m.id
|
INNER JOIN mods m on v.mod_id = m.id
|
||||||
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)
|
||||||
",
|
",
|
||||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::VersionStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
hashes_parsed.as_slice(),
|
hashes_parsed.as_slice(),
|
||||||
update_data.algorithm,
|
update_data.algorithm,
|
||||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
&*crate::models::projects::ProjectStatus::iterator()
|
||||||
|
.filter(|x| x.is_hidden())
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.fetch_many(&mut *transaction)
|
.fetch_many(&mut *transaction)
|
||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id))))
|
Ok(e.right()
|
||||||
})
|
.map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id))))
|
||||||
.try_collect::<Vec<_>>()
|
})
|
||||||
.await?;
|
.try_collect::<Vec<_>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut version_ids: HashMap<database::models::VersionId, Vec<u8>> =
|
let mut version_ids: HashMap<database::models::VersionId, Vec<u8>> = HashMap::new();
|
||||||
HashMap::new();
|
|
||||||
|
|
||||||
let updated_versions = database::models::Version::get_projects_versions(
|
let updated_versions = database::models::Version::get_projects_versions(
|
||||||
result
|
result
|
||||||
@@ -583,17 +591,13 @@ pub async fn update_files(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (hash, id) in result {
|
for (hash, id) in result {
|
||||||
if let Some(latest_version) =
|
if let Some(latest_version) = updated_versions.get(&id).and_then(|x| x.last()) {
|
||||||
updated_versions.get(&id).and_then(|x| x.last())
|
|
||||||
{
|
|
||||||
version_ids.insert(*latest_version, hash);
|
version_ids.insert(*latest_version, hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let query_version_ids = version_ids.keys().copied().collect::<Vec<_>>();
|
let query_version_ids = version_ids.keys().copied().collect::<Vec<_>>();
|
||||||
let versions =
|
let versions = database::models::Version::get_many_full(&query_version_ids, &**pool).await?;
|
||||||
database::models::Version::get_many_full(&query_version_ids, &**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut response = HashMap::new();
|
let mut response = HashMap::new();
|
||||||
|
|
||||||
@@ -602,10 +606,7 @@ pub async fn update_files(
|
|||||||
|
|
||||||
if let Some(hash) = hash {
|
if let Some(hash) = hash {
|
||||||
if let Ok(parsed_hash) = String::from_utf8(hash.clone()) {
|
if let Ok(parsed_hash) = String::from_utf8(hash.clone()) {
|
||||||
response.insert(
|
response.insert(parsed_hash, models::projects::Version::from(version));
|
||||||
parsed_hash,
|
|
||||||
models::projects::Version::from(version),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let version_id: VersionId = version.inner.id.into();
|
let version_id: VersionId = version.inner.id.into();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
|
||||||
Dependency, FileType, VersionStatus, VersionType,
|
|
||||||
};
|
|
||||||
use crate::models::teams::Permissions;
|
use crate::models::teams::Permissions;
|
||||||
use crate::util::auth::{
|
use crate::util::auth::{
|
||||||
filter_authorized_versions, get_user_from_headers, is_authorized,
|
filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version,
|
||||||
is_authorized_version,
|
|
||||||
};
|
};
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||||
@@ -49,10 +46,7 @@ pub async fn version_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(
|
let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?;
|
||||||
&string, &**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -80,9 +74,7 @@ pub async fn version_list(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut versions =
|
let mut versions = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||||
database::models::Version::get_many_full(&version_ids, &**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut response = versions
|
let mut response = versions
|
||||||
.iter()
|
.iter()
|
||||||
@@ -95,22 +87,13 @@ pub async fn version_list(
|
|||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
versions.sort_by(|a, b| {
|
versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||||
b.inner.date_published.cmp(&a.inner.date_published)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attempt to populate versions with "auto featured" versions
|
// Attempt to populate versions with "auto featured" versions
|
||||||
if response.is_empty()
|
if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) {
|
||||||
&& !versions.is_empty()
|
|
||||||
&& filters.featured.unwrap_or(false)
|
|
||||||
{
|
|
||||||
let (loaders, game_versions) = futures::future::try_join(
|
let (loaders, game_versions) = futures::future::try_join(
|
||||||
database::models::categories::Loader::list(&**pool),
|
database::models::categories::Loader::list(&**pool),
|
||||||
database::models::categories::GameVersion::list_filter(
|
database::models::categories::GameVersion::list_filter(None, Some(true), &**pool),
|
||||||
None,
|
|
||||||
Some(true),
|
|
||||||
&**pool,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -139,13 +122,10 @@ pub async fn version_list(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.sort_by(|a, b| {
|
response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published));
|
||||||
b.inner.date_published.cmp(&a.inner.date_published)
|
|
||||||
});
|
|
||||||
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
response.dedup_by(|a, b| a.inner.id == b.inner.id);
|
||||||
|
|
||||||
let response =
|
let response = filter_authorized_versions(response, &user_option, &pool).await?;
|
||||||
filter_authorized_versions(response, &user_option, &pool).await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
} else {
|
} else {
|
||||||
@@ -162,16 +142,13 @@ pub async fn version_project_get(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner();
|
let id = info.into_inner();
|
||||||
let version_data =
|
let version_data =
|
||||||
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool)
|
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool).await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data {
|
||||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||||
return Ok(
|
return Ok(HttpResponse::Ok().json(models::projects::Version::from(data)));
|
||||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,18 +166,15 @@ pub async fn versions_get(
|
|||||||
web::Query(ids): web::Query<VersionIds>,
|
web::Query(ids): web::Query<VersionIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let version_ids =
|
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
||||||
serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
|
.into_iter()
|
||||||
.into_iter()
|
.map(|x| x.into())
|
||||||
.map(|x| x.into())
|
.collect::<Vec<database::models::VersionId>>();
|
||||||
.collect::<Vec<database::models::VersionId>>();
|
let versions_data = database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
||||||
let versions_data =
|
|
||||||
database::models::Version::get_many_full(&version_ids, &**pool).await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let versions =
|
let versions = filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
||||||
filter_authorized_versions(versions_data, &user_option, &pool).await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(versions))
|
Ok(HttpResponse::Ok().json(versions))
|
||||||
}
|
}
|
||||||
@@ -212,16 +186,13 @@ pub async fn version_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let version_data =
|
let version_data = database::models::Version::get_full(id.into(), &**pool).await?;
|
||||||
database::models::Version::get_full(id.into(), &**pool).await?;
|
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data {
|
||||||
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
if is_authorized_version(&data.inner, &user_option, &pool).await? {
|
||||||
return Ok(
|
return Ok(HttpResponse::Ok().json(models::projects::Version::from(data)));
|
||||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,9 +244,9 @@ pub async fn version_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
new_version.validate().map_err(|err| {
|
new_version
|
||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
.validate()
|
||||||
})?;
|
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let version_id = info.into_inner().0;
|
let version_id = info.into_inner().0;
|
||||||
let id = version_id.into();
|
let id = version_id.into();
|
||||||
@@ -283,19 +254,15 @@ pub async fn version_edit(
|
|||||||
let result = database::models::Version::get_full(id, &**pool).await?;
|
let result = database::models::Version::get_full(id, &**pool).await?;
|
||||||
|
|
||||||
if let Some(version_item) = result {
|
if let Some(version_item) = result {
|
||||||
let project_item = database::models::Project::get_full(
|
let project_item =
|
||||||
version_item.inner.project_id,
|
database::models::Project::get_full(version_item.inner.project_id, &**pool).await?;
|
||||||
|
|
||||||
|
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||||
|
version_item.inner.id,
|
||||||
|
user.id.into(),
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let team_member =
|
|
||||||
database::models::TeamMember::get_from_user_id_version(
|
|
||||||
version_item.inner.id,
|
|
||||||
user.id.into(),
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let permissions;
|
let permissions;
|
||||||
|
|
||||||
if user.role.is_admin() {
|
if user.role.is_admin() {
|
||||||
@@ -303,8 +270,7 @@ pub async fn version_edit(
|
|||||||
} else if let Some(member) = team_member {
|
} else if let Some(member) = team_member {
|
||||||
permissions = Some(member.permissions)
|
permissions = Some(member.permissions)
|
||||||
} else if user.role.is_mod() {
|
} else if user.role.is_mod() {
|
||||||
permissions =
|
permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
||||||
Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY)
|
|
||||||
} else {
|
} else {
|
||||||
permissions = None
|
permissions = None
|
||||||
}
|
}
|
||||||
@@ -312,8 +278,7 @@ pub async fn version_edit(
|
|||||||
if let Some(perms) = permissions {
|
if let Some(perms) = permissions {
|
||||||
if !perms.contains(Permissions::UPLOAD_VERSION) {
|
if !perms.contains(Permissions::UPLOAD_VERSION) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit this version!"
|
"You do not have the permissions to edit this version!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,18 +368,16 @@ pub async fn version_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for game_version in game_versions {
|
for game_version in game_versions {
|
||||||
let game_version_id =
|
let game_version_id = database::models::categories::GameVersion::get_id(
|
||||||
database::models::categories::GameVersion::get_id(
|
&game_version.0,
|
||||||
&game_version.0,
|
&mut *transaction,
|
||||||
&mut *transaction,
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInput(
|
||||||
|
"No database entry for game version provided.".to_string(),
|
||||||
)
|
)
|
||||||
.await?
|
})?;
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::InvalidInput(
|
|
||||||
"No database entry for game version provided."
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -447,17 +410,13 @@ pub async fn version_edit(
|
|||||||
|
|
||||||
for loader in loaders {
|
for loader in loaders {
|
||||||
let loader_id =
|
let loader_id =
|
||||||
database::models::categories::Loader::get_id(
|
database::models::categories::Loader::get_id(&loader.0, &mut *transaction)
|
||||||
&loader.0,
|
.await?
|
||||||
&mut *transaction,
|
.ok_or_else(|| {
|
||||||
)
|
ApiError::InvalidInput(
|
||||||
.await?
|
"No database entry for loader provided.".to_string(),
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::InvalidInput(
|
})?;
|
||||||
"No database entry for loader provided."
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -551,8 +510,7 @@ pub async fn version_edit(
|
|||||||
if let Some(downloads) = &new_version.downloads {
|
if let Some(downloads) = &new_version.downloads {
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You don't have permission to set the downloads of this mod"
|
"You don't have permission to set the downloads of this mod".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,8 +535,7 @@ pub async fn version_edit(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
diff as i32,
|
diff as i32,
|
||||||
version_item.inner.project_id
|
version_item.inner.project_id as database::models::ids::ProjectId,
|
||||||
as database::models::ids::ProjectId,
|
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -667,8 +624,7 @@ pub async fn version_schedule(
|
|||||||
|
|
||||||
if scheduling_data.time < Utc::now() {
|
if scheduling_data.time < Utc::now() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You cannot schedule a version to be released in the past!"
|
"You cannot schedule a version to be released in the past!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,17 +635,15 @@ pub async fn version_schedule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let result =
|
let result = database::models::Version::get_full(string.into(), &**pool).await?;
|
||||||
database::models::Version::get_full(string.into(), &**pool).await?;
|
|
||||||
|
|
||||||
if let Some(version_item) = result {
|
if let Some(version_item) = result {
|
||||||
let team_member =
|
let team_member = database::models::TeamMember::get_from_user_id_version(
|
||||||
database::models::TeamMember::get_from_user_id_version(
|
version_item.inner.id,
|
||||||
version_item.inner.id,
|
user.id.into(),
|
||||||
user.id.into(),
|
&**pool,
|
||||||
&**pool,
|
)
|
||||||
)
|
.await?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !user.role.is_mod()
|
if !user.role.is_mod()
|
||||||
&& !team_member
|
&& !team_member
|
||||||
@@ -748,17 +702,14 @@ pub async fn version_delete(
|
|||||||
.contains(Permissions::DELETE_VERSION)
|
.contains(Permissions::DELETE_VERSION)
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have permission to delete versions in this team"
|
"You do not have permission to delete versions in this team".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let result =
|
let result = database::models::Version::remove_full(id.into(), &mut transaction).await?;
|
||||||
database::models::Version::remove_full(id.into(), &mut transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,9 @@ impl Drop for Scheduler {
|
|||||||
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
|
||||||
pub fn schedule_versions(
|
pub fn schedule_versions(scheduler: &mut Scheduler, pool: sqlx::Pool<sqlx::Postgres>) {
|
||||||
scheduler: &mut Scheduler,
|
let version_index_interval =
|
||||||
pool: sqlx::Pool<sqlx::Postgres>,
|
std::time::Duration::from_secs(parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800));
|
||||||
) {
|
|
||||||
let version_index_interval = std::time::Duration::from_secs(
|
|
||||||
parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800),
|
|
||||||
);
|
|
||||||
|
|
||||||
scheduler.run(version_index_interval, move || {
|
scheduler.run(version_index_interval, move || {
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
@@ -82,15 +78,11 @@ struct VersionFormat<'a> {
|
|||||||
release_time: DateTime<Utc>,
|
release_time: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_versions(
|
async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> Result<(), VersionIndexingError> {
|
||||||
pool: &sqlx::Pool<sqlx::Postgres>,
|
let input = reqwest::get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")
|
||||||
) -> Result<(), VersionIndexingError> {
|
.await?
|
||||||
let input = reqwest::get(
|
.json::<InputFormat>()
|
||||||
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json",
|
.await?;
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.json::<InputFormat>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut skipped_versions_count = 0u32;
|
let mut skipped_versions_count = 0u32;
|
||||||
|
|
||||||
@@ -152,8 +144,7 @@ async fn update_versions(
|
|||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
||||||
{
|
{
|
||||||
if let Some((_, alternate)) =
|
if let Some((_, alternate)) = HALL_OF_SHAME.iter().find(|(version, _)| name == *version)
|
||||||
HALL_OF_SHAME.iter().find(|(version, _)| name == *version)
|
|
||||||
{
|
{
|
||||||
name = String::from(*alternate);
|
name = String::from(*alternate);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ use log::info;
|
|||||||
use super::IndexingError;
|
use super::IndexingError;
|
||||||
use crate::database::models::ProjectId;
|
use crate::database::models::ProjectId;
|
||||||
use crate::search::UploadSearchProject;
|
use crate::search::UploadSearchProject;
|
||||||
|
use serde::Deserialize;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
|
||||||
pub async fn index_local(
|
pub async fn index_local(pool: PgPool) -> Result<Vec<UploadSearchProject>, IndexingError> {
|
||||||
pool: PgPool,
|
|
||||||
) -> Result<Vec<UploadSearchProject>, IndexingError> {
|
|
||||||
info!("Indexing local projects!");
|
info!("Indexing local projects!");
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
@@ -23,7 +22,8 @@ pub async fn index_local(
|
|||||||
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
||||||
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
||||||
ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is false) gallery,
|
ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is false) gallery,
|
||||||
ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_gallery
|
ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_gallery,
|
||||||
|
JSONB_AGG(DISTINCT jsonb_build_object('id', mdep.id, 'dep_type', d.dependency_type)) filter (where mdep.id is not null) dependencies
|
||||||
FROM mods m
|
FROM mods m
|
||||||
LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id
|
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 categories c ON mc.joining_category_id = c.id
|
||||||
@@ -33,6 +33,8 @@ pub async fn index_local(
|
|||||||
LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id
|
LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id
|
||||||
LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id
|
LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id
|
||||||
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
|
LEFT OUTER JOIN dependencies d ON d.dependent_id = v.id
|
||||||
|
LEFT OUTER JOIN mods mdep ON mdep.id = d.mod_dependency_id
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
@@ -70,6 +72,21 @@ pub async fn index_local(
|
|||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct TempDependency {
|
||||||
|
id: ProjectId,
|
||||||
|
dep_type: String
|
||||||
|
}
|
||||||
|
|
||||||
|
let dependencies = serde_json::from_value::<Vec<TempDependency>>(
|
||||||
|
m.dependencies.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| format!("{}-{}", crate::models::ids::ProjectId::from(x.id), x.dep_type))
|
||||||
|
.collect();
|
||||||
|
|
||||||
UploadSearchProject {
|
UploadSearchProject {
|
||||||
project_id: project_id.to_string(),
|
project_id: project_id.to_string(),
|
||||||
title: m.title,
|
title: m.title,
|
||||||
@@ -95,6 +112,7 @@ pub async fn index_local(
|
|||||||
open_source,
|
open_source,
|
||||||
color: m.color.map(|x| x as u32),
|
color: m.color.map(|x| x as u32),
|
||||||
featured_gallery: m.featured_gallery.unwrap_or_default().first().cloned(),
|
featured_gallery: m.featured_gallery.unwrap_or_default().first().cloned(),
|
||||||
|
dependencies,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -203,10 +203,10 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
|
|||||||
"gallery",
|
"gallery",
|
||||||
"featured_gallery",
|
"featured_gallery",
|
||||||
"color",
|
"color",
|
||||||
|
"dependencies",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] =
|
const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] = &["title", "description", "author", "slug"];
|
||||||
&["title", "description", "author", "slug"];
|
|
||||||
|
|
||||||
const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
||||||
"categories",
|
"categories",
|
||||||
@@ -224,6 +224,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
|||||||
"project_id",
|
"project_id",
|
||||||
"open_source",
|
"open_source",
|
||||||
"color",
|
"color",
|
||||||
|
"dependencies",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
|
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ pub struct UploadSearchProject {
|
|||||||
pub modified_timestamp: i64,
|
pub modified_timestamp: i64,
|
||||||
pub open_source: bool,
|
pub open_source: bool,
|
||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
|
/// format: {project_id}-{dep_type}
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
@@ -134,6 +136,8 @@ pub struct ResultSearchProject {
|
|||||||
pub gallery: Vec<String>,
|
pub gallery: Vec<String>,
|
||||||
pub featured_gallery: Option<String>,
|
pub featured_gallery: Option<String>,
|
||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
|
/// format: {project_id}-{dep_type}
|
||||||
|
pub dependencies: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn search_for_project(
|
pub async fn search_for_project(
|
||||||
@@ -177,13 +181,12 @@ pub async fn search_for_project(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let filters: Cow<_> =
|
let filters: Cow<_> = match (info.filters.as_deref(), info.version.as_deref()) {
|
||||||
match (info.filters.as_deref(), info.version.as_deref()) {
|
(Some(f), Some(v)) => format!("({f}) AND ({v})").into(),
|
||||||
(Some(f), Some(v)) => format!("({f}) AND ({v})").into(),
|
(Some(f), None) => f.into(),
|
||||||
(Some(f), None) => f.into(),
|
(None, Some(v)) => v.into(),
|
||||||
(None, Some(v)) => v.into(),
|
(None, None) => "".into(),
|
||||||
(None, None) => "".into(),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(facets) = facets {
|
if let Some(facets) = facets {
|
||||||
filter_string.push('(');
|
filter_string.push('(');
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ where
|
|||||||
{
|
{
|
||||||
let github_user = get_github_user_from_token(access_token).await?;
|
let github_user = get_github_user_from_token(access_token).await?;
|
||||||
|
|
||||||
let res =
|
let res = models::User::get_from_github_id(github_user.id, executor).await?;
|
||||||
models::User::get_from_github_id(github_user.id, executor).await?;
|
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Some(result) => Ok(User {
|
Some(result) => Ok(User {
|
||||||
@@ -190,8 +189,7 @@ pub async fn filter_authorized_projects(
|
|||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
check_projects.retain(|x| {
|
check_projects.retain(|x| {
|
||||||
let bool = x.inner.id.0 == row.id
|
let bool = x.inner.id.0 == row.id && x.inner.team_id.0 == row.team_id;
|
||||||
&& x.inner.team_id.0 == row.team_id;
|
|
||||||
|
|
||||||
if bool {
|
if bool {
|
||||||
return_projects.push(x.clone().into());
|
return_projects.push(x.clone().into());
|
||||||
@@ -274,25 +272,29 @@ pub async fn filter_authorized_versions(
|
|||||||
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2
|
INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2
|
||||||
WHERE m.id = ANY($1)
|
WHERE m.id = ANY($1)
|
||||||
",
|
",
|
||||||
&check_versions.iter().map(|x| x.inner.project_id.0).collect::<Vec<_>>(),
|
&check_versions
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.inner.project_id.0)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
user_id as database::models::ids::UserId,
|
user_id as database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_many(&***pool)
|
.fetch_many(&***pool)
|
||||||
.try_for_each(|e| {
|
.try_for_each(|e| {
|
||||||
if let Some(row) = e.right() {
|
if let Some(row) = e.right() {
|
||||||
check_versions.retain(|x| {
|
check_versions.retain(|x| {
|
||||||
let bool = x.inner.project_id.0 == row.id;
|
let bool = x.inner.project_id.0 == row.id;
|
||||||
|
|
||||||
if bool {
|
if bool {
|
||||||
return_versions.push(x.clone().into());
|
return_versions.push(x.clone().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
!bool
|
!bool
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::future::ready(Ok(()))
|
futures::future::ready(Ok(()))
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ use actix_web::guard::GuardContext;
|
|||||||
|
|
||||||
pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin";
|
pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin";
|
||||||
pub fn admin_key_guard(ctx: &GuardContext) -> bool {
|
pub fn admin_key_guard(ctx: &GuardContext) -> bool {
|
||||||
let admin_key = std::env::var("LABRINTH_ADMIN_KEY").expect(
|
let admin_key = std::env::var("LABRINTH_ADMIN_KEY")
|
||||||
"No admin key provided, this should have been caught by check_env_vars",
|
.expect("No admin key provided, this should have been caught by check_env_vars");
|
||||||
);
|
|
||||||
|
|
||||||
ctx.head()
|
ctx.head()
|
||||||
.headers()
|
.headers()
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ pub fn get_color_from_img(data: &[u8]) -> Result<Option<u32>, ImageError> {
|
|||||||
let image = image::load_from_memory(data)?
|
let image = image::load_from_memory(data)?
|
||||||
.resize(256, 256, FilterType::Nearest)
|
.resize(256, 256, FilterType::Nearest)
|
||||||
.crop_imm(128, 128, 64, 64);
|
.crop_imm(128, 128, 64, 64);
|
||||||
let color = color_thief::get_palette(
|
let color = color_thief::get_palette(image.to_rgb8().as_bytes(), ColorFormat::Rgb, 10, 2)
|
||||||
image.to_rgb8().as_bytes(),
|
.ok()
|
||||||
ColorFormat::Rgb,
|
.and_then(|x| x.get(0).copied())
|
||||||
10,
|
.map(|x| (x.r as u32) << 16 | (x.g as u32) << 8 | (x.b as u32));
|
||||||
2,
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x.get(0).copied())
|
|
||||||
.map(|x| (x.r as u32) << 16 | (x.g as u32) << 8 | (x.b as u32));
|
|
||||||
|
|
||||||
Ok(color)
|
Ok(color)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ pub async fn read_from_payload(
|
|||||||
return Err(ApiError::InvalidInput(String::from(err_msg)));
|
return Err(ApiError::InvalidInput(String::from(err_msg)));
|
||||||
} else {
|
} else {
|
||||||
bytes.extend_from_slice(&item.map_err(|_| {
|
bytes.extend_from_slice(&item.map_err(|_| {
|
||||||
ApiError::InvalidInput(
|
ApiError::InvalidInput("Unable to parse bytes in payload sent!".to_string())
|
||||||
"Unable to parse bytes in payload sent!".to_string(),
|
|
||||||
)
|
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,9 +41,7 @@ pub async fn read_from_field(
|
|||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ok_or_not_found<T, U>(
|
pub(crate) fn ok_or_not_found<T, U>(version_data: Option<T>) -> Result<HttpResponse, ApiError>
|
||||||
version_data: Option<T>,
|
|
||||||
) -> Result<HttpResponse, ApiError>
|
|
||||||
where
|
where
|
||||||
U: From<T> + Serialize,
|
U: From<T> + Serialize,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,15 +4,11 @@ use regex::Regex;
|
|||||||
use validator::{ValidationErrors, ValidationErrorsKind};
|
use validator::{ValidationErrors, ValidationErrorsKind};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref RE_URL_SAFE: Regex =
|
pub static ref RE_URL_SAFE: Regex = Regex::new(r#"^[a-zA-Z0-9!@$()`.+,_"-]*$"#).unwrap();
|
||||||
Regex::new(r#"^[a-zA-Z0-9!@$()`.+,_"-]*$"#).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future!
|
//TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future!
|
||||||
pub fn validation_errors_to_string(
|
pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option<String>) -> String {
|
||||||
errors: ValidationErrors,
|
|
||||||
adder: Option<String>,
|
|
||||||
) -> String {
|
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
let map = errors.into_errors();
|
let map = errors.into_errors();
|
||||||
@@ -23,10 +19,7 @@ pub fn validation_errors_to_string(
|
|||||||
if let Some(error) = map.get(field) {
|
if let Some(error) = map.get(field) {
|
||||||
return match error {
|
return match error {
|
||||||
ValidationErrorsKind::Struct(errors) => {
|
ValidationErrorsKind::Struct(errors) => {
|
||||||
validation_errors_to_string(
|
validation_errors_to_string(*errors.clone(), Some(format!("of item {field}")))
|
||||||
*errors.clone(),
|
|
||||||
Some(format!("of item {field}")),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ValidationErrorsKind::List(list) => {
|
ValidationErrorsKind::List(list) => {
|
||||||
if let Some((index, errors)) = list.iter().next() {
|
if let Some((index, errors)) = list.iter().next() {
|
||||||
|
|||||||
@@ -180,8 +180,7 @@ pub async fn send_discord_webhook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !versions.is_empty() {
|
if !versions.is_empty() {
|
||||||
let formatted_game_versions: String =
|
let formatted_game_versions: String = get_gv_range(versions, all_game_versions);
|
||||||
get_gv_range(versions, all_game_versions);
|
|
||||||
|
|
||||||
fields.push(DiscordEmbedField {
|
fields.push(DiscordEmbedField {
|
||||||
name: "Versions",
|
name: "Versions",
|
||||||
@@ -229,9 +228,7 @@ pub async fn send_discord_webhook(
|
|||||||
thumbnail: DiscordEmbedThumbnail {
|
thumbnail: DiscordEmbedThumbnail {
|
||||||
url: project.icon_url,
|
url: project.icon_url,
|
||||||
},
|
},
|
||||||
image: if let Some(first) =
|
image: if let Some(first) = project.featured_gallery.unwrap_or_default().first() {
|
||||||
project.featured_gallery.unwrap_or_default().first()
|
|
||||||
{
|
|
||||||
Some(first.clone())
|
Some(first.clone())
|
||||||
} else {
|
} else {
|
||||||
project.gallery.unwrap_or_default().first().cloned()
|
project.gallery.unwrap_or_default().first().cloned()
|
||||||
@@ -242,9 +239,7 @@ pub async fn send_discord_webhook(
|
|||||||
"{}{display_project_type} on Modrinth",
|
"{}{display_project_type} on Modrinth",
|
||||||
display_project_type.remove(0).to_uppercase()
|
display_project_type.remove(0).to_uppercase()
|
||||||
),
|
),
|
||||||
icon_url: Some(
|
icon_url: Some("https://cdn-raw.modrinth.com/modrinth-new.png".to_string()),
|
||||||
"https://cdn-raw.modrinth.com/modrinth-new.png".to_string(),
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,10 +248,7 @@ pub async fn send_discord_webhook(
|
|||||||
client
|
client
|
||||||
.post(&webhook_url)
|
.post(&webhook_url)
|
||||||
.json(&DiscordWebhook {
|
.json(&DiscordWebhook {
|
||||||
avatar_url: Some(
|
avatar_url: Some("https://cdn.modrinth.com/Modrinth_Dark_Logo.png".to_string()),
|
||||||
"https://cdn.modrinth.com/Modrinth_Dark_Logo.png"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
username: Some("Modrinth Release".to_string()),
|
username: Some("Modrinth Release".to_string()),
|
||||||
embeds: vec![embed],
|
embeds: vec![embed],
|
||||||
content: message,
|
content: message,
|
||||||
@@ -264,9 +256,7 @@ pub async fn send_discord_webhook(
|
|||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::DiscordError(
|
ApiError::DiscordError("Error while sending projects webhook".to_string())
|
||||||
"Error while sending projects webhook".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,21 +300,15 @@ fn get_gv_range(
|
|||||||
} else {
|
} else {
|
||||||
let interval_base = &intervals[current_interval];
|
let interval_base = &intervals[current_interval];
|
||||||
|
|
||||||
if ((index as i32)
|
if ((index as i32) - (interval_base[interval_base.len() - 1][1] as i32) == 1
|
||||||
- (interval_base[interval_base.len() - 1][1] as i32)
|
|| (release_index as i32) - (interval_base[interval_base.len() - 1][2] as i32) == 1)
|
||||||
== 1
|
|
||||||
|| (release_index as i32)
|
|
||||||
- (interval_base[interval_base.len() - 1][2] as i32)
|
|
||||||
== 1)
|
|
||||||
&& (all_game_versions[interval_base[0][1]].type_ == "release"
|
&& (all_game_versions[interval_base[0][1]].type_ == "release"
|
||||||
|| all_game_versions[index].type_ != "release")
|
|| all_game_versions[index].type_ != "release")
|
||||||
{
|
{
|
||||||
if intervals[current_interval].get(1).is_some() {
|
if intervals[current_interval].get(1).is_some() {
|
||||||
intervals[current_interval][1] =
|
intervals[current_interval][1] = vec![i, index, release_index];
|
||||||
vec![i, index, release_index];
|
|
||||||
} else {
|
} else {
|
||||||
intervals[current_interval]
|
intervals[current_interval].insert(1, vec![i, index, release_index]);
|
||||||
.insert(1, vec![i, index, release_index]);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
current_interval += 1;
|
current_interval += 1;
|
||||||
@@ -336,10 +320,7 @@ fn get_gv_range(
|
|||||||
let mut new_intervals = Vec::new();
|
let mut new_intervals = Vec::new();
|
||||||
|
|
||||||
for interval in intervals {
|
for interval in intervals {
|
||||||
if interval.len() == 2
|
if interval.len() == 2 && interval[0][2] != MAX_VALUE && interval[1][2] == MAX_VALUE {
|
||||||
&& interval[0][2] != MAX_VALUE
|
|
||||||
&& interval[1][2] == MAX_VALUE
|
|
||||||
{
|
|
||||||
let mut last_snapshot: Option<usize> = None;
|
let mut last_snapshot: Option<usize> = None;
|
||||||
|
|
||||||
for j in ((interval[0][1] + 1)..=interval[1][1]).rev() {
|
for j in ((interval[0][1] + 1)..=interval[1][1]).rev() {
|
||||||
@@ -349,16 +330,12 @@ fn get_gv_range(
|
|||||||
vec![
|
vec![
|
||||||
game_versions
|
game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.position(|x| {
|
.position(|x| x.version == all_game_versions[j].version)
|
||||||
x.version == all_game_versions[j].version
|
|
||||||
})
|
|
||||||
.unwrap_or(MAX_VALUE),
|
.unwrap_or(MAX_VALUE),
|
||||||
j,
|
j,
|
||||||
all_releases
|
all_releases
|
||||||
.iter()
|
.iter()
|
||||||
.position(|x| {
|
.position(|x| x.version == all_game_versions[j].version)
|
||||||
x.version == all_game_versions[j].version
|
|
||||||
})
|
|
||||||
.unwrap_or(MAX_VALUE),
|
.unwrap_or(MAX_VALUE),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -370,10 +347,7 @@ fn get_gv_range(
|
|||||||
game_versions
|
game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.position(|x| {
|
.position(|x| {
|
||||||
x.version
|
x.version == all_game_versions[last_snapshot].version
|
||||||
== all_game_versions
|
|
||||||
[last_snapshot]
|
|
||||||
.version
|
|
||||||
})
|
})
|
||||||
.unwrap_or(MAX_VALUE),
|
.unwrap_or(MAX_VALUE),
|
||||||
last_snapshot,
|
last_snapshot,
|
||||||
@@ -402,8 +376,7 @@ fn get_gv_range(
|
|||||||
if interval.len() == 2 {
|
if interval.len() == 2 {
|
||||||
output.push(format!(
|
output.push(format!(
|
||||||
"{}—{}",
|
"{}—{}",
|
||||||
&game_versions[interval[0][0]].version,
|
&game_versions[interval[0][0]].version, &game_versions[interval[1][0]].version
|
||||||
&game_versions[interval[1][0]].version
|
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
output.push(game_versions[interval[0][0]].version.clone())
|
output.push(game_versions[interval[0][0]].version.clone())
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
@@ -38,9 +36,10 @@ impl super::Validator for FabricValidator {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !archive.file_names().any(|name| {
|
if !archive
|
||||||
name.ends_with("refmap.json") || name.ends_with(".class")
|
.file_names()
|
||||||
}) {
|
.any(|name| name.ends_with("refmap.json") || name.ends_with(".class"))
|
||||||
|
{
|
||||||
return Ok(ValidationResult::Warning(
|
return Ok(ValidationResult::Warning(
|
||||||
"Fabric mod file is a source file!",
|
"Fabric mod file is a source file!",
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ use crate::validate::modpack::ModpackValidator;
|
|||||||
use crate::validate::plugin::*;
|
use crate::validate::plugin::*;
|
||||||
use crate::validate::quilt::QuiltValidator;
|
use crate::validate::quilt::QuiltValidator;
|
||||||
use crate::validate::resourcepack::{PackValidator, TexturePackValidator};
|
use crate::validate::resourcepack::{PackValidator, TexturePackValidator};
|
||||||
use crate::validate::shader::{
|
use crate::validate::shader::{CanvasShaderValidator, CoreShaderValidator, ShaderValidator};
|
||||||
CanvasShaderValidator, CoreShaderValidator, ShaderValidator,
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -119,8 +117,7 @@ pub async fn validate_file(
|
|||||||
|
|
||||||
if let Some(file_type) = file_type {
|
if let Some(file_type) = file_type {
|
||||||
match file_type {
|
match file_type {
|
||||||
FileType::RequiredResourcePack
|
FileType::RequiredResourcePack | FileType::OptionalResourcePack => {
|
||||||
| FileType::OptionalResourcePack => {
|
|
||||||
project_type = "resourcepack".to_string();
|
project_type = "resourcepack".to_string();
|
||||||
loaders = vec![Loader("minecraft".to_string())];
|
loaders = vec![Loader("minecraft".to_string())];
|
||||||
}
|
}
|
||||||
@@ -150,13 +147,12 @@ pub async fn validate_file(
|
|||||||
|
|
||||||
if visited {
|
if visited {
|
||||||
if ALWAYS_ALLOWED_EXT.contains(&&*file_extension) {
|
if ALWAYS_ALLOWED_EXT.contains(&&*file_extension) {
|
||||||
Ok(ValidationResult::Warning("File extension is invalid for input file"))
|
Ok(ValidationResult::Warning(
|
||||||
|
"File extension is invalid for input file",
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::InvalidInput(
|
Err(ValidationError::InvalidInput(
|
||||||
format!(
|
format!("File extension {file_extension} is invalid for input file").into(),
|
||||||
"File extension {file_extension} is invalid for input file"
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -173,24 +169,20 @@ fn game_version_supported(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
match supported_game_versions {
|
match supported_game_versions {
|
||||||
SupportedGameVersions::All => true,
|
SupportedGameVersions::All => true,
|
||||||
SupportedGameVersions::PastDate(date) => {
|
SupportedGameVersions::PastDate(date) => game_versions.iter().any(|x| {
|
||||||
game_versions.iter().any(|x| {
|
all_game_versions
|
||||||
all_game_versions
|
.iter()
|
||||||
.iter()
|
.find(|y| y.version == x.0)
|
||||||
.find(|y| y.version == x.0)
|
.map(|x| x.created > date)
|
||||||
.map(|x| x.created > date)
|
.unwrap_or(false)
|
||||||
.unwrap_or(false)
|
}),
|
||||||
})
|
SupportedGameVersions::Range(before, after) => game_versions.iter().any(|x| {
|
||||||
}
|
all_game_versions
|
||||||
SupportedGameVersions::Range(before, after) => {
|
.iter()
|
||||||
game_versions.iter().any(|x| {
|
.find(|y| y.version == x.0)
|
||||||
all_game_versions
|
.map(|x| x.created > before && x.created < after)
|
||||||
.iter()
|
.unwrap_or(false)
|
||||||
.find(|y| y.version == x.0)
|
}),
|
||||||
.map(|x| x.created > before && x.created < after)
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SupportedGameVersions::Custom(versions) => {
|
SupportedGameVersions::Custom(versions) => {
|
||||||
versions.iter().any(|x| game_versions.contains(x))
|
versions.iter().any(|x| game_versions.contains(x))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use crate::models::pack::{PackFileHash, PackFormat};
|
use crate::models::pack::{PackFileHash, PackFormat};
|
||||||
use crate::util::validate::validation_errors_to_string;
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -32,14 +30,11 @@ impl super::Validator for ModpackValidator {
|
|||||||
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
||||||
) -> Result<ValidationResult, ValidationError> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
let pack: PackFormat = {
|
let pack: PackFormat = {
|
||||||
let mut file =
|
let mut file = if let Ok(file) = archive.by_name("modrinth.index.json") {
|
||||||
if let Ok(file) = archive.by_name("modrinth.index.json") {
|
file
|
||||||
file
|
} else {
|
||||||
} else {
|
return Ok(ValidationResult::Warning("Pack manifest is missing."));
|
||||||
return Ok(ValidationResult::Warning(
|
};
|
||||||
"Pack manifest is missing.",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
@@ -48,9 +43,7 @@ impl super::Validator for ModpackValidator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pack.validate().map_err(|err| {
|
pack.validate().map_err(|err| {
|
||||||
ValidationError::InvalidInput(
|
ValidationError::InvalidInput(validation_errors_to_string(err, None).into())
|
||||||
validation_errors_to_string(err, None).into(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if pack.game != "minecraft" {
|
if pack.game != "minecraft" {
|
||||||
@@ -75,11 +68,7 @@ impl super::Validator for ModpackValidator {
|
|||||||
let path = std::path::Path::new(&file.path)
|
let path = std::path::Path::new(&file.path)
|
||||||
.components()
|
.components()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| ValidationError::InvalidInput("Invalid pack file path!".into()))?;
|
||||||
ValidationError::InvalidInput(
|
|
||||||
"Invalid pack file path!".into(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match path {
|
match path {
|
||||||
Component::CurDir | Component::Normal(_) => {}
|
Component::CurDir | Component::Normal(_) => {}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
@@ -37,9 +35,10 @@ impl super::Validator for QuiltValidator {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !archive.file_names().any(|name| {
|
if !archive
|
||||||
name.ends_with("refmap.json") || name.ends_with(".class")
|
.file_names()
|
||||||
}) {
|
.any(|name| name.ends_with("refmap.json") || name.ends_with(".class"))
|
||||||
|
{
|
||||||
return Ok(ValidationResult::Warning(
|
return Ok(ValidationResult::Warning(
|
||||||
"Quilt mod file is a source file!",
|
"Quilt mod file is a source file!",
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
use crate::validate::{
|
use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult};
|
||||||
SupportedGameVersions, ValidationError, ValidationResult,
|
|
||||||
};
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user