diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index f5a8b8674..000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -edition = "2018" -max_width = 80 \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 06dac4788..000000000 --- a/shell.nix +++ /dev/null @@ -1,13 +0,0 @@ -# TODO: Move to flake -{pkgs ? import {}, - 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 - ]; -} diff --git a/sqlx-data.json b/sqlx-data.json index 2d4d0cbd3..180ae8656 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -525,26 +525,6 @@ }, "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": { "describe": { "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 " }, + "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": { "describe": { "columns": [], @@ -611,6 +763,18 @@ }, "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": { "describe": { "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 " }, - "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": { "describe": { "columns": [], @@ -2673,6 +2824,26 @@ }, "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": { "describe": { "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 " }, + "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": { "describe": { "columns": [], @@ -4752,172 +4949,6 @@ }, "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": { "describe": { "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 " }, + "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": { "describe": { "columns": [], diff --git a/src/database/models/categories.rs b/src/database/models/categories.rs index bbea15200..fd7dce6d3 100644 --- a/src/database/models/categories.rs +++ b/src/database/models/categories.rs @@ -52,10 +52,7 @@ pub struct DonationPlatform { } impl Category { - pub async fn get_id<'a, E>( - name: &str, - exec: E, - ) -> Result, DatabaseError> + pub async fn get_id<'a, E>(name: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -124,10 +121,7 @@ impl Category { } impl Loader { - pub async fn get_id<'a, E>( - name: &str, - exec: E, - ) -> Result, DatabaseError> + pub async fn get_id<'a, E>(name: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -315,10 +309,7 @@ impl GameVersion { impl<'a> GameVersionBuilder<'a> { /// The game version. Spaces must be replaced with '_' for it to be valid - pub fn version( - self, - version: &'a str, - ) -> Result, DatabaseError> { + pub fn version(self, version: &'a str) -> Result, DatabaseError> { Ok(Self { version: Some(version), ..self @@ -342,10 +333,7 @@ impl<'a> GameVersionBuilder<'a> { } } - pub async fn insert<'b, E>( - self, - exec: E, - ) -> Result + pub async fn insert<'b, E>(self, exec: E) -> Result where E: sqlx::Executor<'b, Database = sqlx::Postgres>, { @@ -393,9 +381,7 @@ impl DonationPlatform { Ok(result.map(|r| DonationPlatformId(r.id))) } - pub async fn list<'a, E>( - exec: E, - ) -> Result, DatabaseError> + pub async fn list<'a, E>(exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -420,10 +406,7 @@ impl DonationPlatform { } impl ReportType { - pub async fn get_id<'a, E>( - name: &str, - exec: E, - ) -> Result, DatabaseError> + pub async fn get_id<'a, E>(name: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -459,10 +442,7 @@ impl ReportType { } impl ProjectType { - pub async fn get_id<'a, E>( - name: &str, - exec: E, - ) -> Result, DatabaseError> + pub async fn get_id<'a, E>(name: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -498,10 +478,7 @@ impl ProjectType { } impl SideType { - pub async fn get_id<'a, E>( - name: &str, - exec: E, - ) -> Result, DatabaseError> + pub async fn get_id<'a, E>(name: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { diff --git a/src/database/models/notification_item.rs b/src/database/models/notification_item.rs index 7c9d0a078..dda0150b9 100644 --- a/src/database/models/notification_item.rs +++ b/src/database/models/notification_item.rs @@ -102,8 +102,7 @@ impl Notification { { use futures::stream::TryStreamExt; - let notification_ids_parsed: Vec = - notification_ids.iter().map(|x| x.0).collect(); + let notification_ids_parsed: Vec = notification_ids.iter().map(|x| x.0).collect(); sqlx::query!( " 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 } + pub async fn read( + id: NotificationId, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result, sqlx::error::Error> { + Self::read_many(&[id], transaction).await + } + + pub async fn read_many( + notification_ids: &[NotificationId], + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result, sqlx::error::Error> { + let notification_ids_parsed: Vec = 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( id: NotificationId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, @@ -215,8 +241,7 @@ impl Notification { notification_ids: &[NotificationId], transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result, sqlx::error::Error> { - let notification_ids_parsed: Vec = - notification_ids.iter().map(|x| x.0).collect(); + let notification_ids_parsed: Vec = notification_ids.iter().map(|x| x.0).collect(); sqlx::query!( " diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index ae460c431..5c8c19263 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -186,12 +186,11 @@ impl ProjectBuilder { self.project_id as ProjectId, category as CategoryId, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } - Project::update_game_versions(self.project_id, &mut *transaction) - .await?; + Project::update_game_versions(self.project_id, &mut *transaction).await?; Project::update_loaders(self.project_id, &mut *transaction).await?; Ok(self.project_id) @@ -307,8 +306,7 @@ impl Project { { use futures::stream::TryStreamExt; - let project_ids_parsed: Vec = - project_ids.iter().map(|x| x.0).collect(); + let project_ids_parsed: Vec = project_ids.iter().map(|x| x.0).collect(); let projects = sqlx::query!( " SELECT id, project_type, title, description, downloads, follows, @@ -342,12 +340,8 @@ impl Project { license_url: m.license_url, discord_url: m.discord_url, client_side: SideTypeId(m.client_side), - status: ProjectStatus::from_str( - &m.status, - ), - requested_status: m.requested_status.map(|x| ProjectStatus::from_str( - &x, - )), + status: ProjectStatus::from_str(&m.status), + requested_status: m.requested_status.map(|x| ProjectStatus::from_str(&x)), server_side: SideTypeId(m.server_side), license: m.license, slug: m.slug, @@ -402,11 +396,7 @@ impl Project { if let Some(thread_id) = thread_id { if let Some(id) = thread_id.thread_id { - crate::database::models::Thread::remove_full( - ThreadId(id), - transaction, - ) - .await?; + crate::database::models::Thread::remove_full(ThreadId(id), transaction).await?; } } @@ -595,23 +585,18 @@ impl Project { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - let id_option = - crate::models::ids::base62_impl::parse_base62(slug_or_project_id) - .ok(); + let id_option = crate::models::ids::base62_impl::parse_base62(slug_or_project_id).ok(); if let Some(id) = id_option { - let mut project = - Project::get(ProjectId(id as i64), executor).await?; + let mut project = Project::get(ProjectId(id as i64), executor).await?; if project.is_none() { - project = Project::get_from_slug(slug_or_project_id, executor) - .await?; + project = Project::get_from_slug(slug_or_project_id, executor).await?; } Ok(project) } else { - let project = - Project::get_from_slug(slug_or_project_id, executor).await?; + let project = Project::get_from_slug(slug_or_project_id, executor).await?; Ok(project) } @@ -624,25 +609,18 @@ impl Project { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - let id_option = - crate::models::ids::base62_impl::parse_base62(slug_or_project_id) - .ok(); + let id_option = crate::models::ids::base62_impl::parse_base62(slug_or_project_id).ok(); if let Some(id) = id_option { - let mut project = - Project::get_full(ProjectId(id as i64), executor).await?; + let mut project = Project::get_full(ProjectId(id as i64), executor).await?; if project.is_none() { - project = - Project::get_full_from_slug(slug_or_project_id, executor) - .await?; + project = Project::get_full_from_slug(slug_or_project_id, executor).await?; } Ok(project) } else { - let project = - Project::get_full_from_slug(slug_or_project_id, executor) - .await?; + let project = Project::get_full_from_slug(slug_or_project_id, executor).await?; Ok(project) } } @@ -668,8 +646,7 @@ impl Project { { use futures::TryStreamExt; - let project_ids_parsed: Vec = - project_ids.iter().map(|x| x.0).collect(); + let project_ids_parsed: Vec = project_ids.iter().map(|x| x.0).collect(); sqlx::query!( " SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows, diff --git a/src/database/models/report_item.rs b/src/database/models/report_item.rs index a975ed1ab..11775d08e 100644 --- a/src/database/models/report_item.rs +++ b/src/database/models/report_item.rs @@ -58,10 +58,7 @@ impl Report { Ok(()) } - pub async fn get<'a, E>( - id: ReportId, - exec: E, - ) -> Result, sqlx::Error> + pub async fn get<'a, E>(id: ReportId, exec: E) -> Result, sqlx::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { @@ -79,8 +76,7 @@ impl Report { { use futures::stream::TryStreamExt; - let report_ids_parsed: Vec = - report_ids.iter().map(|x| x.0).collect(); + let report_ids_parsed: Vec = report_ids.iter().map(|x| x.0).collect(); 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 @@ -141,11 +137,7 @@ impl Report { if let Some(thread_id) = thread_id { if let Some(id) = thread_id.thread_id { - crate::database::models::Thread::remove_full( - ThreadId(id), - transaction, - ) - .await?; + crate::database::models::Thread::remove_full(ThreadId(id), transaction).await?; } } diff --git a/src/database/models/team_item.rs b/src/database/models/team_item.rs index 7b52ed22a..3a365606d 100644 --- a/src/database/models/team_item.rs +++ b/src/database/models/team_item.rs @@ -36,8 +36,7 @@ impl TeamBuilder { .await?; for member in self.members { - let team_member_id = - generate_team_member_id(&mut *transaction).await?; + let team_member_id = generate_team_member_id(&mut *transaction).await?; let team_member = TeamMember { id: team_member_id, team_id, @@ -224,16 +223,16 @@ impl TeamMember { .fetch_many(executor) .try_filter_map(|e| async { if let Some(m) = e.right() { - Ok(Some(Ok(TeamMember { - id: TeamMemberId(m.id), - team_id: TeamId(m.team_id), - user_id, - role: m.role, - permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(), - accepted: m.accepted, - payouts_split: m.payouts_split, - ordering: m.ordering, - }))) + Ok(Some(Ok(TeamMember { + id: TeamMemberId(m.id), + team_id: TeamId(m.team_id), + user_id, + role: m.role, + permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(), + accepted: m.accepted, + payouts_split: m.payouts_split, + ordering: m.ordering, + }))) } else { Ok(None) } @@ -275,8 +274,7 @@ impl TeamMember { team_id: id, user_id, 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, payouts_split: m.payouts_split, ordering: m.ordering, @@ -448,8 +446,7 @@ impl TeamMember { team_id: TeamId(m.team_id), user_id, 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, payouts_split: m.payouts_split, ordering: m.ordering, @@ -486,8 +483,7 @@ impl TeamMember { team_id: TeamId(m.team_id), user_id, 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, payouts_split: m.payouts_split, ordering: m.ordering, diff --git a/src/database/models/thread_item.rs b/src/database/models/thread_item.rs index 2740b8b6d..3fb33973e 100644 --- a/src/database/models/thread_item.rs +++ b/src/database/models/thread_item.rs @@ -42,8 +42,7 @@ impl ThreadMessageBuilder { &self, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { - let thread_message_id = - generate_thread_message_id(&mut *transaction).await?; + let thread_message_id = generate_thread_message_id(&mut *transaction).await?; sqlx::query!( " @@ -76,14 +75,16 @@ impl ThreadBuilder { sqlx::query!( " INSERT INTO threads ( - id, thread_type + id, thread_type, report_id, project_id ) VALUES ( - $1, $2 + $1, $2, $3, $4 ) ", thread_id as ThreadId, self.type_.as_str(), + self.report_id.map(|x| x.0), + self.project_id.map(|x| x.0), ) .execute(&mut *transaction) .await?; @@ -110,10 +111,7 @@ impl ThreadBuilder { } impl Thread { - pub async fn get<'a, E>( - id: ThreadId, - exec: E, - ) -> Result, sqlx::Error> + pub async fn get<'a, E>(id: ThreadId, exec: E) -> Result, sqlx::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { @@ -131,8 +129,7 @@ impl Thread { { use futures::stream::TryStreamExt; - let thread_ids_parsed: Vec = - thread_ids.iter().map(|x| x.0).collect(); + let thread_ids_parsed: Vec = thread_ids.iter().map(|x| x.0).collect(); let threads = sqlx::query!( " 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; - let message_ids_parsed: Vec = - message_ids.iter().map(|x| x.0).collect(); + let message_ids_parsed: Vec = message_ids.iter().map(|x| x.0).collect(); let messages = sqlx::query!( " SELECT tm.id, tm.author_id, tm.thread_id, tm.body, tm.created @@ -246,8 +242,7 @@ impl ThreadMessage { id: ThreadMessageId(x.id), thread_id: ThreadId(x.thread_id), author_id: x.author_id.map(UserId), - body: serde_json::from_value(x.body) - .unwrap_or(MessageBody::Deleted), + body: serde_json::from_value(x.body).unwrap_or(MessageBody::Deleted), created: x.created, })) }) @@ -268,8 +263,7 @@ impl ThreadMessage { WHERE id = $1 ", id as ThreadMessageId, - serde_json::to_value(MessageBody::Deleted) - .unwrap_or(serde_json::json!({})) + serde_json::to_value(MessageBody::Deleted).unwrap_or(serde_json::json!({})) ) .execute(&mut *transaction) .await?; diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index 9ea37aeba..2ccf38146 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -50,10 +50,7 @@ impl User { Ok(()) } - pub async fn get<'a, 'b, E>( - id: UserId, - executor: E, - ) -> Result, sqlx::error::Error> + pub async fn get<'a, 'b, E>(id: UserId, executor: E) -> Result, sqlx::error::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { @@ -95,12 +92,9 @@ impl User { bio: row.bio, created: row.created, role: row.role, - badges: Badges::from_bits(row.badges as u64) - .unwrap_or_default(), + badges: Badges::from_bits(row.badges as u64).unwrap_or_default(), balance: row.balance, - payout_wallet: row - .payout_wallet - .map(|x| RecipientWallet::from_string(&x)), + payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)), payout_wallet_type: row .payout_wallet_type .map(|x| RecipientType::from_string(&x)), @@ -144,12 +138,9 @@ impl User { bio: row.bio, created: row.created, role: row.role, - badges: Badges::from_bits(row.badges as u64) - .unwrap_or_default(), + badges: Badges::from_bits(row.badges as u64).unwrap_or_default(), balance: row.balance, - payout_wallet: row - .payout_wallet - .map(|x| RecipientWallet::from_string(&x)), + payout_wallet: row.payout_wallet.map(|x| RecipientWallet::from_string(&x)), payout_wallet_type: row .payout_wallet_type .map(|x| RecipientType::from_string(&x)), @@ -160,10 +151,7 @@ impl User { } } - pub async fn get_many<'a, E>( - user_ids: &[UserId], - exec: E, - ) -> Result, sqlx::Error> + pub async fn get_many<'a, E>(user_ids: &[UserId], exec: E) -> Result, sqlx::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { @@ -196,12 +184,8 @@ impl User { role: u.role, badges: Badges::from_bits(u.badges as u64).unwrap_or_default(), balance: u.balance, - payout_wallet: u - .payout_wallet - .map(|x| RecipientWallet::from_string(&x)), - payout_wallet_type: u - .payout_wallet_type - .map(|x| RecipientType::from_string(&x)), + payout_wallet: u.payout_wallet.map(|x| RecipientWallet::from_string(&x)), + payout_wallet_type: u.payout_wallet_type.map(|x| RecipientType::from_string(&x)), payout_address: u.payout_address, })) }) @@ -384,11 +368,8 @@ impl User { .await?; for project_id in projects { - let _result = super::project_item::Project::remove_full( - project_id, - transaction, - ) - .await?; + let _result = + super::project_item::Project::remove_full(project_id, transaction).await?; } let notifications: Vec = sqlx::query!( @@ -489,8 +470,7 @@ impl User { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - let id_option = - crate::models::ids::base62_impl::parse_base62(username_or_id).ok(); + let id_option = crate::models::ids::base62_impl::parse_base62(username_or_id).ok(); if let Some(id) = id_option { let id = UserId(id as i64); diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index e7fbcee4a..bbef408dd 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -499,8 +499,7 @@ impl Version { { use futures::stream::TryStreamExt; - let version_ids_parsed: Vec = - version_ids.iter().map(|x| x.0).collect(); + let version_ids_parsed: Vec = version_ids.iter().map(|x| x.0).collect(); 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, @@ -648,8 +647,7 @@ impl Version { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - let project_id_opt = - parse_base62(project_id_or_slug).ok().map(|x| x as i64); + let project_id_opt = 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 = sqlx::query!( " diff --git a/src/database/postgres_database.rs b/src/database/postgres_database.rs index 65601bde9..bf7da77ad 100644 --- a/src/database/postgres_database.rs +++ b/src/database/postgres_database.rs @@ -6,8 +6,7 @@ use std::time::Duration; pub async fn connect() -> Result { info!("Initializing database connection"); - let database_url = - dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env"); + let database_url = dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env"); let pool = PgPoolOptions::new() .min_connections( dotenvy::var("DATABASE_MIN_CONNECTIONS") diff --git a/src/file_hosting/backblaze.rs b/src/file_hosting/backblaze.rs index 28d302245..57e425542 100644 --- a/src/file_hosting/backblaze.rs +++ b/src/file_hosting/backblaze.rs @@ -16,12 +16,10 @@ pub struct BackblazeHost { impl BackblazeHost { pub async fn new(key_id: &str, key: &str, bucket_id: &str) -> Self { - let authorization_data = - authorization::authorize_account(key_id, key).await.unwrap(); - let upload_url_data = - authorization::get_upload_url(&authorization_data, bucket_id) - .await - .unwrap(); + let authorization_data = authorization::authorize_account(key_id, key).await.unwrap(); + let upload_url_data = authorization::get_upload_url(&authorization_data, bucket_id) + .await + .unwrap(); BackblazeHost { upload_url_data, @@ -40,13 +38,8 @@ impl FileHost for BackblazeHost { ) -> Result { let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes)); - let upload_data = upload::upload_file( - &self.upload_url_data, - content_type, - file_name, - file_bytes, - ) - .await?; + let upload_data = + upload::upload_file(&self.upload_url_data, content_type, file_name, file_bytes).await?; Ok(UploadFileData { file_id: upload_data.file_id, file_name: upload_data.file_name, @@ -81,12 +74,8 @@ impl FileHost for BackblazeHost { file_id: &str, file_name: &str, ) -> Result { - let delete_data = delete::delete_file_version( - &self.authorization_data, - file_id, - file_name, - ) - .await?; + let delete_data = + delete::delete_file_version(&self.authorization_data, file_id, file_name).await?; Ok(DeleteFileData { file_id: delete_data.file_id, file_name: delete_data.file_name, @@ -94,9 +83,7 @@ impl FileHost for BackblazeHost { } } -pub async fn process_response( - response: Response, -) -> Result +pub async fn process_response(response: Response) -> Result where T: for<'de> Deserialize<'de>, { diff --git a/src/file_hosting/backblaze/authorization.rs b/src/file_hosting/backblaze/authorization.rs index e64e34935..5c1dd7d0b 100644 --- a/src/file_hosting/backblaze/authorization.rs +++ b/src/file_hosting/backblaze/authorization.rs @@ -56,13 +56,7 @@ pub async fn get_upload_url( bucket_id: &str, ) -> Result { let response = reqwest::Client::new() - .post( - &format!( - "{}/b2api/v2/b2_get_upload_url", - authorization_data.api_url - ) - .to_string(), - ) + .post(&format!("{}/b2api/v2/b2_get_upload_url", authorization_data.api_url).to_string()) .header(reqwest::header::CONTENT_TYPE, "application/json") .header( reqwest::header::AUTHORIZATION, diff --git a/src/file_hosting/mock.rs b/src/file_hosting/mock.rs index e13a60a84..aa4706bf2 100644 --- a/src/file_hosting/mock.rs +++ b/src/file_hosting/mock.rs @@ -20,12 +20,9 @@ impl FileHost for MockHost { file_name: &str, file_bytes: Bytes, ) -> Result { - let path = - std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap()) - .join(file_name.replace("../", "")); - std::fs::create_dir_all( - path.parent().ok_or(FileHostingError::InvalidFilename)?, - )?; + let path = std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap()) + .join(file_name.replace("../", "")); + std::fs::create_dir_all(path.parent().ok_or(FileHostingError::InvalidFilename)?)?; let content_sha1 = sha1::Sha1::from(&file_bytes).hexdigest(); let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes)); @@ -47,9 +44,8 @@ impl FileHost for MockHost { file_id: &str, file_name: &str, ) -> Result { - let path = - std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap()) - .join(file_name.replace("../", "")); + let path = std::path::Path::new(&dotenvy::var("MOCK_FILE_PATH").unwrap()) + .join(file_name.replace("../", "")); std::fs::remove_file(path)?; Ok(DeleteFileData { diff --git a/src/file_hosting/s3_host.rs b/src/file_hosting/s3_host.rs index 87be229ab..67fdb824b 100644 --- a/src/file_hosting/s3_host.rs +++ b/src/file_hosting/s3_host.rs @@ -1,6 +1,4 @@ -use crate::file_hosting::{ - DeleteFileData, FileHost, FileHostingError, UploadFileData, -}; +use crate::file_hosting::{DeleteFileData, FileHost, FileHostingError, UploadFileData}; use async_trait::async_trait; use bytes::Bytes; use chrono::Utc; @@ -33,23 +31,12 @@ impl S3Host { endpoint: url.to_string(), } }, - Credentials::new( - Some(access_token), - Some(secret), - None, - None, - None, - ) - .map_err(|_| { - FileHostingError::S3Error( - "Error while creating credentials".to_string(), - ) + Credentials::new(Some(access_token), Some(secret), None, None, None).map_err(|_| { + FileHostingError::S3Error("Error while creating credentials".to_string()) })?, ) .map_err(|_| { - FileHostingError::S3Error( - "Error while creating Bucket instance".to_string(), - ) + FileHostingError::S3Error("Error while creating Bucket instance".to_string()) })?; Ok(S3Host { bucket }) @@ -68,16 +55,10 @@ impl FileHost for S3Host { let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes)); self.bucket - .put_object_with_content_type( - format!("/{file_name}"), - &file_bytes, - content_type, - ) + .put_object_with_content_type(format!("/{file_name}"), &file_bytes, content_type) .await .map_err(|_| { - FileHostingError::S3Error( - "Error while uploading file to S3".to_string(), - ) + FileHostingError::S3Error("Error while uploading file to S3".to_string()) })?; Ok(UploadFileData { @@ -101,9 +82,7 @@ impl FileHost for S3Host { .delete_object(format!("/{file_name}")) .await .map_err(|_| { - FileHostingError::S3Error( - "Error while deleting file from S3".to_string(), - ) + FileHostingError::S3Error("Error while deleting file from S3".to_string()) })?; Ok(DeleteFileData { diff --git a/src/health/status.rs b/src/health/status.rs index 2b8e55c39..043c7d971 100644 --- a/src/health/status.rs +++ b/src/health/status.rs @@ -1,9 +1,7 @@ use actix_web::web; use sqlx::PgPool; -pub async fn test_database( - postgres: web::Data, -) -> Result<(), sqlx::Error> { +pub async fn test_database(postgres: web::Data) -> Result<(), sqlx::Error> { let mut transaction = postgres.acquire().await?; sqlx::query( " diff --git a/src/main.rs b/src/main.rs index 67a8dd068..733982210 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,8 +35,7 @@ pub struct Pepper { #[actix_rt::main] async fn main() -> std::io::Result<()> { dotenvy::dotenv().ok(); - env_logger::Builder::from_env(Env::default().default_filter_or("info")) - .init(); + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); if check_env_vars() { error!("Some environment variables are missing!"); @@ -75,40 +74,37 @@ async fn main() -> std::io::Result<()> { .await .expect("Database connection failed"); - let storage_backend = - dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string()); + let storage_backend = dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string()); - let file_host: Arc = - match storage_backend.as_str() { - "backblaze" => Arc::new( - file_hosting::BackblazeHost::new( - &dotenvy::var("BACKBLAZE_KEY_ID").unwrap(), - &dotenvy::var("BACKBLAZE_KEY").unwrap(), - &dotenvy::var("BACKBLAZE_BUCKET_ID").unwrap(), - ) - .await, - ), - "s3" => Arc::new( - S3Host::new( - &dotenvy::var("S3_BUCKET_NAME").unwrap(), - &dotenvy::var("S3_REGION").unwrap(), - &dotenvy::var("S3_URL").unwrap(), - &dotenvy::var("S3_ACCESS_TOKEN").unwrap(), - &dotenvy::var("S3_SECRET").unwrap(), - ) - .unwrap(), - ), - "local" => Arc::new(file_hosting::MockHost::new()), - _ => panic!("Invalid storage backend specified. Aborting startup!"), - }; + let file_host: Arc = match storage_backend.as_str() { + "backblaze" => Arc::new( + file_hosting::BackblazeHost::new( + &dotenvy::var("BACKBLAZE_KEY_ID").unwrap(), + &dotenvy::var("BACKBLAZE_KEY").unwrap(), + &dotenvy::var("BACKBLAZE_BUCKET_ID").unwrap(), + ) + .await, + ), + "s3" => Arc::new( + S3Host::new( + &dotenvy::var("S3_BUCKET_NAME").unwrap(), + &dotenvy::var("S3_REGION").unwrap(), + &dotenvy::var("S3_URL").unwrap(), + &dotenvy::var("S3_ACCESS_TOKEN").unwrap(), + &dotenvy::var("S3_SECRET").unwrap(), + ) + .unwrap(), + ), + "local" => Arc::new(file_hosting::MockHost::new()), + _ => panic!("Invalid storage backend specified. Aborting startup!"), + }; let mut scheduler = scheduler::Scheduler::new(); // The interval in seconds at which the local database is indexed // for searching. Defaults to 1 hour if unset. - let local_index_interval = std::time::Duration::from_secs( - parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600), - ); + let local_index_interval = + std::time::Duration::from_secs(parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600)); let pool_ref = pool.clone(); let search_config_ref = search_config.clone(); @@ -118,8 +114,7 @@ async fn main() -> std::io::Result<()> { async move { info!("Indexing local database"); let settings = IndexingSettings { index_local: true }; - let result = - index_projects(pool_ref, settings, &search_config_ref).await; + let result = index_projects(pool_ref, settings, &search_config_ref).await; if let Err(e) = result { warn!("Local project indexing failed: {:?}", e); } @@ -170,14 +165,11 @@ async fn main() -> std::io::Result<()> { ", crate::models::projects::ProjectStatus::Scheduled.as_str(), ) - .execute(&pool_ref) - .await; + .execute(&pool_ref) + .await; if let Err(e) = projects_results { - warn!( - "Syncing scheduled releases for projects failed: {:?}", - e - ); + warn!("Syncing scheduled releases for projects failed: {:?}", e); } let versions_results = sqlx::query!( @@ -188,21 +180,18 @@ async fn main() -> std::io::Result<()> { ", crate::models::projects::VersionStatus::Scheduled.as_str(), ) - .execute(&pool_ref) - .await; + .execute(&pool_ref) + .await; if let Err(e) = versions_results { - warn!( - "Syncing scheduled releases for versions failed: {:?}", - e - ); + warn!("Syncing scheduled releases for versions failed: {:?}", e); } 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 webhook_message_sent = Arc::new(Mutex::new(Vec::<( database::models::ProjectId, @@ -212,7 +201,7 @@ async fn main() -> std::io::Result<()> { scheduler.run(std::time::Duration::from_secs(10 * 60), move || { let pool_ref = pool_ref.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 { let do_steps = async { @@ -221,7 +210,7 @@ async fn main() -> std::io::Result<()> { let project_ids = sqlx::query!( " 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 ", crate::models::projects::ProjectStatus::Processing.as_str(), @@ -247,7 +236,7 @@ async fn main() -> std::io::Result<()> { project.into(), &pool_ref, 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 .ok(); @@ -261,12 +250,12 @@ async fn main() -> std::io::Result<()> { if let Err(e) = do_steps.await { warn!( - "Checking reviewed projects submitted more than 24hrs ago failed: {:?}", + "Checking reviewed projects submitted more than 40hrs ago failed: {:?}", 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 { - pepper: models::ids::Base62Id(models::ids::random_base62(11)) - .to_string(), + pepper: models::ids::Base62Id(models::ids::random_base62(11)).to_string(), }; 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()) .with_identifier(|req| { let connection_info = req.connection_info(); - let ip = String::from( - if parse_var("CLOUDFLARE_INTEGRATION") - .unwrap_or(false) - { - if let Some(header) = - req.headers().get("CF-Connecting-IP") - { - header - .to_str() - .map_err(|_| ARError::Identification)? + let ip = + String::from(if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) { + if let Some(header) = req.headers().get("CF-Connecting-IP") { + header.to_str().map_err(|_| ARError::Identification)? } else { - connection_info - .peer_addr() - .ok_or(ARError::Identification)? + connection_info.peer_addr().ok_or(ARError::Identification)? } } else { - connection_info - .peer_addr() - .ok_or(ARError::Identification)? - }, - ); + connection_info.peer_addr().ok_or(ARError::Identification)? + }); Ok(ip) }) .with_interval(std::time::Duration::from_secs(60)) .with_max_requests(300) - .with_ignore_key( - dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok(), - ), + .with_ignore_key(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(file_host.clone())) .app_data(web::Data::new(search_config.clone())) diff --git a/src/models/ids.rs b/src/models/ids.rs index 69e57c777..119bdee3a 100644 --- a/src/models/ids.rs +++ b/src/models/ids.rs @@ -131,10 +131,7 @@ pub mod base62_impl { impl<'de> Visitor<'de> for Base62Visitor { type Value = Base62Id; - fn expecting( - &self, - formatter: &mut std::fmt::Formatter, - ) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 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 - if let Some(n) = - num.checked_mul(62).and_then(|n| n.checked_add(next_digit)) - { + if let Some(n) = num.checked_mul(62).and_then(|n| n.checked_add(next_digit)) { num = n; } else { return Err(DecodingError::Overflow); diff --git a/src/models/notifications.rs b/src/models/notifications.rs index 77f6d0436..7f848df92 100644 --- a/src/models/notifications.rs +++ b/src/models/notifications.rs @@ -2,9 +2,7 @@ use super::ids::Base62Id; use super::users::UserId; use crate::database::models::notification_item::Notification as DBNotification; use crate::database::models::notification_item::NotificationAction as DBNotificationAction; -use crate::models::ids::{ - ProjectId, ReportId, TeamId, ThreadId, ThreadMessageId, VersionId, -}; +use crate::models::ids::{ProjectId, ReportId, TeamId, ThreadId, ThreadMessageId, VersionId}; use crate::models::projects::ProjectStatus; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; @@ -91,27 +89,18 @@ impl From for Notification { } => ( Some("team_invite".to_string()), "You have been invited to join a team!".to_string(), - format!( - "An invite has been sent for you to be {} of a team", - role - ), + format!("An invite has been sent for you to be {} of a team", role), format!("/project/{}", project_id), vec![ NotificationAction { title: "Accept".to_string(), - action_route: ( - "POST".to_string(), - format!("team/{team_id}/join"), - ), + action_route: ("POST".to_string(), format!("team/{team_id}/join")), }, NotificationAction { title: "Deny".to_string(), action_route: ( "DELETE".to_string(), - format!( - "team/{team_id}/members/{}", - UserId::from(notif.user_id) - ), + format!("team/{team_id}/members/{}", UserId::from(notif.user_id)), ), }, ], diff --git a/src/models/pack.rs b/src/models/pack.rs index 8d648cd5d..c6feac604 100644 --- a/src/models/pack.rs +++ b/src/models/pack.rs @@ -30,9 +30,7 @@ pub struct PackFile { pub file_size: u32, } -fn validate_download_url( - values: &[String], -) -> Result<(), validator::ValidationError> { +fn validate_download_url(values: &[String]) -> Result<(), validator::ValidationError> { for value in values { let url = url::Url::parse(value) .ok() @@ -42,8 +40,7 @@ fn validate_download_url( return Err(validator::ValidationError::new("invalid URL")); } - let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS") - .unwrap_or_default(); + let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").unwrap_or_default(); if !domains.contains( &url.domain() .ok_or_else(|| validator::ValidationError::new("invalid URL"))? diff --git a/src/models/projects.rs b/src/models/projects.rs index da9fdefb4..0095d8b80 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -534,16 +534,10 @@ impl From for Version { version_id: d.version_id.map(|i| VersionId(i.0 as u64)), project_id: d.project_id.map(|i| ProjectId(i.0 as u64)), file_name: d.file_name, - dependency_type: DependencyType::from_str( - d.dependency_type.as_str(), - ), + dependency_type: DependencyType::from_str(d.dependency_type.as_str()), }) .collect(), - game_versions: data - .game_versions - .into_iter() - .map(GameVersion) - .collect(), + game_versions: data.game_versions.into_iter().map(GameVersion).collect(), loaders: data.loaders.into_iter().map(Loader).collect(), } } diff --git a/src/queue/payouts.rs b/src/queue/payouts.rs index a56c5644a..3afc54a56 100644 --- a/src/queue/payouts.rs +++ b/src/queue/payouts.rs @@ -68,36 +68,25 @@ impl PayoutsQueue { .form(&form) .send() .await - .map_err(|_| { - ApiError::Payments( - "Error while authenticating with PayPal".to_string(), - ) - })? + .map_err(|_| ApiError::Payments("Error while authenticating with PayPal".to_string()))? .json() .await .map_err(|_| { ApiError::Payments( - "Error while authenticating with PayPal (deser error)" - .to_string(), + "Error while authenticating with PayPal (deser error)".to_string(), ) })?; - self.credential_expires = - Utc::now() + Duration::seconds(credential.expires_in); + self.credential_expires = Utc::now() + Duration::seconds(credential.expires_in); self.credential = credential; Ok(()) } - pub async fn send_payout( - &mut self, - mut payout: PayoutItem, - ) -> Result { + pub async fn send_payout(&mut self, mut payout: PayoutItem) -> Result { if self.credential_expires < Utc::now() { self.refresh_token().await.map_err(|_| { - ApiError::Payments( - "Error while authenticating with PayPal".to_string(), - ) + ApiError::Payments("Error while authenticating with PayPal".to_string()) })?; } @@ -109,8 +98,7 @@ impl PayoutsQueue { std::cmp::min( std::cmp::max( Decimal::ONE / Decimal::from(4), - (Decimal::from(2) / Decimal::ONE_HUNDRED) - * payout.amount.value, + (Decimal::from(2) / Decimal::ONE_HUNDRED) * payout.amount.value, ), Decimal::from(20), ) @@ -151,9 +139,7 @@ impl PayoutsQueue { } let body: PayPalError = res.json().await.map_err(|_| { - ApiError::Payments( - "Error while registering payment in PayPal!".to_string(), - ) + ApiError::Payments("Error while registering payment in PayPal!".to_string()) })?; return Err(ApiError::Payments(format!( @@ -190,8 +176,7 @@ impl PayoutsQueue { "Authorization", format!( "{} {}", - self.credential.token_type, - self.credential.access_token + self.credential.token_type, self.credential.access_token ), ) .send() @@ -199,9 +184,7 @@ impl PayoutsQueue { { if let Ok(res) = res.json::().await { if let Some(data) = res.items.first() { - if (fee - data.payout_item_fee.value) - > Decimal::ZERO - { + if (fee - data.payout_item_fee.value) > Decimal::ZERO { return Ok(fee - data.payout_item_fee.value); } } diff --git a/src/ratelimit/errors.rs b/src/ratelimit/errors.rs index 1ed568322..ef103117e 100644 --- a/src/ratelimit/errors.rs +++ b/src/ratelimit/errors.rs @@ -35,27 +35,18 @@ impl ResponseError for ARError { reset, } => { let mut response = actix_web::HttpResponse::TooManyRequests(); - response.insert_header(( - "x-ratelimit-limit", - max_requests.to_string(), - )); - response.insert_header(( - "x-ratelimit-remaining", - remaining.to_string(), - )); - response - .insert_header(("x-ratelimit-reset", reset.to_string())); + response.insert_header(("x-ratelimit-limit", max_requests.to_string())); + response.insert_header(("x-ratelimit-remaining", remaining.to_string())); + response.insert_header(("x-ratelimit-reset", reset.to_string())); response.json(ApiError { error: "ratelimit_error", description: &self.to_string(), }) } - _ => actix_web::HttpResponse::build(self.status_code()).json( - ApiError { - error: "ratelimit_error", - description: &self.to_string(), - }, - ), + _ => actix_web::HttpResponse::build(self.status_code()).json(ApiError { + error: "ratelimit_error", + description: &self.to_string(), + }), } } } diff --git a/src/ratelimit/memory.rs b/src/ratelimit/memory.rs index b605026bc..9b8fcddac 100644 --- a/src/ratelimit/memory.rs +++ b/src/ratelimit/memory.rs @@ -36,9 +36,9 @@ impl MemoryStore { pub fn with_capacity(capacity: usize) -> Self { debug!("Creating new MemoryStore"); MemoryStore { - inner: Arc::new( - DashMap::::with_capacity(capacity), - ), + inner: Arc::new(DashMap::::with_capacity( + capacity, + )), } } } @@ -74,18 +74,10 @@ impl Supervised for MemoryStoreActor { impl Handler for MemoryStoreActor { type Result = ActorResponse; - fn handle( - &mut self, - msg: ActorMessage, - ctx: &mut Self::Context, - ) -> Self::Result { + fn handle(&mut self, msg: ActorMessage, ctx: &mut Self::Context) -> Self::Result { match msg { ActorMessage::Set { key, value, expiry } => { - debug!( - "Inserting key {} with expiry {}", - &key, - &expiry.as_secs() - ); + debug!("Inserting key {} with expiry {}", &key, &expiry.as_secs()); let future_key = String::from(&key); let now = SystemTime::now(); let now = now.duration_since(UNIX_EPOCH).unwrap(); @@ -93,10 +85,7 @@ impl Handler for MemoryStoreActor { ctx.notify_later(ActorMessage::Remove(future_key), expiry); ActorResponse::Set(Box::pin(future::ready(Ok(())))) } - ActorMessage::Update { key, value } => match self - .inner - .get_mut(&key) - { + ActorMessage::Update { key, value } => match self.inner.get_mut(&key) { Some(mut c) => { let val_mut: &mut (usize, Duration) = c.value_mut(); if val_mut.0 > value { @@ -107,22 +96,18 @@ impl Handler for MemoryStoreActor { let new_val = val_mut.0; ActorResponse::Update(Box::pin(future::ready(Ok(new_val)))) } - None => ActorResponse::Update(Box::pin(future::ready(Err( - ARError::ReadWrite( - "memory store: read failed!".to_string(), - ), - )))), + None => ActorResponse::Update(Box::pin(future::ready(Err(ARError::ReadWrite( + "memory store: read failed!".to_string(), + ))))), }, ActorMessage::Get(key) => { if self.inner.contains_key(&key) { let val = match self.inner.get(&key) { Some(c) => c, None => { - return ActorResponse::Get(Box::pin(future::ready( - Err(ARError::ReadWrite( - "memory store: read failed!".to_string(), - )), - ))) + return ActorResponse::Get(Box::pin(future::ready(Err( + ARError::ReadWrite("memory store: read failed!".to_string()), + )))) } }; let val = val.value().0; @@ -135,17 +120,14 @@ impl Handler for MemoryStoreActor { let c = match self.inner.get(&key) { Some(d) => d, None => { - return ActorResponse::Expire(Box::pin(future::ready( - Err(ARError::ReadWrite( - "memory store: read failed!".to_string(), - )), - ))) + return ActorResponse::Expire(Box::pin(future::ready(Err( + ARError::ReadWrite("memory store: read failed!".to_string()), + )))) } }; let dur = c.value().1; let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let res = - dur.checked_sub(now).unwrap_or_else(|| Duration::new(0, 0)); + let res = dur.checked_sub(now).unwrap_or_else(|| Duration::new(0, 0)); ActorResponse::Expire(Box::pin(future::ready(Ok(res)))) } ActorMessage::Remove(key) => { @@ -153,11 +135,9 @@ impl Handler for MemoryStoreActor { let val = match self.inner.remove::(&key) { Some(c) => c, None => { - return ActorResponse::Remove(Box::pin(future::ready( - Err(ARError::ReadWrite( - "memory store: remove failed!".to_string(), - )), - ))) + return ActorResponse::Remove(Box::pin(future::ready(Err( + ARError::ReadWrite("memory store: remove failed!".to_string()), + )))) } }; let val = val.1; diff --git a/src/ratelimit/middleware.rs b/src/ratelimit/middleware.rs index e4bf8d6e7..495dcad5e 100644 --- a/src/ratelimit/middleware.rs +++ b/src/ratelimit/middleware.rs @@ -18,8 +18,7 @@ use std::{ time::Duration, }; -type RateLimiterIdentifier = - Rc Result + 'static>>; +type RateLimiterIdentifier = Rc Result + 'static>>; pub struct RateLimiter where @@ -42,8 +41,7 @@ where pub fn new(store: Addr) -> Self { let identifier = |req: &ServiceRequest| { let connection_info = req.connection_info(); - let ip = - connection_info.peer_addr().ok_or(ARError::Identification)?; + let ip = connection_info.peer_addr().ok_or(ARError::Identification)?; Ok(String::from(ip)) }; RateLimiter { @@ -74,9 +72,7 @@ where } /// Function to get the identifier for the client request - pub fn with_identifier< - F: Fn(&ServiceRequest) -> Result + 'static, - >( + pub fn with_identifier Result + 'static>( mut self, identifier: F, ) -> Self { @@ -89,8 +85,7 @@ impl Transform for RateLimiter where T: Handler + Send + Sync + 'static, T::Context: ToEnvelope, - S: Service, Error = AWError> - + 'static, + S: Service, Error = AWError> + 'static, S::Future: 'static, B: 'static, { @@ -130,21 +125,16 @@ where impl Service for RateLimitMiddleware where T: Handler + 'static, - S: Service, Error = AWError> - + 'static, + S: Service, Error = AWError> + 'static, S::Future: 'static, B: 'static, T::Context: ToEnvelope, { type Response = ServiceResponse; type Error = S::Error; - type Future = - Pin>>>; + type Future = Pin>>>; - fn poll_ready( - &self, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.borrow_mut().poll_ready(cx) } @@ -178,15 +168,9 @@ where if let Some(c) = opt { // Existing entry in store let expiry = store - .send(ActorMessage::Expire(String::from( - &identifier, - ))) + .send(ActorMessage::Expire(String::from(&identifier))) .await - .map_err(|_| { - ARError::ReadWrite( - "Setting timeout".to_string(), - ) - })?; + .map_err(|_| ARError::ReadWrite("Setting timeout".to_string()))?; let reset: Duration = match expiry { ActorResponse::Expire(dur) => dur.await?, _ => unreachable!(), @@ -208,9 +192,7 @@ where }) .await .map_err(|_| { - ARError::ReadWrite( - "Decrementing ratelimit".to_string(), - ) + ARError::ReadWrite("Decrementing ratelimit".to_string()) })?; let updated_value: usize = match res { ActorResponse::Update(c) => c.await?, @@ -223,23 +205,15 @@ where // Safe unwraps, since usize is always convertible to string headers.insert( HeaderName::from_static("x-ratelimit-limit"), - HeaderValue::from_str( - max_requests.to_string().as_str(), - )?, + HeaderValue::from_str(max_requests.to_string().as_str())?, ); headers.insert( - HeaderName::from_static( - "x-ratelimit-remaining", - ), - HeaderValue::from_str( - updated_value.to_string().as_str(), - )?, + HeaderName::from_static("x-ratelimit-remaining"), + HeaderValue::from_str(updated_value.to_string().as_str())?, ); headers.insert( HeaderName::from_static("x-ratelimit-reset"), - HeaderValue::from_str( - reset.as_secs().to_string().as_str(), - )?, + HeaderValue::from_str(reset.as_secs().to_string().as_str())?, ); Ok(res) } @@ -253,11 +227,7 @@ where expiry: interval, }) .await - .map_err(|_| { - ARError::ReadWrite( - "Creating store entry".to_string(), - ) - })?; + .map_err(|_| ARError::ReadWrite("Creating store entry".to_string()))?; match res { ActorResponse::Set(c) => c.await?, _ => unreachable!(), @@ -268,24 +238,15 @@ where // Safe unwraps, since usize is always convertible to string headers.insert( HeaderName::from_static("x-ratelimit-limit"), - HeaderValue::from_str( - max_requests.to_string().as_str(), - ) - .unwrap(), + HeaderValue::from_str(max_requests.to_string().as_str()).unwrap(), ); headers.insert( HeaderName::from_static("x-ratelimit-remaining"), - HeaderValue::from_str( - current_value.to_string().as_str(), - ) - .unwrap(), + HeaderValue::from_str(current_value.to_string().as_str()).unwrap(), ); headers.insert( HeaderName::from_static("x-ratelimit-reset"), - HeaderValue::from_str( - interval.as_secs().to_string().as_str(), - ) - .unwrap(), + HeaderValue::from_str(interval.as_secs().to_string().as_str()).unwrap(), ); Ok(res) } diff --git a/src/routes/maven.rs b/src/routes/maven.rs index a21cef0a0..d25d9dd06 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -68,11 +68,8 @@ pub async fn maven_metadata( pool: web::Data, ) -> Result { let project_id = params.into_inner().0; - let project_data = database::models::Project::get_from_slug_or_project_id( - &project_id, - &**pool, - ) - .await?; + let project_data = + database::models::Project::get_from_slug_or_project_id(&project_id, &**pool).await?; let data = if let Some(data) = project_data { data @@ -150,9 +147,7 @@ fn find_file<'a>( version: &'a QueryVersion, file: &str, ) -> Option<&'a QueryFile> { - if let Some(selected_file) = - version.files.iter().find(|x| x.filename == file) - { + if let Some(selected_file) = version.files.iter().find(|x| x.filename == file) { return Some(selected_file); } @@ -193,11 +188,7 @@ pub async fn version_file( ) -> Result { let (project_id, vnum, file) = params.into_inner(); let project_data = - database::models::Project::get_full_from_slug_or_project_id( - &project_id, - &**pool, - ) - .await?; + database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?; let project = if let Some(data) = project_data { data @@ -229,11 +220,9 @@ pub async fn version_file( return Ok(HttpResponse::NotFound().body("")); }; - let version = if let Some(version) = database::models::Version::get_full( - database::models::ids::VersionId(vid.id), - &**pool, - ) - .await? + let version = if let Some(version) = + database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool) + .await? { version } else { @@ -263,9 +252,7 @@ pub async fn version_file( return Ok(HttpResponse::Ok() .content_type("text/xml") .body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?)); - } else if let Some(selected_file) = - find_file(&project_id, &project, &version, &file) - { + } else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) { return Ok(HttpResponse::TemporaryRedirect() .append_header(("location", &*selected_file.url)) .body("")); @@ -282,11 +269,7 @@ pub async fn version_file_sha1( ) -> Result { let (project_id, vnum, file) = params.into_inner(); let project_data = - database::models::Project::get_full_from_slug_or_project_id( - &project_id, - &**pool, - ) - .await?; + database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?; let project = if let Some(data) = project_data { data @@ -318,11 +301,9 @@ pub async fn version_file_sha1( return Ok(HttpResponse::NotFound().body("")); }; - let version = if let Some(version) = database::models::Version::get_full( - database::models::ids::VersionId(vid.id), - &**pool, - ) - .await? + let version = if let Some(version) = + database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool) + .await? { version } else { @@ -343,11 +324,7 @@ pub async fn version_file_sha512( ) -> Result { let (project_id, vnum, file) = params.into_inner(); let project_data = - database::models::Project::get_full_from_slug_or_project_id( - &project_id, - &**pool, - ) - .await?; + database::models::Project::get_full_from_slug_or_project_id(&project_id, &**pool).await?; let project = if let Some(data) = project_data { data @@ -379,11 +356,9 @@ pub async fn version_file_sha512( return Ok(HttpResponse::NotFound().body("")); }; - let version = if let Some(version) = database::models::Version::get_full( - database::models::ids::VersionId(vid.id), - &**pool, - ) - .await? + let version = if let Some(version) = + database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool) + .await? { version } else { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 193ecea36..96cf2dcbb 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -97,30 +97,28 @@ impl actix_web::ResponseError for ApiError { } fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()).json( - crate::models::error::ApiError { - error: match self { - ApiError::Env(..) => "environment_error", - ApiError::SqlxDatabase(..) => "database_error", - ApiError::Database(..) => "database_error", - ApiError::Authentication(..) => "unauthorized", - ApiError::CustomAuthentication(..) => "unauthorized", - ApiError::Xml(..) => "xml_error", - ApiError::Json(..) => "json_error", - ApiError::Search(..) => "search_error", - ApiError::Indexing(..) => "indexing_error", - ApiError::FileHosting(..) => "file_hosting_error", - ApiError::InvalidInput(..) => "invalid_input", - ApiError::Validation(..) => "invalid_input", - ApiError::Analytics(..) => "analytics_error", - ApiError::Crypto(..) => "crypto_error", - ApiError::Payments(..) => "payments_error", - ApiError::DiscordError(..) => "discord_error", - ApiError::Decoding(..) => "decoding_error", - ApiError::ImageError(..) => "invalid_image", - }, - description: &self.to_string(), + HttpResponse::build(self.status_code()).json(crate::models::error::ApiError { + error: match self { + ApiError::Env(..) => "environment_error", + ApiError::SqlxDatabase(..) => "database_error", + ApiError::Database(..) => "database_error", + ApiError::Authentication(..) => "unauthorized", + ApiError::CustomAuthentication(..) => "unauthorized", + ApiError::Xml(..) => "xml_error", + ApiError::Json(..) => "json_error", + ApiError::Search(..) => "search_error", + ApiError::Indexing(..) => "indexing_error", + ApiError::FileHosting(..) => "file_hosting_error", + ApiError::InvalidInput(..) => "invalid_input", + ApiError::Validation(..) => "invalid_input", + ApiError::Analytics(..) => "analytics_error", + ApiError::Crypto(..) => "crypto_error", + ApiError::Payments(..) => "payments_error", + ApiError::DiscordError(..) => "discord_error", + ApiError::Decoding(..) => "decoding_error", + ApiError::ImageError(..) => "invalid_image", }, - ) + description: &self.to_string(), + }) } } diff --git a/src/routes/updates.rs b/src/routes/updates.rs index b0ad0a0ea..ac3d119f4 100644 --- a/src/routes/updates.rs +++ b/src/routes/updates.rs @@ -6,9 +6,7 @@ use sqlx::PgPool; use crate::database; use crate::models::projects::VersionType; -use crate::util::auth::{ - filter_authorized_versions, get_user_from_headers, is_authorized, -}; +use crate::util::auth::{filter_authorized_versions, get_user_from_headers, is_authorized}; use super::ApiError; @@ -26,10 +24,9 @@ pub async fn forge_updates( let (id,) = info.into_inner(); - let project = - database::models::Project::get_from_slug_or_project_id(&id, &**pool) - .await? - .ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?; + let project = database::models::Project::get_from_slug_or_project_id(&id, &**pool) + .await? + .ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); @@ -48,11 +45,9 @@ pub async fn forge_updates( ) .await?; - let versions = - database::models::Version::get_many_full(&version_ids, &**pool).await?; + let versions = database::models::Version::get_many_full(&version_ids, &**pool).await?; - let mut versions = - filter_authorized_versions(versions, &user_option, &pool).await?; + let mut versions = filter_authorized_versions(versions, &user_option, &pool).await?; versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); diff --git a/src/routes/v2/admin.rs b/src/routes/v2/admin.rs index 6d35436da..1bf18be59 100644 --- a/src/routes/v2/admin.rs +++ b/src/routes/v2/admin.rs @@ -37,26 +37,22 @@ pub async fn count_download( download_body: web::Json, download_queue: web::Data>, ) -> Result { - let project_id: crate::database::models::ids::ProjectId = - download_body.project_id.into(); + let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into(); - let id_option = crate::models::ids::base62_impl::parse_base62( - &download_body.version_name, - ) - .ok() - .map(|x| x as i64); + let id_option = crate::models::ids::base62_impl::parse_base62(&download_body.version_name) + .ok() + .map(|x| x as i64); - let (version_id, project_id, file_type) = if let Some(version) = - sqlx::query!( - " + let (version_id, project_id, file_type) = if let Some(version) = sqlx::query!( + " SELECT v.id id, v.mod_id mod_id, file_type FROM files f INNER JOIN versions v ON v.id = f.version_id WHERE f.url = $1 ", - download_body.url, - ) - .fetch_optional(pool.as_ref()) - .await? + download_body.url, + ) + .fetch_optional(pool.as_ref()) + .await? { (version.id, version.mod_id, version.file_type) } else if let Some(version) = sqlx::query!( @@ -143,17 +139,11 @@ pub async fn process_payout( )]) .send() .await - .map_err(|_| { - ApiError::Analytics( - "Error while fetching payout multipliers!".to_string(), - ) - })? + .map_err(|_| ApiError::Analytics("Error while fetching payout multipliers!".to_string()))? .json() .await .map_err(|_| { - ApiError::Analytics( - "Error while deserializing payout multipliers!".to_string(), - ) + ApiError::Analytics("Error while deserializing payout multipliers!".to_string()) })?; struct Project { @@ -176,26 +166,33 @@ pub async fn process_payout( INNER JOIN project_types pt ON pt.id = m.project_type WHERE m.id = ANY($1) AND m.monetization_status = $2 ", - &multipliers.values.keys().flat_map(|x| x.parse::().ok()).collect::>(), + &multipliers + .values + .keys() + .flat_map(|x| x.parse::().ok()) + .collect::>(), MonetizationStatus::Monetized.as_str(), ) - .fetch_many(&mut *transaction) - .try_for_each(|e| { - if let Some(row) = e.right() { - if let Some(project) = projects_map.get_mut(&row.id) { - project.team_members.push((row.user_id, row.payouts_split)); - } else { - projects_map.insert(row.id, Project { + .fetch_many(&mut *transaction) + .try_for_each(|e| { + if let Some(row) = e.right() { + if let Some(project) = projects_map.get_mut(&row.id) { + project.team_members.push((row.user_id, row.payouts_split)); + } else { + projects_map.insert( + row.id, + Project { project_type: row.project_type, team_members: vec![(row.user_id, row.payouts_split)], - split_team_members: Default::default() - }); - } + split_team_members: Default::default(), + }, + ); } + } - futures::future::ready(Ok(())) - }) - .await?; + futures::future::ready(Ok(())) + }) + .await?; // Specific Payout Conditions (ex: modpack payout split) let mut projects_split_dependencies = Vec::new(); @@ -208,8 +205,7 @@ pub async fn process_payout( if !projects_split_dependencies.is_empty() { // (dependent_id, (dependency_id, times_depended)) - let mut project_dependencies: HashMap> = - HashMap::new(); + let mut project_dependencies: HashMap> = HashMap::new(); // dependency_ids to fetch team members from let mut fetch_team_members: Vec = Vec::new(); @@ -229,14 +225,11 @@ pub async fn process_payout( if let Some(row) = e.right() { 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))); } else { - project_dependencies.insert( - row.mod_id, - vec![(row.id, row.times_depended.unwrap_or(0))], - ); + project_dependencies + .insert(row.mod_id, vec![(row.id, row.times_depended.unwrap_or(0))]); } } @@ -245,8 +238,7 @@ pub async fn process_payout( .await?; // (project_id, (user_id, payouts_split)) - let mut team_members: HashMap> = - HashMap::new(); + let mut team_members: HashMap> = HashMap::new(); sqlx::query!( " @@ -263,8 +255,7 @@ pub async fn process_payout( if let Some(project) = team_members.get_mut(&row.id) { project.push((row.user_id, row.payouts_split)); } else { - team_members - .insert(row.id, vec![(row.user_id, row.payouts_split)]); + team_members.insert(row.id, vec![(row.user_id, row.payouts_split)]); } } @@ -281,17 +272,14 @@ pub async fn process_payout( if dep_sum > 0 { for dependency in dependencies { let project_multiplier: Decimal = - Decimal::from(dependency.1) - / Decimal::from(dep_sum); + Decimal::from(dependency.1) / Decimal::from(dep_sum); if let Some(members) = team_members.get(&dependency.0) { - let members_sum: Decimal = - members.iter().map(|x| x.1).sum(); + let members_sum: Decimal = members.iter().map(|x| x.1).sum(); if members_sum > Decimal::ZERO { for member in members { - let member_multiplier: Decimal = - member.1 / members_sum; + let member_multiplier: Decimal = member.1 / members_sum; project.split_team_members.push(( member.0, member_multiplier * project_multiplier, @@ -315,10 +303,8 @@ pub async fn process_payout( let split_given = Decimal::ONE / Decimal::from(5); let split_retention = Decimal::from(4) / Decimal::from(5); - let sum_splits: Decimal = - 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_splits: Decimal = project.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 { for (user_id, split) in project.team_members { @@ -342,8 +328,8 @@ pub async fn process_payout( payout, start ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; sqlx::query!( " @@ -378,8 +364,8 @@ pub async fn process_payout( payout, start ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; sqlx::query!( " diff --git a/src/routes/v2/auth.rs b/src/routes/v2/auth.rs index cc3964ba5..f6c71534a 100644 --- a/src/routes/v2/auth.rs +++ b/src/routes/v2/auth.rs @@ -58,12 +58,8 @@ impl actix_web::ResponseError for AuthorizationError { fn status_code(&self) -> StatusCode { match self { AuthorizationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR, - AuthorizationError::SqlxDatabase(..) => { - StatusCode::INTERNAL_SERVER_ERROR - } - AuthorizationError::Database(..) => { - StatusCode::INTERNAL_SERVER_ERROR - } + AuthorizationError::SqlxDatabase(..) => StatusCode::INTERNAL_SERVER_ERROR, + AuthorizationError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR, AuthorizationError::SerDe(..) => StatusCode::BAD_REQUEST, AuthorizationError::Github(..) => StatusCode::FAILED_DEPENDENCY, AuthorizationError::InvalidCredentials => StatusCode::UNAUTHORIZED, @@ -84,9 +80,7 @@ impl actix_web::ResponseError for AuthorizationError { AuthorizationError::Github(..) => "github_error", AuthorizationError::InvalidCredentials => "invalid_credentials", AuthorizationError::Decoding(..) => "decoding_error", - AuthorizationError::Authentication(..) => { - "authentication_error" - } + AuthorizationError::Authentication(..) => "authentication_error", AuthorizationError::Url => "url_error", AuthorizationError::Banned => "user_banned", }, @@ -119,16 +113,12 @@ pub async fn init( Query(info): Query, client: Data, ) -> Result { - let url = - url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?; + let url = url::Url::parse(&info.url).map_err(|_| AuthorizationError::Url)?; - let allowed_callback_urls = - parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default(); + let allowed_callback_urls = parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default(); let domain = url.domain().ok_or(AuthorizationError::Url)?; - if !allowed_callback_urls.iter().any(|x| domain.ends_with(x)) - && domain != "modrinth.com" - { + if !allowed_callback_urls.iter().any(|x| domain.ends_with(x)) && domain != "modrinth.com" { 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_result = - User::get_from_github_id(user.id, &mut *transaction).await?; + let user_result = User::get_from_github_id(user.id, &mut *transaction).await?; match user_result { Some(_) => {} None => { @@ -231,9 +220,7 @@ pub async fn auth_callback( return Err(AuthorizationError::Banned); } - let user_id = - crate::database::models::generate_user_id(&mut transaction) - .await?; + let user_id = crate::database::models::generate_user_id(&mut transaction).await?; let mut username_increment: i32 = 0; let mut username = None; diff --git a/src/routes/v2/midas.rs b/src/routes/v2/midas.rs index e191c0cca..0ddd474b8 100644 --- a/src/routes/v2/midas.rs +++ b/src/routes/v2/midas.rs @@ -54,17 +54,11 @@ pub async fn init_checkout( ]) .send() .await - .map_err(|_| { - ApiError::Payments( - "Error while creating checkout session!".to_string(), - ) - })? + .map_err(|_| ApiError::Payments("Error while creating checkout session!".to_string()))? .json::() .await .map_err(|_| { - ApiError::Payments( - "Error while deserializing checkout response!".to_string(), - ) + ApiError::Payments("Error while deserializing checkout response!".to_string()) })?; Ok(HttpResponse::Ok().json(json!( @@ -92,11 +86,7 @@ pub async fn init_customer_portal( .fetch_optional(&**pool) .await? .and_then(|x| x.stripe_customer_id) - .ok_or_else(|| { - ApiError::InvalidInput( - "User is not linked to stripe account!".to_string(), - ) - })?; + .ok_or_else(|| ApiError::InvalidInput("User is not linked to stripe account!".to_string()))?; let client = reqwest::Client::new(); @@ -117,17 +107,11 @@ pub async fn init_customer_portal( ]) .send() .await - .map_err(|_| { - ApiError::Payments( - "Error while creating billing session!".to_string(), - ) - })? + .map_err(|_| ApiError::Payments("Error while creating billing session!".to_string()))? .json::() .await .map_err(|_| { - ApiError::Payments( - "Error while deserializing billing response!".to_string(), - ) + ApiError::Payments("Error while deserializing billing response!".to_string()) })?; Ok(HttpResponse::Ok().json(json!( @@ -166,27 +150,25 @@ pub async fn handle_stripe_webhook( if let Some(signature) = signature { type HmacSha256 = Hmac; - let mut key = HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes()).map_err(|_| { - ApiError::Crypto( - "Unable to initialize HMAC instance due to invalid key length!".to_string(), - ) - })?; + let mut key = + HmacSha256::new_from_slice(dotenvy::var("STRIPE_WEBHOOK_SECRET")?.as_bytes()) + .map_err(|_| { + ApiError::Crypto( + "Unable to initialize HMAC instance due to invalid key length!" + .to_string(), + ) + })?; key.update(format!("{timestamp}.{body}").as_bytes()); key.verify(&signature).map_err(|_| { - ApiError::Crypto( - "Unable to verify webhook signature!".to_string(), - ) + ApiError::Crypto("Unable to verify webhook signature!".to_string()) })?; if timestamp < (Utc::now() - Duration::minutes(5)).timestamp() - || timestamp - > (Utc::now() + Duration::minutes(5)).timestamp() + || timestamp > (Utc::now() + Duration::minutes(5)).timestamp() { - return Err(ApiError::Crypto( - "Webhook signature expired!".to_string(), - )); + return Err(ApiError::Crypto("Webhook signature expired!".to_string())); } } else { 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 match &*webhook.type_ { "checkout.session.completed" => { - let session: CheckoutSession = - serde_json::from_value(webhook.data.object)?; + let session: CheckoutSession = serde_json::from_value(webhook.data.object)?; sqlx::query!( " @@ -276,8 +257,7 @@ pub async fn handle_stripe_webhook( if let Some(item) = invoice.lines.data.first() { let expires: DateTime = DateTime::from_utc( - NaiveDateTime::from_timestamp_opt(item.period.end, 0) - .unwrap_or_default(), + NaiveDateTime::from_timestamp_opt(item.period.end, 0).unwrap_or_default(), Utc, ) + Duration::days(1); @@ -323,8 +303,7 @@ pub async fn handle_stripe_webhook( } } "customer.subscription.deleted" => { - let session: Subscription = - serde_json::from_value(webhook.data.object)?; + let session: Subscription = serde_json::from_value(webhook.data.object)?; sqlx::query!( " @@ -334,8 +313,8 @@ pub async fn handle_stripe_webhook( ", session.customer, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } _ => {} }; diff --git a/src/routes/v2/moderation.rs b/src/routes/v2/moderation.rs index 90458b2d7..bc7fa228d 100644 --- a/src/routes/v2/moderation.rs +++ b/src/routes/v2/moderation.rs @@ -46,18 +46,15 @@ pub async fn get_projects( count.count as i64 ) .fetch_many(&**pool) - .try_filter_map(|e| async { - Ok(e.right().map(|m| database::models::ProjectId(m.id))) - }) + .try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ProjectId(m.id))) }) .try_collect::>() .await?; - let projects: Vec<_> = - database::Project::get_many_full(&project_ids, &**pool) - .await? - .into_iter() - .map(crate::models::projects::Project::from) - .collect(); + let projects: Vec<_> = database::Project::get_many_full(&project_ids, &**pool) + .await? + .into_iter() + .map(crate::models::projects::Project::from) + .collect(); Ok(HttpResponse::Ok().json(projects)) } diff --git a/src/routes/v2/notifications.rs b/src/routes/v2/notifications.rs index a4c14cd62..e47f3403b 100644 --- a/src/routes/v2/notifications.rs +++ b/src/routes/v2/notifications.rs @@ -3,17 +3,19 @@ use crate::models::ids::NotificationId; use crate::models::notifications::Notification; use crate::routes::ApiError; 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 sqlx::PgPool; pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(notifications_get); cfg.service(notifications_delete); + cfg.service(notifications_read); cfg.service( web::scope("notification") .service(notification_get) + .service(notifications_read) .service(notification_delete), ); } @@ -31,7 +33,6 @@ pub async fn notifications_get( ) -> Result { 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::NotificationId as DBNotificationId; @@ -42,11 +43,8 @@ pub async fn notifications_get( .collect(); let notifications_data: Vec = - database::models::notification_item::Notification::get_many( - ¬ification_ids, - &**pool, - ) - .await?; + database::models::notification_item::Notification::get_many(¬ification_ids, &**pool) + .await?; let notifications: Vec = notifications_data .into_iter() @@ -68,11 +66,7 @@ pub async fn notification_get( let id = info.into_inner().0; let notification_data = - database::models::notification_item::Notification::get( - id.into(), - &**pool, - ) - .await?; + database::models::notification_item::Notification::get(id.into(), &**pool).await?; if let Some(data) = notification_data { 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, +) -> Result { + 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}")] pub async fn notification_delete( req: HttpRequest, @@ -96,29 +123,21 @@ pub async fn notification_delete( let id = info.into_inner().0; let notification_data = - database::models::notification_item::Notification::get( - id.into(), - &**pool, - ) - .await?; + 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::remove( - id.into(), - &mut transaction, - ) - .await?; + database::models::notification_item::Notification::remove(id.into(), &mut transaction) + .await?; transaction.commit().await?; Ok(HttpResponse::NoContent().body("")) } else { Err(ApiError::CustomAuthentication( - "You are not authorized to delete this notification!" - .to_string(), + "You are not authorized to delete this notification!".to_string(), )) } } else { @@ -126,6 +145,41 @@ pub async fn notification_delete( } } +#[patch("notifications")] +pub async fn notifications_read( + req: HttpRequest, + web::Query(ids): web::Query, + pool: web::Data, +) -> Result { + let user = get_user_from_headers(req.headers(), &**pool).await?; + + let notification_ids = serde_json::from_str::>(&ids.ids)? + .into_iter() + .map(|x| x.into()) + .collect::>(); + + let mut transaction = pool.begin().await?; + + let notifications_data = + database::models::notification_item::Notification::get_many(¬ification_ids, &**pool) + .await?; + + let mut notifications: Vec = 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")] pub async fn notifications_delete( req: HttpRequest, @@ -134,23 +188,18 @@ pub async fn notifications_delete( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let notification_ids = - serde_json::from_str::>(&ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect::>(); + let notification_ids = serde_json::from_str::>(&ids.ids)? + .into_iter() + .map(|x| x.into()) + .collect::>(); let mut transaction = pool.begin().await?; let notifications_data = - database::models::notification_item::Notification::get_many( - ¬ification_ids, - &**pool, - ) - .await?; + database::models::notification_item::Notification::get_many(¬ification_ids, &**pool) + .await?; - let mut notifications: Vec = - Vec::new(); + let mut notifications: Vec = Vec::new(); for notification in notifications_data { if notification.user_id == user.id.into() || user.role.is_admin() { diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index bb9895dce..256b2c108 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -4,8 +4,8 @@ use crate::database::models::thread_item::ThreadBuilder; use crate::file_hosting::{FileHost, FileHostingError}; use crate::models::error::ApiError; use crate::models::projects::{ - DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, - SideType, VersionId, VersionStatus, + DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, SideType, VersionId, + VersionStatus, }; use crate::models::threads::ThreadType; use crate::models::users::UserId; @@ -79,14 +79,10 @@ impl actix_web::ResponseError for CreateError { fn status_code(&self) -> StatusCode { match self { CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR, - CreateError::SqlxDatabaseError(..) => { - StatusCode::INTERNAL_SERVER_ERROR - } + CreateError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR, CreateError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR, CreateError::IndexingError(..) => StatusCode::INTERNAL_SERVER_ERROR, - CreateError::FileHostingError(..) => { - StatusCode::INTERNAL_SERVER_ERROR - } + CreateError::FileHostingError(..) => StatusCode::INTERNAL_SERVER_ERROR, CreateError::SerDeError(..) => StatusCode::BAD_REQUEST, CreateError::MultipartError(..) => StatusCode::BAD_REQUEST, CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST, @@ -97,9 +93,7 @@ impl actix_web::ResponseError for CreateError { CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST, CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST, CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED, - CreateError::CustomAuthenticationError(..) => { - StatusCode::UNAUTHORIZED - } + CreateError::CustomAuthenticationError(..) => StatusCode::UNAUTHORIZED, CreateError::SlugCollision => StatusCode::BAD_REQUEST, CreateError::ValidationError(..) => StatusCode::BAD_REQUEST, CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST, @@ -347,21 +341,17 @@ async fn project_create_inner( let cdn_url = dotenvy::var("CDN_URL")?; // The currently logged in user - let current_user = - get_user_from_headers(req.headers(), &mut *transaction).await?; + let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?; - let project_id: ProjectId = - models::generate_project_id(transaction).await?.into(); + let project_id: ProjectId = models::generate_project_id(transaction).await?.into(); let project_create_data; let mut versions; let mut versions_map = std::collections::HashMap::new(); let mut gallery_urls = Vec::new(); - let all_game_versions = - models::categories::GameVersion::list(&mut *transaction).await?; - let all_loaders = - models::categories::Loader::list(&mut *transaction).await?; + let all_game_versions = models::categories::GameVersion::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 @@ -378,9 +368,9 @@ async fn project_create_inner( })?; let content_disposition = field.content_disposition(); - let name = content_disposition.get_name().ok_or_else(|| { - CreateError::MissingValueError(String::from("Missing content name")) - })?; + let name = content_disposition + .get_name() + .ok_or_else(|| CreateError::MissingValueError(String::from("Missing content name")))?; if name != "data" { return Err(CreateError::InvalidInput(String::from( @@ -390,22 +380,19 @@ async fn project_create_inner( let mut data = Vec::new(); while let Some(chunk) = field.next().await { - data.extend_from_slice( - &chunk.map_err(CreateError::MultipartError)?, - ); + data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); } let create_data: ProjectCreateData = serde_json::from_slice(&data)?; - create_data.validate().map_err(|err| { - CreateError::InvalidInput(validation_errors_to_string(err, None)) - })?; + create_data + .validate() + .map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?; let slug_project_id_option: Option = serde_json::from_str(&format!("\"{}\"", create_data.slug)).ok(); if let Some(slug_project_id) = slug_project_id_option { - let slug_project_id: models::ids::ProjectId = - slug_project_id.into(); + let slug_project_id: models::ids::ProjectId = slug_project_id.into(); let results = sqlx::query!( " 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 name = content_disposition.get_name().ok_or_else(|| { - CreateError::MissingValueError( - "Missing content name".to_string(), - ) + CreateError::MissingValueError("Missing content name".to_string()) })?; let (file_name, file_extension) = @@ -528,9 +513,7 @@ async fn project_create_inner( ))); } - if let Some(item) = - gallery_items.iter().find(|x| x.item == name) - { + if let Some(item) = gallery_items.iter().find(|x| x.item == name) { let data = read_from_field( &mut field, 5 * (1 << 20), @@ -540,22 +523,13 @@ async fn project_create_inner( let hash = sha1::Sha1::from(&data).hexdigest(); let (_, file_extension) = - super::version_creation::get_name_ext( - &content_disposition, - )?; - let content_type = - crate::util::ext::get_image_content_type( - file_extension, - ) + super::version_creation::get_name_ext(&content_disposition)?; + let content_type = crate::util::ext::get_image_content_type(file_extension) .ok_or_else(|| { - CreateError::InvalidIconFormat( - file_extension.to_string(), - ) + CreateError::InvalidIconFormat(file_extension.to_string()) })?; - let url = format!( - "data/{project_id}/images/{hash}.{file_extension}" - ); + let url = format!("data/{project_id}/images/{hash}.{file_extension}"); let upload_data = file_host .upload_file(content_type, &url, data.freeze()) .await?; @@ -588,8 +562,7 @@ async fn project_create_inner( // `index` is always valid for these lists let created_version = versions.get_mut(index).unwrap(); - let version_data = - project_create_data.initial_versions.get(index).unwrap(); + let version_data = project_create_data.initial_versions.get(index).unwrap(); // Upload the new jar file super::version_creation::upload_file( @@ -642,8 +615,7 @@ async fn project_create_inner( } // Convert the list of category names to actual categories - let mut categories = - Vec::with_capacity(project_create_data.categories.len()); + let mut categories = Vec::with_capacity(project_create_data.categories.len()); for category in &project_create_data.categories { let id = models::categories::Category::get_id_project( category, @@ -706,9 +678,7 @@ async fn project_create_inner( ) .await? .ok_or_else(|| { - CreateError::InvalidInput( - "Client side type specified does not exist.".to_string(), - ) + CreateError::InvalidInput("Client side type specified does not exist.".to_string()) })?; let server_side_id = models::categories::SideType::get_id( @@ -717,35 +687,27 @@ async fn project_create_inner( ) .await? .ok_or_else(|| { - CreateError::InvalidInput( - "Server side type specified does not exist.".to_string(), - ) + CreateError::InvalidInput("Server side type specified does not exist.".to_string()) })?; - let license_id = spdx::Expression::parse( - &project_create_data.license_id, - ) - .map_err(|err| { - CreateError::InvalidInput(format!( - "Invalid SPDX license identifier: {err}" - )) - })?; + let license_id = + spdx::Expression::parse(&project_create_data.license_id).map_err(|err| { + CreateError::InvalidInput(format!("Invalid SPDX license identifier: {err}")) + })?; let mut donation_urls = vec![]; if let Some(urls) = &project_create_data.donation_urls { for url in urls { - let platform_id = models::categories::DonationPlatform::get_id( - &url.id, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - CreateError::InvalidInput(format!( - "Donation platform {} does not exist.", - url.id.clone() - )) - })?; + let platform_id = + models::categories::DonationPlatform::get_id(&url.id, &mut *transaction) + .await? + .ok_or_else(|| { + CreateError::InvalidInput(format!( + "Donation platform {} does not exist.", + url.id.clone() + )) + })?; donation_urls.push(models::project_item::DonationUrl { platform_id, @@ -856,16 +818,10 @@ async fn project_create_inner( let _project_id = project_builder.insert(&mut *transaction).await?; if status == ProjectStatus::Processing { - if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") - { - crate::util::webhook::send_discord_webhook( - response.id, - pool, - webhook_url, - None, - ) - .await - .ok(); + if let Ok(webhook_url) = dotenvy::var("MODERATION_DISCORD_WEBHOOK") { + crate::util::webhook::send_discord_webhook(response.id, pool, webhook_url, None) + .await + .ok(); } } @@ -888,13 +844,12 @@ async fn create_initial_version( ))); } - version_data.validate().map_err(|err| { - CreateError::ValidationError(validation_errors_to_string(err, None)) - })?; + version_data + .validate() + .map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?; // Randomly generate a new id to be used for the version - let version_id: VersionId = - models::generate_version_id(transaction).await?.into(); + let version_id: VersionId = models::generate_version_id(transaction).await?.into(); let game_versions = version_data .game_versions @@ -963,15 +918,8 @@ async fn process_icon_upload( mut field: Field, cdn_url: &str, ) -> Result<(String, Option), CreateError> { - if let Some(content_type) = - crate::util::ext::get_image_content_type(file_extension) - { - let data = read_from_field( - &mut field, - 262144, - "Icons must be smaller than 256KiB", - ) - .await?; + if let Some(content_type) = 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 color = crate::util::img::get_color_from_img(&data)?; diff --git a/src/routes/v2/projects.rs b/src/routes/v2/projects.rs index 8bcb797d4..65b58ccbe 100644 --- a/src/routes/v2/projects.rs +++ b/src/routes/v2/projects.rs @@ -6,16 +6,13 @@ use crate::models; use crate::models::ids::base62_impl::parse_base62; use crate::models::notifications::NotificationBody; use crate::models::projects::{ - DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, - SearchRequest, SideType, + DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, SideType, }; use crate::models::teams::Permissions; use crate::models::threads::MessageBody; use crate::routes::ApiError; use crate::search::{search_for_project, SearchConfig, SearchError}; -use crate::util::auth::{ - filter_authorized_projects, get_user_from_headers, is_authorized, -}; +use crate::util::auth::{filter_authorized_projects, get_user_from_headers, is_authorized}; use crate::util::routes::read_from_payload; use crate::util::validate::validation_errors_to_string; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; @@ -78,30 +75,30 @@ pub async fn random_projects_get( web::Query(count): web::Query, pool: web::Data, ) -> Result { - count.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + count + .validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let project_ids = sqlx::query!( - " + " SELECT id FROM mods TABLESAMPLE SYSTEM_ROWS($1) WHERE status = ANY($2) ", - count.count as i32, - &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_searchable()).map(|x| x.to_string()).collect::>(), - ) - .fetch_many(&**pool) - .try_filter_map(|e| async { - Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) - }) - .try_collect::>() - .await?; + count.count as i32, + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_searchable()) + .map(|x| x.to_string()) + .collect::>(), + ) + .fetch_many(&**pool) + .try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) }) + .try_collect::>() + .await?; - let projects_data = - database::models::Project::get_many_full(&project_ids, &**pool) - .await? - .into_iter() - .map(Project::from) - .collect::>(); + let projects_data = database::models::Project::get_many_full(&project_ids, &**pool) + .await? + .into_iter() + .map(Project::from) + .collect::>(); Ok(HttpResponse::Ok().json(projects_data)) } @@ -123,13 +120,11 @@ pub async fn projects_get( .map(|x| x.into()) .collect(); - let projects_data = - database::models::Project::get_many_full(&project_ids, &**pool).await?; + let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); - let projects = - filter_authorized_projects(projects_data, &user_option, &pool).await?; + let projects = filter_authorized_projects(projects_data, &user_option, &pool).await?; Ok(HttpResponse::Ok().json(projects)) } @@ -143,10 +138,7 @@ pub async fn project_get( let string = info.into_inner().0; let project_data = - database::models::Project::get_full_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); @@ -229,10 +221,7 @@ pub async fn dependency_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?; 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 { Ok(e.right().map(|x| { ( - x.dependency_id - .map(database::models::VersionId), - if x.mod_id == Some(0) { None } else { x.mod_id - .map(database::models::ProjectId) }, - x.mod_dependency_id - .map(database::models::ProjectId), + x.dependency_id.map(database::models::VersionId), + if x.mod_id == Some(0) { + None + } else { + x.mod_id.map(database::models::ProjectId) + }, + x.mod_dependency_id.map(database::models::ProjectId), ) })) }) @@ -430,15 +420,13 @@ pub async fn project_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - new_project.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + new_project + .validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let string = info.into_inner().0; - let result = database::models::Project::get_full_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + let result = + database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?; if let Some(project_item) = result { let id = project_item.inner.id; @@ -456,8 +444,7 @@ pub async fn project_edit( } else if let Some(ref member) = team_member { permissions = Some(member.permissions) } else if user.role.is_mod() { - permissions = - Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY) + permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY) } else { permissions = None } @@ -518,12 +505,10 @@ pub async fn project_edit( if !(user.role.is_mod() || !project_item.inner.status.is_approved() && status == &ProjectStatus::Processing - || project_item.inner.status.is_approved() - && status.can_be_requested()) + || project_item.inner.status.is_approved() && status.can_be_requested()) { return Err(ApiError::CustomAuthentication( - "You don't have permission to set this status!" - .to_string(), + "You don't have permission to set this status!".to_string(), )); } @@ -545,9 +530,7 @@ pub async fn project_edit( .execute(&mut *transaction) .await?; - 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( project_item.inner.id.into(), &pool, @@ -559,9 +542,7 @@ pub async fn project_edit( } } - if status.is_approved() - && !project_item.inner.status.is_approved() - { + if status.is_approved() && !project_item.inner.status.is_approved() { sqlx::query!( " UPDATE mods @@ -575,9 +556,7 @@ pub async fn project_edit( } if status.is_searchable() && !project_item.inner.webhook_sent { - if let Ok(webhook_url) = - dotenvy::var("PUBLIC_DISCORD_WEBHOOK") - { + if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") { crate::util::webhook::send_discord_webhook( project_item.inner.id.into(), &pool, @@ -607,8 +586,7 @@ pub async fn project_edit( FROM team_members tm WHERE tm.team_id = $1 AND tm.accepted ", - project_item.inner.team_id - as database::models::ids::TeamId + project_item.inner.team_id as database::models::ids::TeamId ) .fetch_many(&mut *transaction) .try_filter_map(|e| async { @@ -653,9 +631,7 @@ pub async fn project_edit( .execute(&mut *transaction) .await?; - if project_item.inner.status.is_searchable() - && !status.is_searchable() - { + if project_item.inner.status.is_searchable() && !status.is_searchable() { delete_from_index(id.into(), config).await?; } } @@ -726,17 +702,14 @@ pub async fn project_edit( for category in categories { let category_id = - database::models::categories::Category::get_id( - category, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; + database::models::categories::Category::get_id(category, &mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!( + "Category {} does not exist.", + category.clone() + )) + })?; sqlx::query!( " @@ -761,17 +734,14 @@ pub async fn project_edit( for category in categories { let category_id = - database::models::categories::Category::get_id( - category, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; + database::models::categories::Category::get_id(category, &mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!( + "Category {} does not exist.", + category.clone() + )) + })?; sqlx::query!( " @@ -899,8 +869,7 @@ pub async fn project_edit( )); } - let slug_project_id_option: Option = - parse_base62(slug).ok(); + let slug_project_id_option: Option = parse_base62(slug).ok(); if let Some(slug_project_id) = slug_project_id_option { let results = sqlx::query!( " @@ -913,8 +882,7 @@ pub async fn project_edit( if results.exists.unwrap_or(true) { return Err(ApiError::InvalidInput( - "Slug collides with other project's id!" - .to_string(), + "Slug collides with other project's id!".to_string(), )); } } @@ -933,8 +901,7 @@ pub async fn project_edit( if results.exists.unwrap_or(true) { return Err(ApiError::InvalidInput( - "Slug collides with other project's id!" - .to_string(), + "Slug collides with other project's id!".to_string(), )); } } @@ -960,13 +927,12 @@ pub async fn project_edit( )); } - let side_type_id = - database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = database::models::categories::SideType::get_id( + new_side.as_str(), + &mut *transaction, + ) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -989,13 +955,12 @@ pub async fn project_edit( )); } - let side_type_id = - database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = database::models::categories::SideType::get_id( + new_side.as_str(), + &mut *transaction, + ) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -1025,9 +990,7 @@ pub async fn project_edit( } spdx::Expression::parse(&license).map_err(|err| { - ApiError::InvalidInput(format!( - "Invalid SPDX license identifier: {err}" - )) + ApiError::InvalidInput(format!("Invalid SPDX license identifier: {err}")) })?; sqlx::query!( @@ -1062,18 +1025,17 @@ pub async fn project_edit( .await?; for donation in donations { - let platform_id = - database::models::categories::DonationPlatform::get_id( - &donation.id, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Platform {} does not exist.", - donation.id.clone() - )) - })?; + let platform_id = database::models::categories::DonationPlatform::get_id( + &donation.id, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!( + "Platform {} does not exist.", + donation.id.clone() + )) + })?; sqlx::query!( " @@ -1090,9 +1052,7 @@ pub async fn project_edit( } if let Some(moderation_message) = &new_project.moderation_message { - 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( "You do not have the permissions to edit the moderation message of this project!" .to_string(), @@ -1112,12 +1072,8 @@ pub async fn project_edit( .await?; } - if let Some(moderation_message_body) = - &new_project.moderation_message_body - { - if !user.role.is_mod() - && project_item.inner.status != ProjectStatus::Approved - { + if let Some(moderation_message_body) = &new_project.moderation_message_body { + if !user.role.is_mod() && project_item.inner.status != ProjectStatus::Approved { return Err(ApiError::CustomAuthentication( "You do not have the permissions to edit the moderation message body of this project!" .to_string(), @@ -1158,8 +1114,7 @@ pub async fn project_edit( .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) { return Err(ApiError::CustomAuthentication( "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 - == MonetizationStatus::ForceDemonetized + if (*monetization_status == MonetizationStatus::ForceDemonetized || project_item.inner.monetization_status == MonetizationStatus::ForceDemonetized) && !user.role.is_mod() @@ -1276,9 +1230,9 @@ pub async fn projects_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - bulk_edit_project.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + bulk_edit_project + .validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let project_ids: Vec = serde_json::from_str::>(&ids.ids)? @@ -1286,8 +1240,7 @@ pub async fn projects_edit( .map(|x| x.into()) .collect(); - let projects_data = - database::models::Project::get_many_full(&project_ids, &**pool).await?; + let projects_data = database::models::Project::get_many_full(&project_ids, &**pool).await?; if let Some(id) = project_ids .iter() @@ -1303,15 +1256,11 @@ pub async fn projects_edit( .iter() .map(|x| x.inner.team_id) .collect::>(); - let team_members = database::models::TeamMember::get_from_team_full_many( - &team_ids, &**pool, - ) - .await?; + let team_members = + database::models::TeamMember::get_from_team_full_many(&team_ids, &**pool).await?; - let categories = - database::models::categories::Category::list(&**pool).await?; - let donation_platforms = - database::models::categories::DonationPlatform::list(&**pool).await?; + let categories = database::models::categories::Category::list(&**pool).await?; + let donation_platforms = database::models::categories::DonationPlatform::list(&**pool).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) { if !member.permissions.contains(Permissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - format!("You do not have the permissions to bulk edit project {}!", project.inner.title), - )); + return Err(ApiError::CustomAuthentication(format!( + "You do not have the permissions to bulk edit project {}!", + project.inner.title + ))); } } else if project.inner.status.is_hidden() { return Ok(HttpResponse::NotFound().body("")); @@ -1336,18 +1286,15 @@ pub async fn projects_edit( }; } - let mut set_categories = - if let Some(categories) = bulk_edit_project.categories.clone() { - categories - } else { - project.categories.clone() - }; + let mut set_categories = if let Some(categories) = bulk_edit_project.categories.clone() { + categories + } else { + project.categories.clone() + }; if let Some(delete_categories) = &bulk_edit_project.remove_categories { for category in delete_categories { - if let Some(pos) = - set_categories.iter().position(|x| x == category) - { + if let Some(pos) = set_categories.iter().position(|x| x == category) { set_categories.remove(pos); } } @@ -1394,34 +1341,27 @@ pub async fn projects_edit( project.inner.id as database::models::ids::ProjectId, category_id as database::models::ids::CategoryId, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } } - let mut set_additional_categories = if let Some(categories) = - bulk_edit_project.additional_categories.clone() - { - categories - } else { - project.additional_categories.clone() - }; + let mut set_additional_categories = + if let Some(categories) = bulk_edit_project.additional_categories.clone() { + categories + } else { + project.additional_categories.clone() + }; - if let Some(delete_categories) = - &bulk_edit_project.remove_additional_categories - { + if let Some(delete_categories) = &bulk_edit_project.remove_additional_categories { for category in delete_categories { - if let Some(pos) = - set_additional_categories.iter().position(|x| x == category) - { + if let Some(pos) = set_additional_categories.iter().position(|x| x == category) { set_additional_categories.remove(pos); } } } - if let Some(add_categories) = - &bulk_edit_project.add_additional_categories - { + if let Some(add_categories) = &bulk_edit_project.add_additional_categories { for category in add_categories { if set_additional_categories.len() < 256 { set_additional_categories.push(category.clone()); @@ -1476,16 +1416,14 @@ pub async fn projects_edit( url: d.url, }) .collect(); - let mut set_donation_links = if let Some(donation_links) = - bulk_edit_project.donation_urls.clone() - { - donation_links - } else { - project_donations.clone() - }; + let mut set_donation_links = + if let Some(donation_links) = bulk_edit_project.donation_urls.clone() { + donation_links + } else { + 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 { if let Some(pos) = set_donation_links .iter() @@ -1532,8 +1470,8 @@ pub async fn projects_edit( platform_id as database::models::ids::DonationPlatformId, donation.url ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } } @@ -1616,8 +1554,7 @@ pub async fn project_schedule( if scheduling_data.time < Utc::now() { return Err(ApiError::InvalidInput( - "You cannot schedule a project to be released in the past!" - .to_string(), + "You cannot schedule a project to be released in the past!".to_string(), )); } @@ -1628,10 +1565,7 @@ pub async fn project_schedule( } let string = info.into_inner().0; - let result = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?; if let Some(project_item) = result { let team_member = database::models::TeamMember::get_from_user_id( @@ -1684,22 +1618,15 @@ pub async fn project_icon_edit( file_host: web::Data>, mut payload: web::Payload, ) -> Result { - if let Some(content_type) = - crate::util::ext::get_image_content_type(&ext.ext) - { + if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) { let cdn_url = dotenvy::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - let project_item = - database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) + let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool) .await? .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) + ApiError::InvalidInput("The specified project does not exist!".to_string()) })?; if !user.role.is_mod() { @@ -1711,15 +1638,12 @@ pub async fn project_icon_edit( .await .map_err(ApiError::Database)? .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.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this project's icon." - .to_string(), + "You don't have permission to edit this project's icon.".to_string(), )); } } @@ -1732,12 +1656,8 @@ pub async fn project_icon_edit( } } - let bytes = read_from_payload( - &mut payload, - 262144, - "Icons must be smaller than 256KiB", - ) - .await?; + let bytes = + read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?; 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 string = info.into_inner().0; - let project_item = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -1806,15 +1722,12 @@ pub async fn delete_project_icon( .await .map_err(ApiError::Database)? .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.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this project's icon." - .to_string(), + "You don't have permission to edit this project's icon.".to_string(), )); } } @@ -1866,32 +1779,24 @@ pub async fn add_gallery_item( file_host: web::Data>, mut payload: web::Payload, ) -> Result { - if let Some(content_type) = - crate::util::ext::get_image_content_type(&ext.ext) - { - item.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) { + item.validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let cdn_url = dotenvy::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; let project_item = - database::models::Project::get_full_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + database::models::Project::get_full_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if project_item.gallery_items.len() > 64 { return Err(ApiError::CustomAuthentication( - "You have reached the maximum of gallery images to upload." - .to_string(), + "You have reached the maximum of gallery images to upload.".to_string(), )); } @@ -1904,15 +1809,12 @@ pub async fn add_gallery_item( .await .map_err(ApiError::Database)? .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.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this project's gallery." - .to_string(), + "You don't have permission to edit this project's gallery.".to_string(), )); } } @@ -2013,19 +1915,14 @@ pub async fn edit_gallery_item( let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - 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 project_item = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -2036,15 +1933,12 @@ pub async fn edit_gallery_item( .await .map_err(ApiError::Database)? .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.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this project's gallery." - .to_string(), + "You don't have permission to edit this project's gallery.".to_string(), )); } } @@ -2157,15 +2051,11 @@ pub async fn delete_gallery_item( let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - let project_item = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -2176,15 +2066,12 @@ pub async fn delete_gallery_item( .await .map_err(ApiError::Database)? .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.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this project's gallery." - .to_string(), + "You don't have permission to edit this project's gallery.".to_string(), )); } } @@ -2241,30 +2128,23 @@ pub async fn project_delete( let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - let project = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let project = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if !user.role.is_admin() { - let team_member = - database::models::TeamMember::get_from_user_id_project( - project.id, - user.id.into(), - &**pool, - ) - .await - .map_err(ApiError::Database)? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let team_member = database::models::TeamMember::get_from_user_id_project( + project.id, + user.id.into(), + &**pool, + ) + .await + .map_err(ApiError::Database)? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; if !team_member .permissions @@ -2278,9 +2158,7 @@ pub async fn project_delete( let mut transaction = pool.begin().await?; - let result = - database::models::Project::remove_full(project.id, &mut transaction) - .await?; + let result = database::models::Project::remove_full(project.id, &mut transaction).await?; transaction.commit().await?; @@ -2302,15 +2180,11 @@ pub async fn project_follow( let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - let result = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; let user_id: database::models::ids::UserId = user.id.into(); let project_id: database::models::ids::ProjectId = result.id; @@ -2375,15 +2249,11 @@ pub async fn project_unfollow( let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; - let result = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The specified project does not exist!".to_string(), - ) - })?; + let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The specified project does not exist!".to_string()) + })?; let user_id: database::models::ids::UserId = user.id.into(); let project_id = result.id; @@ -2439,8 +2309,7 @@ pub async fn delete_from_index( id: ProjectId, config: web::Data, ) -> Result<(), meilisearch_sdk::errors::Error> { - let client = - meilisearch_sdk::client::Client::new(&*config.address, &*config.key); + let client = meilisearch_sdk::client::Client::new(&*config.address, &*config.key); let indexes: IndexesResults = client.get_indexes().await?; diff --git a/src/routes/v2/reports.rs b/src/routes/v2/reports.rs index 3db2b933c..17c0eb72a 100644 --- a/src/routes/v2/reports.rs +++ b/src/routes/v2/reports.rs @@ -1,15 +1,9 @@ -use crate::database::models::thread_item::{ - ThreadBuilder, ThreadMessageBuilder, -}; -use crate::models::ids::{ - base62_impl::parse_base62, ProjectId, UserId, VersionId, -}; +use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder}; +use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId}; use crate::models::reports::{ItemType, Report}; use crate::models::threads::{MessageBody, ThreadType}; use crate::routes::ApiError; -use crate::util::auth::{ - check_is_moderator_from_headers, get_user_from_headers, -}; +use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers}; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; use chrono::Utc; use futures::StreamExt; @@ -41,31 +35,24 @@ pub async fn report_create( ) -> Result { let mut transaction = pool.begin().await?; - let current_user = - get_user_from_headers(req.headers(), &mut *transaction).await?; + let current_user = get_user_from_headers(req.headers(), &mut *transaction).await?; let mut bytes = web::BytesMut::new(); while let Some(item) = body.next().await { bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInput( - "Error while parsing request payload!".to_string(), - ) + ApiError::InvalidInput("Error while parsing request payload!".to_string()) })?); } let new_report: CreateReport = serde_json::from_slice(bytes.as_ref())?; - let id = - crate::database::models::generate_report_id(&mut transaction).await?; + let id = crate::database::models::generate_report_id(&mut transaction).await?; let report_type = crate::database::models::categories::ReportType::get_id( &new_report.report_type, &mut *transaction, ) .await? .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Invalid report type: {}", - new_report.report_type - )) + ApiError::InvalidInput(format!("Invalid report type: {}", new_report.report_type)) })?; let thread_id = ThreadBuilder { @@ -92,8 +79,7 @@ pub async fn report_create( match new_report.item_type { ItemType::Project => { - let project_id = - ProjectId(parse_base62(new_report.item_id.as_str())?); + let project_id = ProjectId(parse_base62(new_report.item_id.as_str())?); let result = sqlx::query!( "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()) } ItemType::Version => { - let version_id = - VersionId(parse_base62(new_report.item_id.as_str())?); + let version_id = VersionId(parse_base62(new_report.item_id.as_str())?); let result = sqlx::query!( "SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)", @@ -236,11 +221,8 @@ pub async fn reports( .await? }; - let query_reports = crate::database::models::report_item::Report::get_many( - &report_ids, - &**pool, - ) - .await?; + let query_reports = + crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?; 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 id = info.into_inner().0.into(); - let report = - crate::database::models::report_item::Report::get(id, &**pool).await?; + let report = crate::database::models::report_item::Report::get(id, &**pool).await?; if let Some(report) = report { 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 id = info.into_inner().0.into(); - let report = - crate::database::models::report_item::Report::get(id, &**pool).await?; + let report = crate::database::models::report_item::Report::get(id, &**pool).await?; if let Some(report) = report { if !user.role.is_mod() && report.user_id != Some(user.id.into()) { @@ -380,9 +360,7 @@ pub async fn report_delete( } } -fn to_report( - x: crate::database::models::report_item::QueryReport, -) -> Result { +fn to_report(x: crate::database::models::report_item::QueryReport) -> Result { let mut item_id = "".to_string(); let mut item_type = ItemType::Unknown; diff --git a/src/routes/v2/statistics.rs b/src/routes/v2/statistics.rs index 58e068a71..a5220a8e8 100644 --- a/src/routes/v2/statistics.rs +++ b/src/routes/v2/statistics.rs @@ -8,9 +8,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { } #[get("statistics")] -pub async fn get_stats( - pool: web::Data, -) -> Result { +pub async fn get_stats(pool: web::Data) -> Result { let projects = sqlx::query!( " SELECT COUNT(id) diff --git a/src/routes/v2/tags.rs b/src/routes/v2/tags.rs index bee3aefaf..1b0fe2bbf 100644 --- a/src/routes/v2/tags.rs +++ b/src/routes/v2/tags.rs @@ -1,8 +1,6 @@ use super::ApiError; use crate::database::models; -use crate::database::models::categories::{ - DonationPlatform, ProjectType, ReportType, SideType, -}; +use crate::database::models::categories::{DonationPlatform, ProjectType, ReportType, SideType}; use actix_web::{get, web, HttpResponse}; use chrono::{DateTime, Utc}; use models::categories::{Category, GameVersion, Loader}; @@ -34,9 +32,7 @@ pub struct CategoryData { // TODO: searching / filtering? Could be used to implement a live // searching category list #[get("category")] -pub async fn category_list( - pool: web::Data, -) -> Result { +pub async fn category_list(pool: web::Data) -> Result { let results = Category::list(&**pool) .await? .into_iter() @@ -59,9 +55,7 @@ pub struct LoaderData { } #[get("loader")] -pub async fn loader_list( - pool: web::Data, -) -> Result { +pub async fn loader_list(pool: web::Data) -> Result { let mut results = Loader::list(&**pool) .await? .into_iter() @@ -97,11 +91,8 @@ pub async fn game_version_list( pool: web::Data, query: web::Query, ) -> Result { - let results: Vec = if query.type_.is_some() - || query.major.is_some() - { - GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool) - .await? + let results: Vec = if query.type_.is_some() || query.major.is_some() { + GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool).await? } else { GameVersion::list(&**pool).await? } @@ -145,9 +136,7 @@ pub struct LicenseText { } #[get("license/{id}")] -pub async fn license_text( - params: web::Path<(String,)>, -) -> Result { +pub async fn license_text(params: web::Path<(String,)>) -> Result { let license_id = params.into_inner().0; if license_id == *crate::models::projects::DEFAULT_LICENSE_ID { @@ -176,41 +165,32 @@ pub struct DonationPlatformQueryData { } #[get("donation_platform")] -pub async fn donation_platform_list( - pool: web::Data, -) -> Result { - let results: Vec = - DonationPlatform::list(&**pool) - .await? - .into_iter() - .map(|x| DonationPlatformQueryData { - short: x.short, - name: x.name, - }) - .collect(); +pub async fn donation_platform_list(pool: web::Data) -> Result { + let results: Vec = DonationPlatform::list(&**pool) + .await? + .into_iter() + .map(|x| DonationPlatformQueryData { + short: x.short, + name: x.name, + }) + .collect(); Ok(HttpResponse::Ok().json(results)) } #[get("report_type")] -pub async fn report_type_list( - pool: web::Data, -) -> Result { +pub async fn report_type_list(pool: web::Data) -> Result { let results = ReportType::list(&**pool).await?; Ok(HttpResponse::Ok().json(results)) } #[get("project_type")] -pub async fn project_type_list( - pool: web::Data, -) -> Result { +pub async fn project_type_list(pool: web::Data) -> Result { let results = ProjectType::list(&**pool).await?; Ok(HttpResponse::Ok().json(results)) } #[get("side_type")] -pub async fn side_type_list( - pool: web::Data, -) -> Result { +pub async fn side_type_list(pool: web::Data) -> Result { let results = SideType::list(&**pool).await?; Ok(HttpResponse::Ok().json(results)) } diff --git a/src/routes/v2/teams.rs b/src/routes/v2/teams.rs index 153df5782..59971386d 100644 --- a/src/routes/v2/teams.rs +++ b/src/routes/v2/teams.rs @@ -33,33 +33,23 @@ pub async fn team_members_get_project( ) -> Result { let string = info.into_inner().0; let project_data = - crate::database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + crate::database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?; if let Some(project) = project_data { - let members_data = - TeamMember::get_from_team_full(project.team_id, &**pool).await?; + let members_data = TeamMember::get_from_team_full(project.team_id, &**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) = current_user { - let team_member = TeamMember::get_from_user_id( - project.team_id, - user.id.into(), - &**pool, - ) - .await - .map_err(ApiError::Database)?; + let team_member = + TeamMember::get_from_user_id(project.team_id, user.id.into(), &**pool) + .await + .map_err(ApiError::Database)?; if team_member.is_some() { let team_members: Vec<_> = members_data .into_iter() - .map(|data| { - crate::models::teams::TeamMember::from(data, false) - }) + .map(|data| crate::models::teams::TeamMember::from(data, false)) .collect(); return Ok(HttpResponse::Ok().json(team_members)); @@ -85,16 +75,14 @@ pub async fn team_members_get( pool: web::Data, ) -> Result { let id = info.into_inner().0; - let members_data = - TeamMember::get_from_team_full(id.into(), &**pool).await?; + let members_data = TeamMember::get_from_team_full(id.into(), &**pool).await?; let current_user = get_user_from_headers(req.headers(), &**pool).await.ok(); if let Some(user) = ¤t_user { - let team_member = - TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool) - .await - .map_err(ApiError::Database)?; + let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool) + .await + .map_err(ApiError::Database)?; if team_member.is_some() { let team_members: Vec<_> = members_data @@ -139,8 +127,7 @@ pub async fn teams_get( .map(|x| x.into()) .collect::>(); - let teams_data = - TeamMember::get_from_team_full_many(&team_ids, &**pool).await?; + let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool).await?; let current_user = get_user_from_headers(req.headers(), &**pool).await.ok(); let accepted = if let Some(user) = current_user { @@ -159,9 +146,8 @@ pub async fn teams_get( for (id, member_data) in &teams_groups { if accepted.contains(&id) { - let team_members = member_data.map(|data| { - crate::models::teams::TeamMember::from(data, false) - }); + let team_members = + member_data.map(|data| crate::models::teams::TeamMember::from(data, false)); teams.push(team_members.collect()); @@ -187,12 +173,8 @@ pub async fn join_team( let team_id = info.into_inner().0.into(); let current_user = get_user_from_headers(req.headers(), &**pool).await?; - let member = TeamMember::get_from_user_id_pending( - team_id, - current_user.id.into(), - &**pool, - ) - .await?; + let member = + TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?; if let Some(member) = member { if member.accepted { @@ -258,20 +240,17 @@ pub async fn add_team_member( let mut transaction = pool.begin().await?; let current_user = get_user_from_headers(req.headers(), &**pool).await?; - let member = - TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool) - .await? - .ok_or_else(|| { - ApiError::CustomAuthentication( - "You don't have permission to edit members of this team" - .to_string(), - ) - })?; + let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool) + .await? + .ok_or_else(|| { + ApiError::CustomAuthentication( + "You don't have permission to edit members of this team".to_string(), + ) + })?; if !member.permissions.contains(Permissions::MANAGE_INVITES) { return Err(ApiError::CustomAuthentication( - "You don't have permission to invite users to this team" - .to_string(), + "You don't have permission to invite users to this team".to_string(), )); } if !member.permissions.contains(new_member.permissions) { @@ -286,9 +265,7 @@ pub async fn add_team_member( )); } - if new_member.payouts_split < Decimal::ZERO - || new_member.payouts_split > Decimal::from(5000) - { + if new_member.payouts_split < Decimal::ZERO || new_member.payouts_split > Decimal::from(5000) { return Err(ApiError::InvalidInput( "Payouts split must be between 0 and 5000!".to_string(), )); @@ -308,21 +285,16 @@ pub async fn add_team_member( )); } else { return Err(ApiError::InvalidInput( - "There is already a pending member request for this user" - .to_string(), + "There is already a pending member request for this user".to_string(), )); } } crate::database::models::User::get(member.user_id, &**pool) .await? - .ok_or_else(|| { - ApiError::InvalidInput("An invalid User ID specified".to_string()) - })?; + .ok_or_else(|| ApiError::InvalidInput("An invalid User ID specified".to_string()))?; - let new_id = - crate::database::models::ids::generate_team_member_id(&mut transaction) - .await?; + let new_id = crate::database::models::ids::generate_team_member_id(&mut transaction).await?; TeamMember { id: new_id, team_id, @@ -383,24 +355,20 @@ pub async fn edit_team_member( let user_id = ids.1.into(); let current_user = get_user_from_headers(req.headers(), &**pool).await?; - let member = - TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) - .await? - .ok_or_else(|| { - ApiError::CustomAuthentication( - "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? - .ok_or_else(|| { - ApiError::CustomAuthentication( - "You don't have permission to edit members of this team" - .to_string(), - ) - })?; + let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) + .await? + .ok_or_else(|| { + ApiError::CustomAuthentication( + "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? + .ok_or_else(|| { + ApiError::CustomAuthentication( + "You don't have permission to edit members of this team".to_string(), + ) + })?; 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()) { return Err(ApiError::InvalidInput( - "The owner's permission and role of a team cannot be edited" - .to_string(), + "The owner's permission and role of a team cannot be edited".to_string(), )); } if !member.permissions.contains(Permissions::EDIT_MEMBER) { return Err(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 let Some(new_permissions) = edit_member.permissions { if !member.permissions.contains(new_permissions) { return Err(ApiError::InvalidInput( - "The new permissions have permissions that you don't have" - .to_string(), + "The new permissions have permissions that you don't have".to_string(), )); } } 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( "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?; if !current_user.role.is_admin() { - let member = TeamMember::get_from_user_id( - id.into(), - current_user.id.into(), - &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::CustomAuthentication( - "You don't have permission to edit members of this team" - .to_string(), - ) - })?; + let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool) + .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 { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit the ownership of this team" - .to_string(), + "You don't have permission to edit the ownership of this team".to_string(), )); } } - let new_member = TeamMember::get_from_user_id( - id.into(), - new_owner.user_id.into(), - &**pool, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "The new owner specified does not exist".to_string(), - ) - })?; + let new_member = TeamMember::get_from_user_id(id.into(), new_owner.user_id.into(), &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInput("The new owner specified does not exist".to_string()) + })?; if !new_member.accepted { return Err(ApiError::InvalidInput( @@ -559,18 +511,15 @@ pub async fn remove_team_member( let user_id = ids.1.into(); let current_user = get_user_from_headers(req.headers(), &**pool).await?; - let member = - TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) - .await? - .ok_or_else(|| { - ApiError::CustomAuthentication( - "You don't have permission to edit members of this team" - .to_string(), - ) - })?; + let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) + .await? + .ok_or_else(|| { + ApiError::CustomAuthentication( + "You don't have permission to edit members of this team".to_string(), + ) + })?; - let delete_member = - TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?; + let delete_member = TeamMember::get_from_user_id_pending(id, user_id, &**pool).await?; if let Some(delete_member) = delete_member { 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 // removed by a member with the REMOVE_MEMBER permission. if delete_member.user_id == member.user_id - || (member.permissions.contains(Permissions::REMOVE_MEMBER) - && member.accepted) + || (member.permissions.contains(Permissions::REMOVE_MEMBER) && member.accepted) { TeamMember::delete(id, user_id, &mut transaction).await?; } else { @@ -596,8 +544,7 @@ pub async fn remove_team_member( )); } } else if delete_member.user_id == member.user_id - || (member.permissions.contains(Permissions::MANAGE_INVITES) - && member.accepted) + || (member.permissions.contains(Permissions::MANAGE_INVITES) && member.accepted) { // This is a pending invite rather than a member, so the // 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?; } else { return Err(ApiError::CustomAuthentication( - "You do not have permission to cancel a team invite" - .to_string(), + "You do not have permission to cancel a team invite".to_string(), )); } diff --git a/src/routes/v2/threads.rs b/src/routes/v2/threads.rs index b5c6be51b..e77c31a6a 100644 --- a/src/routes/v2/threads.rs +++ b/src/routes/v2/threads.rs @@ -4,14 +4,10 @@ use crate::database::models::thread_item::ThreadMessageBuilder; use crate::models::ids::{ReportId, ThreadMessageId}; use crate::models::notifications::NotificationBody; use crate::models::projects::{ProjectId, ProjectStatus}; -use crate::models::threads::{ - MessageBody, Thread, ThreadId, ThreadMessage, ThreadType, -}; +use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadMessage, ThreadType}; use crate::models::users::User; use crate::routes::ApiError; -use crate::util::auth::{ - check_is_moderator_from_headers, get_user_from_headers, -}; +use crate::util::auth::{check_is_moderator_from_headers, get_user_from_headers}; use actix_web::{delete, get, post, web, HttpRequest, HttpResponse}; use futures::TryStreamExt; use serde::Deserialize; @@ -42,13 +38,13 @@ pub async fn is_authorized_thread( Ok(match thread.type_ { ThreadType::Report => { let report_exists = sqlx::query!( - "SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)", - thread.id as database::models::ids::ThreadId, - user_id as database::models::ids::UserId, - ) - .fetch_one(pool) - .await? - .exists; + "SELECT EXISTS(SELECT 1 FROM reports WHERE thread_id = $1 AND reporter = $2)", + thread.id as database::models::ids::ThreadId, + user_id as database::models::ids::UserId, + ) + .fetch_one(pool) + .await? + .exists; report_exists.unwrap_or(false) } @@ -80,8 +76,7 @@ pub async fn filter_authorized_threads( for thread in threads { if user.role.is_mod() - || (thread.type_ == ThreadType::DirectMessage - && thread.members.contains(&user_id)) + || (thread.type_ == ThreadType::DirectMessage && thread.members.contains(&user_id)) { return_threads.push(thread); } else { @@ -106,23 +101,23 @@ pub async fn filter_authorized_threads( &*project_thread_ids, user_id as database::models::ids::UserId, ) - .fetch_many(&***pool) - .try_for_each(|e| { - if let Some(row) = e.right() { - check_threads.retain(|x| { - let bool = Some(x.id.0) == row.thread_id; + .fetch_many(&***pool) + .try_for_each(|e| { + if let Some(row) = e.right() { + check_threads.retain(|x| { + let bool = Some(x.id.0) == row.thread_id; - if bool { - return_threads.push(x.clone()); - } + if bool { + return_threads.push(x.clone()); + } - !bool - }); - } + !bool + }); + } - futures::future::ready(Ok(())) - }) - .await?; + futures::future::ready(Ok(())) + }) + .await?; } let report_thread_ids = check_threads @@ -176,12 +171,11 @@ pub async fn filter_authorized_threads( .collect::>(), ); - let users: Vec = - database::models::User::get_many(&user_ids, &***pool) - .await? - .into_iter() - .map(From::from) - .collect(); + let users: Vec = database::models::User::get_many(&user_ids, &***pool) + .await? + .into_iter() + .map(From::from) + .collect(); let mut final_threads = Vec::new(); @@ -210,11 +204,7 @@ pub async fn filter_authorized_threads( Ok(final_threads) } -fn convert_thread( - data: database::models::Thread, - users: Vec, - user: &User, -) -> Thread { +fn convert_thread(data: database::models::Thread, users: Vec, user: &User) -> Thread { let thread_type = data.type_; Thread { @@ -279,16 +269,13 @@ pub async fn thread_get( .collect::>(), ); - let users: Vec = - database::models::User::get_many(authors, &**pool) - .await? - .into_iter() - .map(From::from) - .collect(); + let users: Vec = database::models::User::get_many(authors, &**pool) + .await? + .into_iter() + .map(From::from) + .collect(); - return Ok( - HttpResponse::Ok().json(convert_thread(data, users, &user)) - ); + return Ok(HttpResponse::Ok().json(convert_thread(data, users, &user))); } } Ok(HttpResponse::NotFound().body("")) @@ -313,8 +300,7 @@ pub async fn threads_get( .map(|x| x.into()) .collect(); - let threads_data = - database::models::Thread::get_many(&thread_ids, &**pool).await?; + let threads_data = database::models::Thread::get_many(&thread_ids, &**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 { - let thread_message = database::models::ThreadMessage::get( - (*replying_to).into(), - &**pool, - ) - .await?; + let thread_message = + database::models::ThreadMessage::get((*replying_to).into(), &**pool).await?; if let Some(thread_message) = thread_message { if thread_message.thread_id != string { return Err(ApiError::InvalidInput( - "Message replied to is from another thread!" - .to_string(), + "Message replied to is from another thread!".to_string(), )); } } else { @@ -394,17 +376,13 @@ pub async fn thread_send_message( None }; - if report.as_ref().map(|x| x.closed).unwrap_or(false) - && !user.role.is_mod() - { + if report.as_ref().map(|x| x.closed).unwrap_or(false) && !user.role.is_mod() { return Err(ApiError::InvalidInput( "You may not reply to a closed report".to_string(), )); } - let (mod_notif, (user_notif, team_id)) = if thread.type_ - == ThreadType::Project - { + let (mod_notif, (user_notif, team_id)) = if thread.type_ == ThreadType::Project { let record = sqlx::query!( "SELECT m.status, m.team_id FROM mods m WHERE thread_id = $1", thread.id as database::models::ids::ThreadId, @@ -422,7 +400,17 @@ pub async fn thread_send_message( ), ) } 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?; @@ -498,14 +486,11 @@ pub async fn moderation_inbox( " ) .fetch_many(&**pool) - .try_filter_map(|e| async { - Ok(e.right().map(|m| database::models::ThreadId(m.id))) - }) + .try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ThreadId(m.id))) }) .try_collect::>() .await?; - let threads_data = - database::models::Thread::get_many(&ids, &**pool).await?; + let threads_data = database::models::Thread::get_many(&ids, &**pool).await?; let threads = filter_authorized_threads(threads_data, &user, &pool).await?; Ok(HttpResponse::Ok().json(threads)) @@ -546,11 +531,7 @@ pub async fn message_delete( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let result = database::models::ThreadMessage::get( - info.into_inner().0.into(), - &**pool, - ) - .await?; + let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?; if let Some(thread) = result { 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?; - database::models::ThreadMessage::remove_full( - thread.id, - &mut transaction, - ) - .await?; + database::models::ThreadMessage::remove_full(thread.id, &mut transaction).await?; transaction.commit().await?; Ok(HttpResponse::NoContent().body("")) diff --git a/src/routes/v2/users.rs b/src/routes/v2/users.rs index a28e88190..11ffe12a3 100644 --- a/src/routes/v2/users.rs +++ b/src/routes/v2/users.rs @@ -2,9 +2,7 @@ use crate::database::models::User; use crate::file_hosting::FileHost; use crate::models::notifications::Notification; use crate::models::projects::Project; -use crate::models::users::{ - Badges, RecipientType, RecipientWallet, Role, UserId, -}; +use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId}; use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue}; use crate::routes::ApiError; use crate::util::auth::get_user_from_headers; @@ -24,6 +22,7 @@ use validator::Validate; pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(user_auth_get); + cfg.service(user_data_get); cfg.service(users_get); cfg.service( @@ -45,8 +44,44 @@ pub async fn user_auth_get( req: HttpRequest, pool: web::Data, ) -> Result { - Ok(HttpResponse::Ok() - .json(get_user_from_headers(req.headers(), &**pool).await?)) + Ok(HttpResponse::Ok().json(get_user_from_headers(req.headers(), &**pool).await?)) +} + +#[derive(Serialize)] +pub struct UserData { + pub notifs_count: u64, + pub followed_projects: Vec, +} + +#[get("user_data")] +pub async fn user_data_get( + req: HttpRequest, + pool: web::Data, +) -> Result { + 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)] @@ -66,8 +101,7 @@ pub async fn users_get( let users_data = User::get_many(&user_ids, &**pool).await?; - let users: Vec = - users_data.into_iter().map(From::from).collect(); + let users: Vec = users_data.into_iter().map(From::from).collect(); Ok(HttpResponse::Ok().json(users)) } @@ -78,8 +112,7 @@ pub async fn user_get( pool: web::Data, ) -> Result { let string = info.into_inner().0; - let id_option: Option = - serde_json::from_str(&format!("\"{string}\"")).ok(); + let id_option: Option = serde_json::from_str(&format!("\"{string}\"")).ok(); let mut user_data; @@ -109,8 +142,7 @@ pub async fn projects_list( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await.ok(); - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { 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 response: Vec<_> = - crate::database::Project::get_many_full(&project_data, &**pool) - .await? - .into_iter() - .filter(|x| can_view_private || x.inner.status.is_searchable()) - .map(Project::from) - .collect(); + let response: Vec<_> = crate::database::Project::get_many_full(&project_data, &**pool) + .await? + .into_iter() + .filter(|x| can_view_private || x.inner.status.is_searchable()) + .map(Project::from) + .collect(); Ok(HttpResponse::Ok().json(response)) } else { @@ -192,12 +223,11 @@ pub async fn user_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - new_user.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + new_user + .validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { 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) = payout_data { - if payout_data.payout_wallet_type - == RecipientType::UserHandle + if payout_data.payout_wallet_type == RecipientType::UserHandle && payout_data.payout_wallet == RecipientWallet::Paypal { return Err(ApiError::InvalidInput( - "You cannot use a paypal wallet with a user handle!" - .to_string(), + "You cannot use a paypal wallet with a user handle!".to_string(), )); } if !match payout_data.payout_wallet_type { - RecipientType::Email => validator::validate_email( - &payout_data.payout_address, - ), - RecipientType::Phone => validator::validate_phone( - &payout_data.payout_address, - ), + RecipientType::Email => { + validator::validate_email(&payout_data.payout_address) + } + RecipientType::Phone => { + validator::validate_phone(&payout_data.payout_address) + } RecipientType::UserHandle => true, } { return Err(ApiError::InvalidInput( @@ -350,8 +378,8 @@ pub async fn user_edit( ", id as crate::database::models::ids::UserId, ) - .fetch_one(&mut *transaction) - .await?; + .fetch_one(&mut *transaction) + .await?; if results.exists.unwrap_or(false) { return Err(ApiError::InvalidInput( @@ -371,8 +399,8 @@ pub async fn user_edit( payout_data.payout_address, id as crate::database::models::ids::UserId, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } else { sqlx::query!( " @@ -382,8 +410,8 @@ pub async fn user_edit( ", id as crate::database::models::ids::UserId, ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; } } @@ -413,20 +441,15 @@ pub async fn user_icon_edit( file_host: web::Data>, mut payload: web::Payload, ) -> Result { - if let Some(content_type) = - crate::util::ext::get_image_content_type(&ext.ext) - { + if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) { let cdn_url = dotenvy::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool) - .await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if user.id != id.into() && !user.role.is_mod() { return Err(ApiError::CustomAuthentication( - "You don't have permission to edit this user's icon." - .to_string(), + "You don't have permission to edit this user's icon.".to_string(), )); } @@ -452,12 +475,8 @@ pub async fn user_icon_edit( } } - let bytes = read_from_payload( - &mut payload, - 2097152, - "Icons must be smaller than 2MiB", - ) - .await?; + let bytes = + read_from_payload(&mut payload, 2097152, "Icons must be smaller than 2MiB").await?; let hash = sha1::Sha1::from(&bytes).hexdigest(); let upload_data = file_host @@ -509,8 +528,7 @@ pub async fn user_delete( removal_type: web::Query, ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if !user.role.is_admin() && user.id != id.into() { @@ -546,11 +564,7 @@ pub async fn user_follows( pool: web::Data, ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = crate::database::models::User::get_id_from_username_or_id( - &info.into_inner().0, - &**pool, - ) - .await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if !user.role.is_admin() && user.id != id.into() { @@ -576,12 +590,11 @@ pub async fn user_follows( .try_collect::>() .await?; - let projects: Vec<_> = - crate::database::Project::get_many_full(&project_ids, &**pool) - .await? - .into_iter() - .map(Project::from) - .collect(); + let projects: Vec<_> = crate::database::Project::get_many_full(&project_ids, &**pool) + .await? + .into_iter() + .map(Project::from) + .collect(); Ok(HttpResponse::Ok().json(projects)) } else { @@ -596,11 +609,7 @@ pub async fn user_notifications( pool: web::Data, ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = crate::database::models::User::get_id_from_username_or_id( - &info.into_inner().0, - &**pool, - ) - .await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if !user.role.is_admin() && user.id != id.into() { @@ -638,14 +647,12 @@ pub async fn user_payouts( pool: web::Data, ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if !user.role.is_admin() && user.id != id.into() { return Err(ApiError::CustomAuthentication( - "You do not have permission to see the payouts of this user!" - .to_string(), + "You do not have permission to see the payouts of this user!".to_string(), )); } @@ -717,22 +724,18 @@ pub async fn user_payouts_request( let mut payouts_queue = payouts_queue.lock().await; let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = - User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; + let id_option = User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { if !user.role.is_admin() && user.id != id.into() { return Err(ApiError::CustomAuthentication( - "You do not have permission to request payouts of this user!" - .to_string(), + "You do not have permission to request payouts of this user!".to_string(), )); } if let Some(payouts_data) = user.payout_data { if let Some(payout_address) = payouts_data.payout_address { - if let Some(payout_wallet_type) = - payouts_data.payout_wallet_type - { + if let Some(payout_wallet_type) = payouts_data.payout_wallet_type { if let Some(payout_wallet) = payouts_data.payout_wallet { return if data.amount < payouts_data.balance { let mut transaction = pool.begin().await?; @@ -744,10 +747,15 @@ pub async fn user_payouts_request( value: data.amount, }, 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_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?; @@ -760,8 +768,8 @@ pub async fn user_payouts_request( data.amount, "success" ) - .execute(&mut *transaction) - .await?; + .execute(&mut *transaction) + .await?; sqlx::query!( " @@ -780,8 +788,7 @@ pub async fn user_payouts_request( Ok(HttpResponse::NoContent().body("")) } else { Err(ApiError::InvalidInput( - "You do not have enough funds to make this payout!" - .to_string(), + "You do not have enough funds to make this payout!".to_string(), )) }; } diff --git a/src/routes/v2/version_creation.rs b/src/routes/v2/version_creation.rs index 9c2096878..7ef338df3 100644 --- a/src/routes/v2/version_creation.rs +++ b/src/routes/v2/version_creation.rs @@ -8,8 +8,8 @@ use crate::file_hosting::FileHost; use crate::models::notifications::NotificationBody; use crate::models::pack::PackFileHash; use crate::models::projects::{ - Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, - Version, VersionFile, VersionId, VersionStatus, VersionType, + Dependency, DependencyType, FileType, GameVersion, Loader, ProjectId, Version, VersionFile, + VersionId, VersionStatus, VersionType, }; use crate::models::teams::Permissions; use crate::util::auth::get_user_from_headers; @@ -97,11 +97,8 @@ pub async fn version_create( .await; if result.is_err() { - let undo_result = super::project_creation::undo_uploads( - &***file_host, - &uploaded_files, - ) - .await; + let undo_result = + super::project_creation::undo_uploads(&***file_host, &uploaded_files).await; let rollback_result = transaction.rollback().await; undo_result?; @@ -127,10 +124,8 @@ async fn version_create_inner( let mut initial_version_data = None; let mut version_builder = None; - let all_game_versions = - models::categories::GameVersion::list(&mut *transaction).await?; - let all_loaders = - models::categories::Loader::list(&mut *transaction).await?; + let all_game_versions = models::categories::GameVersion::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?; @@ -145,9 +140,7 @@ async fn version_create_inner( let result = async { let content_disposition = field.content_disposition().clone(); let name = content_disposition.get_name().ok_or_else(|| { - CreateError::MissingValueError( - "Missing content name".to_string(), - ) + CreateError::MissingValueError("Missing content name".to_string()) })?; if name == "data" { @@ -156,11 +149,9 @@ async fn version_create_inner( data.extend_from_slice(&chunk?); } - let version_create_data: InitialVersionData = - serde_json::from_slice(&data)?; + let version_create_data: InitialVersionData = serde_json::from_slice(&data)?; initial_version_data = Some(version_create_data); - let version_create_data = - initial_version_data.as_ref().unwrap(); + let version_create_data = initial_version_data.as_ref().unwrap(); if version_create_data.project_id.is_none() { return Err(CreateError::MissingValueError( "Missing project id".to_string(), @@ -168,9 +159,7 @@ async fn version_create_inner( } version_create_data.validate().map_err(|err| { - CreateError::ValidationError(validation_errors_to_string( - err, None, - )) + CreateError::ValidationError(validation_errors_to_string(err, None)) })?; if !version_create_data.status.can_be_requested() { @@ -179,8 +168,7 @@ async fn version_create_inner( )); } - let project_id: models::ProjectId = - version_create_data.project_id.unwrap().into(); + let project_id: models::ProjectId = version_create_data.project_id.unwrap().into(); // Ensure that the project this version is being added to exists let results = sqlx::query!( @@ -206,8 +194,7 @@ async fn version_create_inner( .await? .ok_or_else(|| { CreateError::CustomAuthenticationError( - "You don't have permission to upload this version!" - .to_string(), + "You don't have permission to upload this version!".to_string(), ) })?; @@ -216,13 +203,11 @@ async fn version_create_inner( .contains(Permissions::UPLOAD_VERSION) { return Err(CreateError::CustomAuthenticationError( - "You don't have permission to upload this version!" - .to_string(), + "You don't have permission to upload this version!".to_string(), )); } - let version_id: VersionId = - models::generate_version_id(transaction).await?.into(); + let version_id: VersionId = models::generate_version_id(transaction).await?.into(); let project_type = sqlx::query!( " @@ -243,13 +228,10 @@ async fn version_create_inner( all_game_versions .iter() .find(|y| y.version == x.0) - .ok_or_else(|| { - CreateError::InvalidGameVersion(x.0.clone()) - }) + .ok_or_else(|| CreateError::InvalidGameVersion(x.0.clone())) .map(|y| y.id) }) - .collect::, CreateError>>( - )?; + .collect::, CreateError>>()?; let loaders = version_create_data .loaders @@ -258,13 +240,9 @@ async fn version_create_inner( all_loaders .iter() .find(|y| { - y.loader == x.0 - && y.supported_project_types - .contains(&project_type) - }) - .ok_or_else(|| { - CreateError::InvalidLoader(x.0.clone()) + y.loader == x.0 && y.supported_project_types.contains(&project_type) }) + .ok_or_else(|| CreateError::InvalidLoader(x.0.clone())) .map(|y| y.id) }) .collect::, CreateError>>()?; @@ -286,17 +264,12 @@ async fn version_create_inner( author_id: user.id.into(), name: version_create_data.version_title.clone(), version_number: version_create_data.version_number.clone(), - changelog: version_create_data - .version_body - .clone() - .unwrap_or_default(), + changelog: version_create_data.version_body.clone().unwrap_or_default(), files: Vec::new(), dependencies, game_versions, loaders, - version_type: version_create_data - .release_channel - .to_string(), + version_type: version_create_data.release_channel.to_string(), featured: version_create_data.featured, status: version_create_data.status, requested_status: None, @@ -306,9 +279,7 @@ async fn version_create_inner( } let version = version_builder.as_mut().ok_or_else(|| { - CreateError::InvalidInput(String::from( - "`data` field must come before file fields", - )) + CreateError::InvalidInput(String::from("`data` field must come before file fields")) })?; let project_type = sqlx::query!( @@ -323,12 +294,9 @@ async fn version_create_inner( .await? .name; - let version_data = - initial_version_data.clone().ok_or_else(|| { - CreateError::InvalidInput( - "`data` field is required".to_string(), - ) - })?; + let version_data = initial_version_data + .clone() + .ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?; upload_file( &mut field, @@ -365,12 +333,10 @@ async fn version_create_inner( return Err(error); } - let version_data = initial_version_data.ok_or_else(|| { - CreateError::InvalidInput("`data` field is required".to_string()) - })?; - let builder = version_builder.ok_or_else(|| { - CreateError::InvalidInput("`data` field is required".to_string()) - })?; + let version_data = initial_version_data + .ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?; + let builder = version_builder + .ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?; if builder.files.is_empty() { return Err(CreateError::InvalidInput( @@ -388,9 +354,7 @@ async fn version_create_inner( builder.project_id as crate::database::models::ids::ProjectId ) .fetch_many(&mut *transaction) - .try_filter_map(|e| async { - Ok(e.right().map(|m| models::ids::UserId(m.follower_id))) - }) + .try_filter_map(|e| async { Ok(e.right().map(|m| models::ids::UserId(m.follower_id))) }) .try_collect::>() .await?; @@ -453,8 +417,7 @@ async fn version_create_inner( let project_id = builder.project_id; builder.insert(transaction).await?; - models::Project::update_game_versions(project_id, &mut *transaction) - .await?; + models::Project::update_game_versions(project_id, &mut *transaction).await?; models::Project::update_loaders(project_id, &mut *transaction).await?; Ok(HttpResponse::Ok().json(response)) @@ -486,11 +449,8 @@ pub async fn upload_file_to_version( .await; if result.is_err() { - let undo_result = super::project_creation::undo_uploads( - &***file_host, - &uploaded_files, - ) - .await; + let undo_result = + super::project_creation::undo_uploads(&***file_host, &uploaded_files).await; let rollback_result = transaction.rollback().await; undo_result?; @@ -541,8 +501,7 @@ async fn upload_file_to_version_inner( .await? .ok_or_else(|| { CreateError::CustomAuthenticationError( - "You don't have permission to upload files to this version!" - .to_string(), + "You don't have permission to upload files to this version!".to_string(), ) })?; @@ -551,8 +510,7 @@ async fn upload_file_to_version_inner( .contains(Permissions::UPLOAD_VERSION) { return Err(CreateError::CustomAuthenticationError( - "You don't have permission to upload files to this version!" - .to_string(), + "You don't have permission to upload files to this version!".to_string(), )); } } @@ -571,8 +529,7 @@ async fn upload_file_to_version_inner( .await? .name; - let all_game_versions = - models::categories::GameVersion::list(&mut *transaction).await?; + let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?; let mut error = None; while let Some(item) = payload.next().await { @@ -585,9 +542,7 @@ async fn upload_file_to_version_inner( let result = async { let content_disposition = field.content_disposition().clone(); let name = content_disposition.get_name().ok_or_else(|| { - CreateError::MissingValueError( - "Missing content name".to_string(), - ) + CreateError::MissingValueError("Missing content name".to_string()) })?; 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(|| { - CreateError::InvalidInput(String::from( - "`data` field must come before file fields", - )) + CreateError::InvalidInput(String::from("`data` field must come before file fields")) })?; let mut dependencies = version @@ -703,9 +656,7 @@ pub async fn upload_file( } let content_type = crate::util::ext::project_file_type(file_extension) - .ok_or_else(|| { - CreateError::InvalidFileType(file_extension.to_string()) - })?; + .ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?; let data = read_from_field( field, 500 * (1 << 20), @@ -731,8 +682,7 @@ pub async fn upload_file( if exists { return Err(CreateError::InvalidInput( - "Duplicate files are not allowed to be uploaded to Modrinth!" - .to_string(), + "Duplicate files are not allowed to be uploaded to Modrinth!".to_string(), )); } @@ -761,23 +711,20 @@ pub async fn upload_file( .collect(); let res = sqlx::query!( - " + " 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 versions v on f.version_id = v.id WHERE h.algorithm = 'sha1' AND h.hash = ANY($1) ", - &*hashes - ) - .fetch_all(&mut *transaction).await?; + &*hashes + ) + .fetch_all(&mut *transaction) + .await?; for file in &format.files { if let Some(dep) = res.iter().find(|x| { - Some(&*x.hash) - == file - .hashes - .get(&PackFileHash::Sha1) - .map(|x| x.as_bytes()) + Some(&*x.hash) == file.hashes.get(&PackFileHash::Sha1).map(|x| x.as_bytes()) }) { dependencies.push(DependencyBuilder { project_id: Some(models::ProjectId(dep.project_id)), @@ -828,8 +775,7 @@ pub async fn upload_file( version_id, urlencoding::encode(file_name) ); - let file_path = - format!("data/{}/versions/{}/{}", project_id, version_id, &file_name); + let file_path = format!("data/{}/versions/{}/{}", project_id, version_id, &file_name); let upload_data = file_host .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) }) { return Err(CreateError::InvalidInput( - "Duplicate files are not allowed to be uploaded to Modrinth!" - .to_string(), + "Duplicate files are not allowed to be uploaded to Modrinth!".to_string(), )); } @@ -888,9 +833,9 @@ pub async fn upload_file( pub fn get_name_ext( content_disposition: &actix_web::http::header::ContentDisposition, ) -> Result<(&str, &str), CreateError> { - let file_name = content_disposition.get_filename().ok_or_else(|| { - CreateError::MissingValueError("Missing content file name".to_string()) - })?; + let file_name = content_disposition + .get_filename() + .ok_or_else(|| CreateError::MissingValueError("Missing content file name".to_string()))?; let file_extension = if let Some(last_period) = file_name.rfind('.') { file_name.get((last_period + 1)..).unwrap_or("") } else { diff --git a/src/routes/v2/version_file.rs b/src/routes/v2/version_file.rs index cf8a2c1f0..ad67abd6e 100644 --- a/src/routes/v2/version_file.rs +++ b/src/routes/v2/version_file.rs @@ -84,8 +84,7 @@ pub async fn get_version_from_hash( .iter() .map(|x| database::models::VersionId(x.version_id)) .collect::>(); - 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?; if let Some(first) = versions_data.first() { if hash_query.multiple { @@ -96,8 +95,7 @@ pub async fn get_version_from_hash( .collect::>(), )) } else { - Ok(HttpResponse::Ok() - .json(models::projects::Version::from(first.clone()))) + Ok(HttpResponse::Ok().json(models::projects::Version::from(first.clone()))) } } else { 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) ORDER BY v.date_published ASC ", - &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hash.as_bytes(), hash_query.algorithm, - &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_optional(&mut *transaction) .await?; @@ -178,28 +182,25 @@ pub async fn delete_file( || Some(x.version_id) == hash_query.version_id.map(|x| x.0 as i64) }) { if !user.role.is_admin() { - let team_member = - database::models::TeamMember::get_from_user_id_version( - database::models::ids::VersionId(row.version_id), - user.id.into(), - &**pool, + let team_member = database::models::TeamMember::get_from_user_id_version( + database::models::ids::VersionId(row.version_id), + user.id.into(), + &**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 .permissions .contains(Permissions::DELETE_VERSION) { return Err(ApiError::CustomAuthentication( - "You don't have permission to delete this file!" - .to_string(), + "You don't have permission to delete this file!".to_string(), )); } } @@ -220,8 +221,7 @@ pub async fn delete_file( if files.len() < 2 { return Err(ApiError::InvalidInput( - "Versions must have at least one file uploaded to them" - .to_string(), + "Versions must have at least one file uploaded to them".to_string(), )); } @@ -324,9 +324,7 @@ pub async fn get_update_from_hash( .await?; if let Some(version_id) = version_ids.first() { - let version_data = - database::models::Version::get_full(*version_id, &**pool) - .await?; + let version_data = database::models::Version::get_full(*version_id, &**pool).await?; ok_or_not_found::(version_data) } else { @@ -364,10 +362,16 @@ pub async fn get_versions_from_hashes( 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) ", - &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hashes_parsed.as_slice(), file_data.algorithm, - &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_all(&**pool) .await?; @@ -376,8 +380,7 @@ pub async fn get_versions_from_hashes( .iter() .map(|x| database::models::VersionId(x.version_id)) .collect::>(); - 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 response: Result, ApiError> = result .into_iter() @@ -388,10 +391,7 @@ pub async fn get_versions_from_hashes( .find(|x| x.inner.id.0 == row.version_id) .map(|v| { if let Ok(parsed_hash) = String::from_utf8(row.hash) { - Ok(( - parsed_hash, - crate::models::projects::Version::from(v), - )) + Ok((parsed_hash, crate::models::projects::Version::from(v))) } else { Err(ApiError::Database(DatabaseError::Other(format!( "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 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::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hashes_parsed.as_slice(), file_data.algorithm, - &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) - .fetch_all(&**pool) - .await?; + .fetch_all(&**pool) + .await?; let project_ids = result .iter() .map(|x| database::models::ProjectId(x.project_id)) .collect::>(); - let versions_data = - database::models::Project::get_many_full(&project_ids, &**pool).await?; + let versions_data = database::models::Project::get_many_full(&project_ids, &**pool).await?; let response: Result, ApiError> = result .into_iter() @@ -447,10 +452,7 @@ pub async fn get_projects_from_hashes( .find(|x| x.inner.id.0 == row.project_id) .map(|v| { if let Ok(parsed_hash) = String::from_utf8(row.hash) { - Ok(( - parsed_hash, - crate::models::projects::Project::from(v), - )) + Ok((parsed_hash, crate::models::projects::Project::from(v))) } else { Err(ApiError::Database(DatabaseError::Other(format!( "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 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::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hashes_parsed.as_slice(), update_data.algorithm, - &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) - .fetch_many(&mut *transaction) - .try_filter_map(|e| async { - Ok(e.right().map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id)))) - }) - .try_collect::>() - .await?; + .fetch_many(&mut *transaction) + .try_filter_map(|e| async { + Ok(e.right() + .map(|m| (m.hash, database::models::ids::ProjectId(m.mod_id)))) + }) + .try_collect::>() + .await?; - let mut version_ids: HashMap> = - HashMap::new(); + let mut version_ids: HashMap> = HashMap::new(); let updated_versions = database::models::Version::get_projects_versions( result @@ -583,17 +591,13 @@ pub async fn update_files( .await?; for (hash, id) in result { - if let Some(latest_version) = - updated_versions.get(&id).and_then(|x| x.last()) - { + if let Some(latest_version) = updated_versions.get(&id).and_then(|x| x.last()) { version_ids.insert(*latest_version, hash); } } let query_version_ids = version_ids.keys().copied().collect::>(); - let versions = - database::models::Version::get_many_full(&query_version_ids, &**pool) - .await?; + let versions = database::models::Version::get_many_full(&query_version_ids, &**pool).await?; let mut response = HashMap::new(); @@ -602,10 +606,7 @@ pub async fn update_files( if let Some(hash) = hash { if let Ok(parsed_hash) = String::from_utf8(hash.clone()) { - response.insert( - parsed_hash, - models::projects::Version::from(version), - ); + response.insert(parsed_hash, models::projects::Version::from(version)); } else { let version_id: VersionId = version.inner.id.into(); diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index abc58529c..5492f2eea 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -1,13 +1,10 @@ use super::ApiError; use crate::database; use crate::models; -use crate::models::projects::{ - Dependency, FileType, VersionStatus, VersionType, -}; +use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType}; use crate::models::teams::Permissions; use crate::util::auth::{ - filter_authorized_versions, get_user_from_headers, is_authorized, - is_authorized_version, + filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version, }; use crate::util::validate::validation_errors_to_string; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; @@ -49,10 +46,7 @@ pub async fn version_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get_from_slug_or_project_id( - &string, &**pool, - ) - .await?; + let result = database::models::Project::get_from_slug_or_project_id(&string, &**pool).await?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); @@ -80,9 +74,7 @@ pub async fn version_list( ) .await?; - let mut versions = - database::models::Version::get_many_full(&version_ids, &**pool) - .await?; + let mut versions = database::models::Version::get_many_full(&version_ids, &**pool).await?; let mut response = versions .iter() @@ -95,22 +87,13 @@ pub async fn version_list( .cloned() .collect::>(); - versions.sort_by(|a, b| { - b.inner.date_published.cmp(&a.inner.date_published) - }); + versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); // Attempt to populate versions with "auto featured" versions - if response.is_empty() - && !versions.is_empty() - && filters.featured.unwrap_or(false) - { + if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) { let (loaders, game_versions) = futures::future::try_join( database::models::categories::Loader::list(&**pool), - database::models::categories::GameVersion::list_filter( - None, - Some(true), - &**pool, - ), + database::models::categories::GameVersion::list_filter(None, Some(true), &**pool), ) .await?; @@ -139,13 +122,10 @@ pub async fn version_list( } } - response.sort_by(|a, b| { - b.inner.date_published.cmp(&a.inner.date_published) - }); + response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); response.dedup_by(|a, b| a.inner.id == b.inner.id); - let response = - filter_authorized_versions(response, &user_option, &pool).await?; + let response = filter_authorized_versions(response, &user_option, &pool).await?; Ok(HttpResponse::Ok().json(response)) } else { @@ -162,16 +142,13 @@ pub async fn version_project_get( ) -> Result { let id = info.into_inner(); let version_data = - database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool) - .await?; + database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool).await?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); if let Some(data) = version_data { if is_authorized_version(&data.inner, &user_option, &pool).await? { - return Ok( - HttpResponse::Ok().json(models::projects::Version::from(data)) - ); + return Ok(HttpResponse::Ok().json(models::projects::Version::from(data))); } } @@ -189,18 +166,15 @@ pub async fn versions_get( web::Query(ids): web::Query, pool: web::Data, ) -> Result { - let version_ids = - serde_json::from_str::>(&ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect::>(); - let versions_data = - database::models::Version::get_many_full(&version_ids, &**pool).await?; + let version_ids = serde_json::from_str::>(&ids.ids)? + .into_iter() + .map(|x| x.into()) + .collect::>(); + 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 versions = - filter_authorized_versions(versions_data, &user_option, &pool).await?; + let versions = filter_authorized_versions(versions_data, &user_option, &pool).await?; Ok(HttpResponse::Ok().json(versions)) } @@ -212,16 +186,13 @@ pub async fn version_get( pool: web::Data, ) -> Result { let id = info.into_inner().0; - let version_data = - database::models::Version::get_full(id.into(), &**pool).await?; + let version_data = database::models::Version::get_full(id.into(), &**pool).await?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); if let Some(data) = version_data { if is_authorized_version(&data.inner, &user_option, &pool).await? { - return Ok( - HttpResponse::Ok().json(models::projects::Version::from(data)) - ); + return Ok(HttpResponse::Ok().json(models::projects::Version::from(data))); } } @@ -273,9 +244,9 @@ pub async fn version_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - new_version.validate().map_err(|err| { - ApiError::Validation(validation_errors_to_string(err, None)) - })?; + new_version + .validate() + .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let version_id = info.into_inner().0; let id = version_id.into(); @@ -283,19 +254,15 @@ pub async fn version_edit( let result = database::models::Version::get_full(id, &**pool).await?; if let Some(version_item) = result { - let project_item = database::models::Project::get_full( - version_item.inner.project_id, + let project_item = + 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, ) .await?; - - let team_member = - database::models::TeamMember::get_from_user_id_version( - version_item.inner.id, - user.id.into(), - &**pool, - ) - .await?; let permissions; if user.role.is_admin() { @@ -303,8 +270,7 @@ pub async fn version_edit( } else if let Some(member) = team_member { permissions = Some(member.permissions) } else if user.role.is_mod() { - permissions = - Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY) + permissions = Some(Permissions::EDIT_DETAILS | Permissions::EDIT_BODY) } else { permissions = None } @@ -312,8 +278,7 @@ pub async fn version_edit( if let Some(perms) = permissions { if !perms.contains(Permissions::UPLOAD_VERSION) { return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit this version!" - .to_string(), + "You do not have the permissions to edit this version!".to_string(), )); } @@ -403,18 +368,16 @@ pub async fn version_edit( .await?; for game_version in game_versions { - let game_version_id = - database::models::categories::GameVersion::get_id( - &game_version.0, - &mut *transaction, + let game_version_id = database::models::categories::GameVersion::get_id( + &game_version.0, + &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!( " @@ -447,17 +410,13 @@ pub async fn version_edit( for loader in loaders { let loader_id = - database::models::categories::Loader::get_id( - &loader.0, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "No database entry for loader provided." - .to_string(), - ) - })?; + database::models::categories::Loader::get_id(&loader.0, &mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInput( + "No database entry for loader provided.".to_string(), + ) + })?; sqlx::query!( " @@ -551,8 +510,7 @@ pub async fn version_edit( if let Some(downloads) = &new_version.downloads { if !user.role.is_mod() { return Err(ApiError::CustomAuthentication( - "You don't have permission to set the downloads of this mod" - .to_string(), + "You don't have permission to set the downloads of this mod".to_string(), )); } @@ -577,8 +535,7 @@ pub async fn version_edit( WHERE (id = $2) ", diff as i32, - version_item.inner.project_id - as database::models::ids::ProjectId, + version_item.inner.project_id as database::models::ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -667,8 +624,7 @@ pub async fn version_schedule( if scheduling_data.time < Utc::now() { return Err(ApiError::InvalidInput( - "You cannot schedule a version to be released in the past!" - .to_string(), + "You cannot schedule a version to be released in the past!".to_string(), )); } @@ -679,17 +635,15 @@ pub async fn version_schedule( } let string = info.into_inner().0; - let result = - database::models::Version::get_full(string.into(), &**pool).await?; + let result = database::models::Version::get_full(string.into(), &**pool).await?; if let Some(version_item) = result { - let team_member = - database::models::TeamMember::get_from_user_id_version( - version_item.inner.id, - user.id.into(), - &**pool, - ) - .await?; + let team_member = database::models::TeamMember::get_from_user_id_version( + version_item.inner.id, + user.id.into(), + &**pool, + ) + .await?; if !user.role.is_mod() && !team_member @@ -748,17 +702,14 @@ pub async fn version_delete( .contains(Permissions::DELETE_VERSION) { return Err(ApiError::CustomAuthentication( - "You do not have permission to delete versions in this team" - .to_string(), + "You do not have permission to delete versions in this team".to_string(), )); } } let mut transaction = pool.begin().await?; - let result = - database::models::Version::remove_full(id.into(), &mut transaction) - .await?; + let result = database::models::Version::remove_full(id.into(), &mut transaction).await?; transaction.commit().await?; diff --git a/src/scheduler.rs b/src/scheduler.rs index 57565db4e..30df26240 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -32,13 +32,9 @@ impl Drop for Scheduler { use log::{info, warn}; -pub fn schedule_versions( - scheduler: &mut Scheduler, - pool: sqlx::Pool, -) { - let version_index_interval = std::time::Duration::from_secs( - parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800), - ); +pub fn schedule_versions(scheduler: &mut Scheduler, pool: sqlx::Pool) { + let version_index_interval = + std::time::Duration::from_secs(parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800)); scheduler.run(version_index_interval, move || { let pool_ref = pool.clone(); @@ -82,15 +78,11 @@ struct VersionFormat<'a> { release_time: DateTime, } -async fn update_versions( - pool: &sqlx::Pool, -) -> Result<(), VersionIndexingError> { - let input = reqwest::get( - "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json", - ) - .await? - .json::() - .await?; +async fn update_versions(pool: &sqlx::Pool) -> Result<(), VersionIndexingError> { + let input = reqwest::get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json") + .await? + .json::() + .await?; let mut skipped_versions_count = 0u32; @@ -152,8 +144,7 @@ async fn update_versions( .chars() .all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c)) { - if let Some((_, alternate)) = - HALL_OF_SHAME.iter().find(|(version, _)| name == *version) + if let Some((_, alternate)) = HALL_OF_SHAME.iter().find(|(version, _)| name == *version) { name = String::from(*alternate); } else { diff --git a/src/search/indexing/local_import.rs b/src/search/indexing/local_import.rs index 2db614c97..bb5943f04 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -4,11 +4,10 @@ use log::info; use super::IndexingError; use crate::database::models::ProjectId; use crate::search::UploadSearchProject; +use serde::Deserialize; use sqlx::postgres::PgPool; -pub async fn index_local( - pool: PgPool, -) -> Result, IndexingError> { +pub async fn index_local(pool: PgPool) -> Result, IndexingError> { info!("Indexing local projects!"); 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 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 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 LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.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 lo ON lo.id = lv.loader_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 side_types cs ON m.client_side = cs.id INNER JOIN side_types ss ON m.server_side = ss.id @@ -70,6 +72,21 @@ pub async fn index_local( _ => false, }; + #[derive(Deserialize)] + struct TempDependency { + id: ProjectId, + dep_type: String + } + + let dependencies = serde_json::from_value::>( + 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 { project_id: project_id.to_string(), title: m.title, @@ -95,6 +112,7 @@ pub async fn index_local( open_source, color: m.color.map(|x| x as u32), featured_gallery: m.featured_gallery.unwrap_or_default().first().cloned(), + dependencies, } })) }) diff --git a/src/search/indexing/mod.rs b/src/search/indexing/mod.rs index 1c561d3a8..a4ae2bce8 100644 --- a/src/search/indexing/mod.rs +++ b/src/search/indexing/mod.rs @@ -203,10 +203,10 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[ "gallery", "featured_gallery", "color", + "dependencies", ]; -const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] = - &["title", "description", "author", "slug"]; +const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] = &["title", "description", "author", "slug"]; const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ "categories", @@ -224,6 +224,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ "project_id", "open_source", "color", + "dependencies", ]; const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] = diff --git a/src/search/mod.rs b/src/search/mod.rs index 7080bdbdc..67c702eda 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -99,6 +99,8 @@ pub struct UploadSearchProject { pub modified_timestamp: i64, pub open_source: bool, pub color: Option, + /// format: {project_id}-{dep_type} + pub dependencies: Vec, } #[derive(Serialize, Deserialize, Debug)] @@ -134,6 +136,8 @@ pub struct ResultSearchProject { pub gallery: Vec, pub featured_gallery: Option, pub color: Option, + /// format: {project_id}-{dep_type} + pub dependencies: Vec, } pub async fn search_for_project( @@ -177,13 +181,12 @@ pub async fn search_for_project( None }; - let filters: Cow<_> = - match (info.filters.as_deref(), info.version.as_deref()) { - (Some(f), Some(v)) => format!("({f}) AND ({v})").into(), - (Some(f), None) => f.into(), - (None, Some(v)) => v.into(), - (None, None) => "".into(), - }; + let filters: Cow<_> = match (info.filters.as_deref(), info.version.as_deref()) { + (Some(f), Some(v)) => format!("({f}) AND ({v})").into(), + (Some(f), None) => f.into(), + (None, Some(v)) => v.into(), + (None, None) => "".into(), + }; if let Some(facets) = facets { filter_string.push('('); diff --git a/src/util/auth.rs b/src/util/auth.rs index 5a5a0fcad..c78681312 100644 --- a/src/util/auth.rs +++ b/src/util/auth.rs @@ -59,8 +59,7 @@ where { let github_user = get_github_user_from_token(access_token).await?; - let res = - models::User::get_from_github_id(github_user.id, executor).await?; + let res = models::User::get_from_github_id(github_user.id, executor).await?; match res { Some(result) => Ok(User { @@ -190,8 +189,7 @@ pub async fn filter_authorized_projects( .try_for_each(|e| { if let Some(row) = e.right() { check_projects.retain(|x| { - let bool = x.inner.id.0 == row.id - && x.inner.team_id.0 == row.team_id; + let bool = x.inner.id.0 == row.id && x.inner.team_id.0 == row.team_id; if bool { 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 WHERE m.id = ANY($1) ", - &check_versions.iter().map(|x| x.inner.project_id.0).collect::>(), + &check_versions + .iter() + .map(|x| x.inner.project_id.0) + .collect::>(), user_id as database::models::ids::UserId, ) - .fetch_many(&***pool) - .try_for_each(|e| { - if let Some(row) = e.right() { - check_versions.retain(|x| { - let bool = x.inner.project_id.0 == row.id; + .fetch_many(&***pool) + .try_for_each(|e| { + if let Some(row) = e.right() { + check_versions.retain(|x| { + let bool = x.inner.project_id.0 == row.id; - if bool { - return_versions.push(x.clone().into()); - } + if bool { + return_versions.push(x.clone().into()); + } - !bool - }); - } + !bool + }); + } - futures::future::ready(Ok(())) - }).await?; + futures::future::ready(Ok(())) + }) + .await?; } } diff --git a/src/util/guards.rs b/src/util/guards.rs index bae02a87f..8cc352d6e 100644 --- a/src/util/guards.rs +++ b/src/util/guards.rs @@ -2,9 +2,8 @@ use actix_web::guard::GuardContext; pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin"; pub fn admin_key_guard(ctx: &GuardContext) -> bool { - let admin_key = std::env::var("LABRINTH_ADMIN_KEY").expect( - "No admin key provided, this should have been caught by check_env_vars", - ); + let admin_key = std::env::var("LABRINTH_ADMIN_KEY") + .expect("No admin key provided, this should have been caught by check_env_vars"); ctx.head() .headers() diff --git a/src/util/img.rs b/src/util/img.rs index b7b8b8a42..78af4afee 100644 --- a/src/util/img.rs +++ b/src/util/img.rs @@ -6,15 +6,10 @@ pub fn get_color_from_img(data: &[u8]) -> Result, ImageError> { let image = image::load_from_memory(data)? .resize(256, 256, FilterType::Nearest) .crop_imm(128, 128, 64, 64); - let color = color_thief::get_palette( - image.to_rgb8().as_bytes(), - ColorFormat::Rgb, - 10, - 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)); + let color = color_thief::get_palette(image.to_rgb8().as_bytes(), ColorFormat::Rgb, 10, 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) } diff --git a/src/util/routes.rs b/src/util/routes.rs index 8be27f523..1cda10e04 100644 --- a/src/util/routes.rs +++ b/src/util/routes.rs @@ -18,9 +18,7 @@ pub async fn read_from_payload( return Err(ApiError::InvalidInput(String::from(err_msg))); } else { bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInput( - "Unable to parse bytes in payload sent!".to_string(), - ) + ApiError::InvalidInput("Unable to parse bytes in payload sent!".to_string()) })?); } } @@ -43,9 +41,7 @@ pub async fn read_from_field( Ok(bytes) } -pub(crate) fn ok_or_not_found( - version_data: Option, -) -> Result +pub(crate) fn ok_or_not_found(version_data: Option) -> Result where U: From + Serialize, { diff --git a/src/util/validate.rs b/src/util/validate.rs index 91938185e..474bdf7af 100644 --- a/src/util/validate.rs +++ b/src/util/validate.rs @@ -4,15 +4,11 @@ use regex::Regex; use validator::{ValidationErrors, ValidationErrorsKind}; lazy_static! { - pub static ref RE_URL_SAFE: Regex = - Regex::new(r#"^[a-zA-Z0-9!@$()`.+,_"-]*$"#).unwrap(); + pub static ref RE_URL_SAFE: Regex = 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! -pub fn validation_errors_to_string( - errors: ValidationErrors, - adder: Option, -) -> String { +pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option) -> String { let mut output = String::new(); let map = errors.into_errors(); @@ -23,10 +19,7 @@ pub fn validation_errors_to_string( if let Some(error) = map.get(field) { return match error { ValidationErrorsKind::Struct(errors) => { - validation_errors_to_string( - *errors.clone(), - Some(format!("of item {field}")), - ) + validation_errors_to_string(*errors.clone(), Some(format!("of item {field}"))) } ValidationErrorsKind::List(list) => { if let Some((index, errors)) = list.iter().next() { diff --git a/src/util/webhook.rs b/src/util/webhook.rs index f020e80dc..aab9e9368 100644 --- a/src/util/webhook.rs +++ b/src/util/webhook.rs @@ -180,8 +180,7 @@ pub async fn send_discord_webhook( } if !versions.is_empty() { - let formatted_game_versions: String = - get_gv_range(versions, all_game_versions); + let formatted_game_versions: String = get_gv_range(versions, all_game_versions); fields.push(DiscordEmbedField { name: "Versions", @@ -229,9 +228,7 @@ pub async fn send_discord_webhook( thumbnail: DiscordEmbedThumbnail { url: project.icon_url, }, - image: if let Some(first) = - project.featured_gallery.unwrap_or_default().first() - { + image: if let Some(first) = project.featured_gallery.unwrap_or_default().first() { Some(first.clone()) } else { 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.remove(0).to_uppercase() ), - icon_url: Some( - "https://cdn-raw.modrinth.com/modrinth-new.png".to_string(), - ), + icon_url: Some("https://cdn-raw.modrinth.com/modrinth-new.png".to_string()), }), }; @@ -253,10 +248,7 @@ pub async fn send_discord_webhook( client .post(&webhook_url) .json(&DiscordWebhook { - avatar_url: Some( - "https://cdn.modrinth.com/Modrinth_Dark_Logo.png" - .to_string(), - ), + avatar_url: Some("https://cdn.modrinth.com/Modrinth_Dark_Logo.png".to_string()), username: Some("Modrinth Release".to_string()), embeds: vec![embed], content: message, @@ -264,9 +256,7 @@ pub async fn send_discord_webhook( .send() .await .map_err(|_| { - ApiError::DiscordError( - "Error while sending projects webhook".to_string(), - ) + ApiError::DiscordError("Error while sending projects webhook".to_string()) })?; } @@ -310,21 +300,15 @@ fn get_gv_range( } else { let interval_base = &intervals[current_interval]; - if ((index as i32) - - (interval_base[interval_base.len() - 1][1] as i32) - == 1 - || (release_index as i32) - - (interval_base[interval_base.len() - 1][2] as i32) - == 1) + if ((index as i32) - (interval_base[interval_base.len() - 1][1] as i32) == 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[index].type_ != "release") { if intervals[current_interval].get(1).is_some() { - intervals[current_interval][1] = - vec![i, index, release_index]; + intervals[current_interval][1] = vec![i, index, release_index]; } else { - intervals[current_interval] - .insert(1, vec![i, index, release_index]); + intervals[current_interval].insert(1, vec![i, index, release_index]); } } else { current_interval += 1; @@ -336,10 +320,7 @@ fn get_gv_range( let mut new_intervals = Vec::new(); for interval in intervals { - if interval.len() == 2 - && interval[0][2] != MAX_VALUE - && interval[1][2] == MAX_VALUE - { + if interval.len() == 2 && interval[0][2] != MAX_VALUE && interval[1][2] == MAX_VALUE { let mut last_snapshot: Option = None; for j in ((interval[0][1] + 1)..=interval[1][1]).rev() { @@ -349,16 +330,12 @@ fn get_gv_range( vec![ game_versions .iter() - .position(|x| { - x.version == all_game_versions[j].version - }) + .position(|x| x.version == all_game_versions[j].version) .unwrap_or(MAX_VALUE), j, all_releases .iter() - .position(|x| { - x.version == all_game_versions[j].version - }) + .position(|x| x.version == all_game_versions[j].version) .unwrap_or(MAX_VALUE), ], ]); @@ -370,10 +347,7 @@ fn get_gv_range( game_versions .iter() .position(|x| { - x.version - == all_game_versions - [last_snapshot] - .version + x.version == all_game_versions[last_snapshot].version }) .unwrap_or(MAX_VALUE), last_snapshot, @@ -402,8 +376,7 @@ fn get_gv_range( if interval.len() == 2 { output.push(format!( "{}—{}", - &game_versions[interval[0][0]].version, - &game_versions[interval[1][0]].version + &game_versions[interval[0][0]].version, &game_versions[interval[1][0]].version )) } else { output.push(game_versions[interval[0][0]].version.clone()) diff --git a/src/validate/datapack.rs b/src/validate/datapack.rs index 3e0abd180..623110050 100644 --- a/src/validate/datapack.rs +++ b/src/validate/datapack.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use std::io::Cursor; use zip::ZipArchive; diff --git a/src/validate/fabric.rs b/src/validate/fabric.rs index 37ba779f9..f4d688f1d 100644 --- a/src/validate/fabric.rs +++ b/src/validate/fabric.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; @@ -38,9 +36,10 @@ impl super::Validator for FabricValidator { )); } - if !archive.file_names().any(|name| { - name.ends_with("refmap.json") || name.ends_with(".class") - }) { + if !archive + .file_names() + .any(|name| name.ends_with("refmap.json") || name.ends_with(".class")) + { return Ok(ValidationResult::Warning( "Fabric mod file is a source file!", )); diff --git a/src/validate/forge.rs b/src/validate/forge.rs index 05e8c0abf..9aee22266 100644 --- a/src/validate/forge.rs +++ b/src/validate/forge.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; diff --git a/src/validate/liteloader.rs b/src/validate/liteloader.rs index 4c175d23b..50ec802c1 100644 --- a/src/validate/liteloader.rs +++ b/src/validate/liteloader.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use std::io::Cursor; use zip::ZipArchive; diff --git a/src/validate/mod.rs b/src/validate/mod.rs index 9beaf9694..b0ed33949 100644 --- a/src/validate/mod.rs +++ b/src/validate/mod.rs @@ -8,9 +8,7 @@ use crate::validate::modpack::ModpackValidator; use crate::validate::plugin::*; use crate::validate::quilt::QuiltValidator; use crate::validate::resourcepack::{PackValidator, TexturePackValidator}; -use crate::validate::shader::{ - CanvasShaderValidator, CoreShaderValidator, ShaderValidator, -}; +use crate::validate::shader::{CanvasShaderValidator, CoreShaderValidator, ShaderValidator}; use chrono::{DateTime, Utc}; use std::io::Cursor; use thiserror::Error; @@ -119,8 +117,7 @@ pub async fn validate_file( if let Some(file_type) = file_type { match file_type { - FileType::RequiredResourcePack - | FileType::OptionalResourcePack => { + FileType::RequiredResourcePack | FileType::OptionalResourcePack => { project_type = "resourcepack".to_string(); loaders = vec![Loader("minecraft".to_string())]; } @@ -150,13 +147,12 @@ pub async fn validate_file( if visited { 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 { Err(ValidationError::InvalidInput( - format!( - "File extension {file_extension} is invalid for input file" - ) - .into(), + format!("File extension {file_extension} is invalid for input file").into(), )) } } else { @@ -173,24 +169,20 @@ fn game_version_supported( ) -> bool { match supported_game_versions { SupportedGameVersions::All => true, - SupportedGameVersions::PastDate(date) => { - game_versions.iter().any(|x| { - all_game_versions - .iter() - .find(|y| y.version == x.0) - .map(|x| x.created > date) - .unwrap_or(false) - }) - } - SupportedGameVersions::Range(before, after) => { - game_versions.iter().any(|x| { - all_game_versions - .iter() - .find(|y| y.version == x.0) - .map(|x| x.created > before && x.created < after) - .unwrap_or(false) - }) - } + SupportedGameVersions::PastDate(date) => game_versions.iter().any(|x| { + all_game_versions + .iter() + .find(|y| y.version == x.0) + .map(|x| x.created > date) + .unwrap_or(false) + }), + SupportedGameVersions::Range(before, after) => game_versions.iter().any(|x| { + all_game_versions + .iter() + .find(|y| y.version == x.0) + .map(|x| x.created > before && x.created < after) + .unwrap_or(false) + }), SupportedGameVersions::Custom(versions) => { versions.iter().any(|x| game_versions.contains(x)) } diff --git a/src/validate/modpack.rs b/src/validate/modpack.rs index c515917d0..67ded7823 100644 --- a/src/validate/modpack.rs +++ b/src/validate/modpack.rs @@ -1,8 +1,6 @@ use crate::models::pack::{PackFileHash, PackFormat}; use crate::util::validate::validation_errors_to_string; -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use std::io::{Cursor, Read}; use std::path::Component; use validator::Validate; @@ -32,14 +30,11 @@ impl super::Validator for ModpackValidator { archive: &mut ZipArchive>, ) -> Result { let pack: PackFormat = { - let mut file = - if let Ok(file) = archive.by_name("modrinth.index.json") { - file - } else { - return Ok(ValidationResult::Warning( - "Pack manifest is missing.", - )); - }; + let mut file = if let Ok(file) = archive.by_name("modrinth.index.json") { + file + } else { + return Ok(ValidationResult::Warning("Pack manifest is missing.")); + }; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -48,9 +43,7 @@ impl super::Validator for ModpackValidator { }; pack.validate().map_err(|err| { - ValidationError::InvalidInput( - validation_errors_to_string(err, None).into(), - ) + ValidationError::InvalidInput(validation_errors_to_string(err, None).into()) })?; if pack.game != "minecraft" { @@ -75,11 +68,7 @@ impl super::Validator for ModpackValidator { let path = std::path::Path::new(&file.path) .components() .next() - .ok_or_else(|| { - ValidationError::InvalidInput( - "Invalid pack file path!".into(), - ) - })?; + .ok_or_else(|| ValidationError::InvalidInput("Invalid pack file path!".into()))?; match path { Component::CurDir | Component::Normal(_) => {} diff --git a/src/validate/plugin.rs b/src/validate/plugin.rs index 40cd9ffb9..432364642 100644 --- a/src/validate/plugin.rs +++ b/src/validate/plugin.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use std::io::Cursor; use zip::ZipArchive; diff --git a/src/validate/quilt.rs b/src/validate/quilt.rs index 5ec540cc6..2e3164e90 100644 --- a/src/validate/quilt.rs +++ b/src/validate/quilt.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; @@ -37,9 +35,10 @@ impl super::Validator for QuiltValidator { )); } - if !archive.file_names().any(|name| { - name.ends_with("refmap.json") || name.ends_with(".class") - }) { + if !archive + .file_names() + .any(|name| name.ends_with("refmap.json") || name.ends_with(".class")) + { return Ok(ValidationResult::Warning( "Quilt mod file is a source file!", )); diff --git a/src/validate/resourcepack.rs b/src/validate/resourcepack.rs index fb34306a2..8e9b1bebc 100644 --- a/src/validate/resourcepack.rs +++ b/src/validate/resourcepack.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; diff --git a/src/validate/shader.rs b/src/validate/shader.rs index e2ce1f049..17b4808ed 100644 --- a/src/validate/shader.rs +++ b/src/validate/shader.rs @@ -1,6 +1,4 @@ -use crate::validate::{ - SupportedGameVersions, ValidationError, ValidationResult, -}; +use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; use std::io::Cursor; use zip::ZipArchive;