From 55c3e3ad2a63699bc5832334b62430d7cacfcae9 Mon Sep 17 00:00:00 2001 From: OmegaJak Date: Mon, 9 Oct 2023 18:10:04 -0500 Subject: [PATCH] Risky dedups --- Cargo.lock | 12 + Cargo.toml | 2 + src/database/models/project_item.rs | 54 ++- src/database/models/thread_item.rs | 2 - src/database/models/version_item.rs | 56 ++- src/routes/v2/projects.rs | 582 ++++++++++++---------------- src/routes/v2/versions.rs | 29 +- 7 files changed, 365 insertions(+), 372 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 030e2fc7..605a8368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,17 @@ dependencies = [ "uuid 1.4.0", ] +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_builder" version = "0.12.0" @@ -2246,6 +2257,7 @@ dependencies = [ "color-thief", "dashmap", "deadpool-redis", + "derive-new", "dotenvy", "env_logger", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 8e464ad5..5196b837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,5 +93,7 @@ woothee = "0.13.0" lettre = "0.10.4" +derive-new = "0.5.9" + [dev-dependencies] actix-http = "3.4.0" diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index 4018adb2..f214c9ad 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -105,6 +105,38 @@ impl GalleryItem { } } +#[derive(derive_new::new)] +pub struct ModCategory { + project_id: ProjectId, + category_id: CategoryId, + is_additional: bool, +} + +impl ModCategory { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (project_ids, category_ids, is_additionals): (Vec<_>, Vec<_>, Vec<_>) = items + .into_iter() + .map(|mc| (mc.project_id.0, mc.category_id.0, mc.is_additional)) + .multiunzip(); + sqlx::query!( + " + INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) + SELECT * FROM UNNEST ($1::bigint[], $2::int[], $3::bool[]) + ", + &project_ids[..], + &category_ids[..], + &is_additionals[..] + ) + .execute(&mut *transaction) + .await?; + + Ok(()) + } +} + #[derive(Clone)] pub struct ProjectBuilder { pub project_id: ProjectId, @@ -199,27 +231,17 @@ impl ProjectBuilder { GalleryItem::insert_many(gallery_items, self.project_id, &mut *transaction).await?; - let project_id = self.project_id.0; - let (project_ids, category_ids, is_additionals): (Vec<_>, Vec<_>, Vec<_>) = categories + let project_id = self.project_id; + let mod_categories = categories .into_iter() - .map(|c| (project_id, c.0, false)) + .map(|c| ModCategory::new(project_id, c, false)) .chain( additional_categories .into_iter() - .map(|c| (project_id, c.0, true)), + .map(|c| ModCategory::new(project_id, c, true)), ) - .multiunzip(); - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - SELECT * FROM UNNEST ($1::bigint[], $2::int[], $3::bool[]) - ", - &project_ids[..], - &category_ids[..], - &is_additionals[..] - ) - .execute(&mut *transaction) - .await?; + .collect_vec(); + ModCategory::insert_many(mod_categories, &mut *transaction).await?; Project::update_game_versions(self.project_id, &mut *transaction).await?; Project::update_loaders(self.project_id, &mut *transaction).await?; diff --git a/src/database/models/thread_item.rs b/src/database/models/thread_item.rs index e34b6531..e9aaa817 100644 --- a/src/database/models/thread_item.rs +++ b/src/database/models/thread_item.rs @@ -1,5 +1,3 @@ -use std::os::unix::thread; - use super::ids::*; use crate::database::models::DatabaseError; use crate::models::threads::{MessageBody, ThreadType}; diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index 19ca48b8..fbc7768b 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -241,8 +241,37 @@ impl VersionBuilder { DependencyBuilder::insert_many(dependencies, self.version_id, transaction).await?; - let (loader_ids, version_ids): (Vec<_>, Vec<_>) = - loaders.iter().map(|l| (l.0, version_id.0)).unzip(); + let loader_versions = loaders + .iter() + .map(|l| LoaderVersion::new(*l, version_id)) + .collect_vec(); + LoaderVersion::insert_many(loader_versions, &mut *transaction).await?; + + let game_version_versions = game_versions + .iter() + .map(|v| VersionVersion::new(*v, version_id)) + .collect_vec(); + VersionVersion::insert_many(game_version_versions, &mut *transaction).await?; + + Ok(self.version_id) + } +} + +#[derive(derive_new::new)] +pub struct LoaderVersion { + pub loader_id: LoaderId, + pub version_id: VersionId, +} + +impl LoaderVersion { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (loader_ids, version_ids): (Vec<_>, Vec<_>) = items + .iter() + .map(|l| (l.loader_id.0, l.version_id.0)) + .unzip(); sqlx::query!( " INSERT INTO loaders_versions (loader_id, version_id) @@ -254,8 +283,25 @@ impl VersionBuilder { .execute(&mut *transaction) .await?; - let (game_version_ids, version_ids): (Vec<_>, Vec<_>) = - game_versions.iter().map(|v| (v.0, version_id.0)).unzip(); + Ok(()) + } +} + +#[derive(derive_new::new)] +pub struct VersionVersion { + pub game_version_id: GameVersionId, + pub joining_version_id: VersionId, +} + +impl VersionVersion { + pub async fn insert_many( + items: Vec, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, + ) -> Result<(), DatabaseError> { + let (game_version_ids, version_ids): (Vec<_>, Vec<_>) = items + .into_iter() + .map(|i| (i.game_version_id.0, i.joining_version_id.0)) + .unzip(); sqlx::query!( " INSERT INTO game_versions_versions (game_version_id, joining_version_id) @@ -267,7 +313,7 @@ impl VersionBuilder { .execute(&mut *transaction) .await?; - Ok(self.version_id) + Ok(()) } } diff --git a/src/routes/v2/projects.rs b/src/routes/v2/projects.rs index 009d2561..d23904aa 100644 --- a/src/routes/v2/projects.rs +++ b/src/routes/v2/projects.rs @@ -2,7 +2,7 @@ use crate::auth::{filter_authorized_projects, get_user_from_headers, is_authoriz use crate::database; use crate::database::models::image_item; use crate::database::models::notification_item::NotificationBuilder; -use crate::database::models::project_item::GalleryItem; +use crate::database::models::project_item::{GalleryItem, ModCategory}; use crate::database::models::thread_item::ThreadMessageBuilder; use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; @@ -32,6 +32,9 @@ use sqlx::PgPool; use std::sync::Arc; use validator::Validate; +use database::models as db_models; +use db_models::ids as db_ids; + pub fn config(cfg: &mut web::ServiceConfig) { cfg.service(project_search); cfg.service(projects_get); @@ -98,11 +101,11 @@ pub async fn random_projects_get( .collect::>(), ) .fetch_many(&**pool) - .try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ids::ProjectId(m.id))) }) + .try_filter_map(|e| async { Ok(e.right().map(|m| db_ids::ProjectId(m.id))) }) .try_collect::>() .await?; - let projects_data = database::models::Project::get_many_ids(&project_ids, &**pool, &redis) + let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis) .await? .into_iter() .map(Project::from) @@ -125,7 +128,7 @@ pub async fn projects_get( session_queue: web::Data, ) -> Result { let ids = serde_json::from_str::>(&ids.ids)?; - let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?; + let projects_data = db_models::Project::get_many(&ids, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, @@ -153,7 +156,7 @@ pub async fn project_get( ) -> Result { let string = info.into_inner().0; - let project_data = database::models::Project::get(&string, &**pool, &redis).await?; + let project_data = db_models::Project::get(&string, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, &**pool, @@ -182,7 +185,7 @@ pub async fn project_get_check( ) -> Result { let slug = info.into_inner().0; - let project_data = database::models::Project::get(&slug, &**pool, &redis).await?; + let project_data = db_models::Project::get(&slug, &**pool, &redis).await?; if let Some(project) = project_data { Ok(HttpResponse::Ok().json(json! ({ @@ -209,7 +212,7 @@ pub async fn dependency_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; let user_option = get_user_from_headers( &req, @@ -248,7 +251,7 @@ pub async fn dependency_list( let dep_version_ids = dependencies .iter() .filter_map(|x| x.0) - .collect::>(); + .collect::>(); let (projects_result, versions_result) = futures::future::try_join( database::Project::get_many_ids(&project_ids, &**pool, &redis), database::Version::get_many(&dep_version_ids, &**pool, &redis), @@ -400,13 +403,13 @@ pub async fn project_edit( .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; if let Some(project_item) = result { let id = project_item.inner.id; let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -437,7 +440,7 @@ pub async fn project_edit( WHERE (id = $2) ", title.trim(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -458,7 +461,7 @@ pub async fn project_edit( WHERE (id = $2) ", description, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -495,7 +498,7 @@ pub async fn project_edit( SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW() WHERE (id = $1) ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -506,7 +509,7 @@ pub async fn project_edit( SET show_in_mod_inbox = FALSE WHERE id = $1 ", - project_item.thread_id as database::models::ids::ThreadId, + project_item.thread_id as db_ids::ThreadId, ) .execute(&mut *transaction) .await?; @@ -519,7 +522,7 @@ pub async fn project_edit( SET approved = NOW() WHERE id = $1 AND approved IS NULL ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -543,7 +546,7 @@ pub async fn project_edit( SET webhook_sent = TRUE WHERE id = $1 ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -581,12 +584,10 @@ pub async fn project_edit( FROM team_members tm WHERE tm.team_id = $1 AND tm.accepted ", - project_item.inner.team_id as database::models::ids::TeamId + project_item.inner.team_id as db_ids::TeamId ) .fetch_many(&mut *transaction) - .try_filter_map(|e| async { - Ok(e.right().map(|c| database::models::UserId(c.id))) - }) + .try_filter_map(|e| async { Ok(e.right().map(|c| db_models::UserId(c.id))) }) .try_collect::>() .await?; @@ -619,7 +620,7 @@ pub async fn project_edit( WHERE (id = $2) ", status.as_str(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -653,7 +654,7 @@ pub async fn project_edit( WHERE (id = $2) ", requested_status.map(|x| x.as_str()), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -666,7 +667,7 @@ pub async fn project_edit( DELETE FROM mods_categories WHERE joining_mod_id = $1 AND is_additional = FALSE ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -678,7 +679,7 @@ pub async fn project_edit( DELETE FROM mods_categories WHERE joining_mod_id = $1 AND is_additional = TRUE ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -686,67 +687,25 @@ pub async fn project_edit( } if let Some(categories) = &new_project.categories { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the categories of this project!" - .to_string(), - )); - } - - for category in categories { - let category_id = - database::models::categories::Category::get_id(category, &mut *transaction) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, FALSE) - ", - id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } + edit_project_categories( + categories, + &perms, + id as db_ids::ProjectId, + false, + &mut transaction, + ) + .await?; } if let Some(categories) = &new_project.additional_categories { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the additional categories of this project!" - .to_string(), - )); - } - - for category in categories { - let category_id = - database::models::categories::Category::get_id(category, &mut *transaction) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })?; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, TRUE) - ", - id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } + edit_project_categories( + categories, + &perms, + id as db_ids::ProjectId, + true, + &mut transaction, + ) + .await?; } if let Some(issues_url) = &new_project.issues_url { @@ -764,7 +723,7 @@ pub async fn project_edit( WHERE (id = $2) ", issues_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -785,7 +744,7 @@ pub async fn project_edit( WHERE (id = $2) ", source_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -806,7 +765,7 @@ pub async fn project_edit( WHERE (id = $2) ", wiki_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -827,7 +786,7 @@ pub async fn project_edit( WHERE (id = $2) ", license_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -848,7 +807,7 @@ pub async fn project_edit( WHERE (id = $2) ", discord_url.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -906,7 +865,7 @@ pub async fn project_edit( WHERE (id = $2) ", Some(slug), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -920,12 +879,10 @@ pub async fn project_edit( )); } - let side_type_id = database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = + db_models::categories::SideType::get_id(new_side.as_str(), &mut *transaction) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -933,8 +890,8 @@ pub async fn project_edit( SET client_side = $1 WHERE (id = $2) ", - side_type_id as database::models::SideTypeId, - id as database::models::ids::ProjectId, + side_type_id as db_models::SideTypeId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -948,12 +905,10 @@ pub async fn project_edit( )); } - let side_type_id = database::models::categories::SideType::get_id( - new_side.as_str(), - &mut *transaction, - ) - .await? - .expect("No database entry found for side type"); + let side_type_id = + db_models::categories::SideType::get_id(new_side.as_str(), &mut *transaction) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -961,8 +916,8 @@ pub async fn project_edit( SET server_side = $1 WHERE (id = $2) ", - side_type_id as database::models::SideTypeId, - id as database::models::ids::ProjectId, + side_type_id as db_models::SideTypeId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -993,7 +948,7 @@ pub async fn project_edit( WHERE (id = $2) ", license, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1011,13 +966,13 @@ pub async fn project_edit( DELETE FROM mods_donations WHERE joining_mod_id = $1 ", - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; for donation in donations { - let platform_id = database::models::categories::DonationPlatform::get_id( + let platform_id = db_models::categories::DonationPlatform::get_id( &donation.id, &mut *transaction, ) @@ -1034,8 +989,8 @@ pub async fn project_edit( INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) VALUES ($1, $2, $3) ", - id as database::models::ids::ProjectId, - platform_id as database::models::ids::DonationPlatformId, + id as db_ids::ProjectId, + platform_id as db_ids::DonationPlatformId, donation.url ) .execute(&mut *transaction) @@ -1060,7 +1015,7 @@ pub async fn project_edit( WHERE (id = $2) ", moderation_message.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1084,7 +1039,7 @@ pub async fn project_edit( WHERE (id = $2) ", moderation_message_body.as_deref(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1105,7 +1060,7 @@ pub async fn project_edit( WHERE (id = $2) ", body, - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1137,7 +1092,7 @@ pub async fn project_edit( WHERE (id = $2) ", monetization_status.as_str(), - id as database::models::ids::ProjectId, + id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1155,7 +1110,7 @@ pub async fn project_edit( }; img::delete_unused_images(context, checkable_strings, &mut transaction, &redis).await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1261,14 +1216,12 @@ pub async fn projects_edit( .validate() .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; - let project_ids: Vec = - serde_json::from_str::>(&ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect(); + let project_ids: Vec = serde_json::from_str::>(&ids.ids)? + .into_iter() + .map(|x| x.into()) + .collect(); - let projects_data = - database::models::Project::get_many_ids(&project_ids, &**pool, &redis).await?; + let projects_data = db_models::Project::get_many_ids(&project_ids, &**pool, &redis).await?; if let Some(id) = project_ids .iter() @@ -1283,31 +1236,27 @@ pub async fn projects_edit( let team_ids = projects_data .iter() .map(|x| x.inner.team_id) - .collect::>(); + .collect::>(); let team_members = - database::models::TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?; + db_models::TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?; let organization_ids = projects_data .iter() .filter_map(|x| x.inner.organization_id) - .collect::>(); + .collect::>(); let organizations = - database::models::Organization::get_many_ids(&organization_ids, &**pool, &redis).await?; + db_models::Organization::get_many_ids(&organization_ids, &**pool, &redis).await?; let organization_team_ids = organizations .iter() .map(|x| x.team_id) - .collect::>(); - let organization_team_members = database::models::TeamMember::get_from_team_full_many( - &organization_team_ids, - &**pool, - &redis, - ) - .await?; + .collect::>(); + let organization_team_members = + db_models::TeamMember::get_from_team_full_many(&organization_team_ids, &**pool, &redis) + .await?; - let categories = database::models::categories::Category::list(&**pool, &redis).await?; - let donation_platforms = - database::models::categories::DonationPlatform::list(&**pool, &redis).await?; + let categories = db_models::categories::Category::list(&**pool, &redis).await?; + let donation_platforms = db_models::categories::DonationPlatform::list(&**pool, &redis).await?; let mut transaction = pool.begin().await?; @@ -1357,126 +1306,31 @@ pub async fn projects_edit( }; } - let mut set_categories = if let Some(categories) = bulk_edit_project.categories.clone() { - categories - } else { - project.categories.clone() - }; - - if let Some(delete_categories) = &bulk_edit_project.remove_categories { - for category in delete_categories { - if let Some(pos) = set_categories.iter().position(|x| x == category) { - set_categories.remove(pos); - } - } - } - - if let Some(add_categories) = &bulk_edit_project.add_categories { - for category in add_categories { - if set_categories.len() < 3 { - set_categories.push(category.clone()); - } else { - break; - } - } - } - - if set_categories != project.categories { - sqlx::query!( - " - DELETE FROM mods_categories - WHERE joining_mod_id = $1 AND is_additional = FALSE - ", - project.inner.id as database::models::ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - - for category in set_categories { - let category_id = categories - .iter() - .find(|x| x.category == category) - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })? - .id; - - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, FALSE) - ", - project.inner.id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } - } - - let mut set_additional_categories = - if let Some(categories) = bulk_edit_project.additional_categories.clone() { - categories - } else { - project.additional_categories.clone() - }; - - if let Some(delete_categories) = &bulk_edit_project.remove_additional_categories { - for category in delete_categories { - if let Some(pos) = set_additional_categories.iter().position(|x| x == category) { - set_additional_categories.remove(pos); - } - } - } - - if let Some(add_categories) = &bulk_edit_project.add_additional_categories { - for category in add_categories { - if set_additional_categories.len() < 256 { - set_additional_categories.push(category.clone()); - } else { - break; - } - } - } - - if set_additional_categories != project.additional_categories { - sqlx::query!( - " - DELETE FROM mods_categories - WHERE joining_mod_id = $1 AND is_additional = TRUE - ", - project.inner.id as database::models::ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - - for category in set_additional_categories { - let category_id = categories - .iter() - .find(|x| x.category == category) - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Category {} does not exist.", - category.clone() - )) - })? - .id; + bulk_edit_project_categories( + &categories, + &project.categories, + project.inner.id as db_ids::ProjectId, + &bulk_edit_project.categories, + &bulk_edit_project.remove_categories, + &bulk_edit_project.add_categories, + 3, + false, + &mut transaction, + ) + .await?; - sqlx::query!( - " - INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional) - VALUES ($1, $2, TRUE) - ", - project.inner.id as database::models::ids::ProjectId, - category_id as database::models::ids::CategoryId, - ) - .execute(&mut *transaction) - .await?; - } - } + bulk_edit_project_categories( + &categories, + &project.additional_categories, + project.inner.id as db_ids::ProjectId, + &bulk_edit_project.additional_categories, + &bulk_edit_project.remove_additional_categories, + &bulk_edit_project.add_additional_categories, + 256, + true, + &mut transaction, + ) + .await?; let project_donations: Vec = project .donation_urls @@ -1515,7 +1369,7 @@ pub async fn projects_edit( DELETE FROM mods_donations WHERE joining_mod_id = $1 ", - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1537,8 +1391,8 @@ pub async fn projects_edit( INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) VALUES ($1, $2, $3) ", - project.inner.id as database::models::ids::ProjectId, - platform_id as database::models::ids::DonationPlatformId, + project.inner.id as db_ids::ProjectId, + platform_id as db_ids::DonationPlatformId, donation.url ) .execute(&mut *transaction) @@ -1554,7 +1408,7 @@ pub async fn projects_edit( WHERE (id = $2) ", issues_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1568,7 +1422,7 @@ pub async fn projects_edit( WHERE (id = $2) ", source_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1582,7 +1436,7 @@ pub async fn projects_edit( WHERE (id = $2) ", wiki_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -1596,14 +1450,13 @@ pub async fn projects_edit( WHERE (id = $2) ", discord_url.as_deref(), - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; } - database::models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis) - .await?; + db_models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis).await?; } transaction.commit().await?; @@ -1611,6 +1464,98 @@ pub async fn projects_edit( Ok(HttpResponse::NoContent().body("")) } +pub async fn bulk_edit_project_categories( + all_db_categories: &Vec, + project_categories: &Vec, + project_id: db_ids::ProjectId, + bulk_categories: &Option>, + bulk_remove_categories: &Option>, + bulk_add_categories: &Option>, + max_num_categories: usize, + is_additional: bool, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result<(), ApiError> { + let mut set_categories = if let Some(categories) = bulk_categories.clone() { + categories + } else { + project_categories.clone() + }; + + if let Some(delete_categories) = &bulk_remove_categories { + for category in delete_categories { + if let Some(pos) = set_categories.iter().position(|x| x == category) { + set_categories.remove(pos); + } + } + } + + if let Some(add_categories) = &bulk_add_categories { + for category in add_categories { + if set_categories.len() < max_num_categories { + set_categories.push(category.clone()); + } else { + break; + } + } + } + + if &set_categories != project_categories { + sqlx::query!( + " + DELETE FROM mods_categories + WHERE joining_mod_id = $1 AND is_additional = $2 + ", + project_id as db_ids::ProjectId, + is_additional + ) + .execute(&mut *transaction) + .await?; + + let mut mod_categories = Vec::new(); + for category in set_categories { + let category_id = all_db_categories + .iter() + .find(|x| x.category == category) + .ok_or_else(|| { + ApiError::InvalidInput(format!("Category {} does not exist.", category.clone())) + })? + .id; + mod_categories.push(ModCategory::new(project_id, category_id, is_additional)); + } + ModCategory::insert_many(mod_categories, &mut *transaction).await?; + } + + Ok(()) +} + +pub async fn edit_project_categories( + categories: &Vec, + perms: &ProjectPermissions, + project_id: db_ids::ProjectId, + additional: bool, + transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, +) -> Result<(), ApiError> { + if !perms.contains(ProjectPermissions::EDIT_DETAILS) { + let additional_str = if additional { "additional " } else { "" }; + return Err(ApiError::CustomAuthentication(format!( + "You do not have the permissions to edit the {additional_str}categories of this project!" + ))); + } + + let mut mod_categories = Vec::new(); + for category in categories { + let category_id = db_models::categories::Category::get_id(category, &mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!("Category {} does not exist.", category.clone())) + })?; + mod_categories.push(ModCategory::new(project_id, category_id, additional)); + } + ModCategory::insert_many(mod_categories, &mut *transaction).await?; + + Ok(()) +} + #[derive(Deserialize)] pub struct SchedulingData { pub time: DateTime, @@ -1649,11 +1594,11 @@ pub async fn project_schedule( } let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis).await?; + let result = db_models::Project::get(&string, &**pool, &redis).await?; if let Some(project_item) = result { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1693,12 +1638,12 @@ pub async fn project_schedule( ", ProjectStatus::Scheduled.as_str(), scheduling_data.time, - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&**pool) .await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1742,7 +1687,7 @@ pub async fn project_icon_edit( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1750,7 +1695,7 @@ pub async fn project_icon_edit( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1811,12 +1756,12 @@ pub async fn project_icon_edit( ", format!("{}/{}", cdn_url, upload_data.file_name), color.map(|x| x as i32), - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -1855,7 +1800,7 @@ pub async fn delete_project_icon( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1863,7 +1808,7 @@ pub async fn delete_project_icon( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -1907,18 +1852,13 @@ pub async fn delete_project_icon( SET icon_url = NULL, color = NULL WHERE (id = $1) ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -1964,7 +1904,7 @@ pub async fn add_gallery_item( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -1978,7 +1918,7 @@ pub async fn add_gallery_item( if !user.role.is_admin() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2041,14 +1981,14 @@ pub async fn add_gallery_item( SET featured = $2 WHERE mod_id = $1 ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, false, ) .execute(&mut *transaction) .await?; } - let gallery_item = vec![database::models::project_item::GalleryItem { + let gallery_item = vec![db_models::project_item::GalleryItem { image_url: file_url, featured: item.featured, title: item.title, @@ -2058,7 +1998,7 @@ pub async fn add_gallery_item( }]; GalleryItem::insert_many(gallery_item, project_item.inner.id, &mut transaction).await?; - database::models::Project::clear_cache( + db_models::Project::clear_cache( project_item.inner.id, project_item.inner.slug, None, @@ -2122,7 +2062,7 @@ pub async fn edit_gallery_item( item.validate() .map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2130,7 +2070,7 @@ pub async fn edit_gallery_item( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2185,7 +2125,7 @@ pub async fn edit_gallery_item( SET featured = $2 WHERE mod_id = $1 ", - project_item.inner.id as database::models::ids::ProjectId, + project_item.inner.id as db_ids::ProjectId, false, ) .execute(&mut *transaction) @@ -2244,13 +2184,8 @@ pub async fn edit_gallery_item( .await?; } - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -2283,7 +2218,7 @@ pub async fn delete_gallery_item( .1; let string = info.into_inner().0; - let project_item = database::models::Project::get(&string, &**pool, &redis) + let project_item = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2291,7 +2226,7 @@ pub async fn delete_gallery_item( if !user.role.is_mod() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project_item.inner, user.id.into(), &**pool, @@ -2356,13 +2291,8 @@ pub async fn delete_gallery_item( .execute(&mut *transaction) .await?; - database::models::Project::clear_cache( - project_item.inner.id, - project_item.inner.slug, - None, - &redis, - ) - .await?; + db_models::Project::clear_cache(project_item.inner.id, project_item.inner.slug, None, &redis) + .await?; transaction.commit().await?; @@ -2389,7 +2319,7 @@ pub async fn project_delete( .1; let string = info.into_inner().0; - let project = database::models::Project::get(&string, &**pool, &redis) + let project = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) @@ -2397,7 +2327,7 @@ pub async fn project_delete( if !user.role.is_admin() { let (team_member, organization_team_member) = - database::models::TeamMember::get_for_project_permissions( + db_models::TeamMember::get_for_project_permissions( &project.inner, user.id.into(), &**pool, @@ -2429,8 +2359,7 @@ pub async fn project_delete( let context = ImageContext::Project { project_id: Some(project.inner.id.into()), }; - let uploaded_images = - database::models::Image::get_many_contexted(context, &mut transaction).await?; + let uploaded_images = db_models::Image::get_many_contexted(context, &mut transaction).await?; for image in uploaded_images { image_item::Image::remove(image.id, &mut transaction, &redis).await?; } @@ -2440,13 +2369,12 @@ pub async fn project_delete( DELETE FROM collections_mods WHERE mod_id = $1 ", - project.inner.id as database::models::ids::ProjectId, + project.inner.id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; - let result = - database::models::Project::remove(project.inner.id, &mut transaction, &redis).await?; + let result = db_models::Project::remove(project.inner.id, &mut transaction, &redis).await?; transaction.commit().await?; @@ -2478,14 +2406,14 @@ pub async fn project_follow( .1; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis) + let result = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) })?; - let user_id: database::models::ids::UserId = user.id.into(); - let project_id: database::models::ids::ProjectId = result.inner.id; + let user_id: db_ids::UserId = user.id.into(); + let project_id: db_ids::ProjectId = result.inner.id; if !is_authorized(&result.inner, &Some(user), &pool).await? { return Ok(HttpResponse::NotFound().body("")); @@ -2495,8 +2423,8 @@ pub async fn project_follow( " SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .fetch_one(&**pool) .await? @@ -2512,7 +2440,7 @@ pub async fn project_follow( SET follows = follows + 1 WHERE id = $1 ", - project_id as database::models::ids::ProjectId, + project_id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -2522,8 +2450,8 @@ pub async fn project_follow( INSERT INTO mod_follows (follower_id, mod_id) VALUES ($1, $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .execute(&mut *transaction) .await?; @@ -2557,21 +2485,21 @@ pub async fn project_unfollow( .1; let string = info.into_inner().0; - let result = database::models::Project::get(&string, &**pool, &redis) + let result = db_models::Project::get(&string, &**pool, &redis) .await? .ok_or_else(|| { ApiError::InvalidInput("The specified project does not exist!".to_string()) })?; - let user_id: database::models::ids::UserId = user.id.into(); + let user_id: db_ids::UserId = user.id.into(); let project_id = result.inner.id; let following = sqlx::query!( " SELECT EXISTS(SELECT 1 FROM mod_follows mf WHERE mf.follower_id = $1 AND mf.mod_id = $2) ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .fetch_one(&**pool) .await? @@ -2587,7 +2515,7 @@ pub async fn project_unfollow( SET follows = follows - 1 WHERE id = $1 ", - project_id as database::models::ids::ProjectId, + project_id as db_ids::ProjectId, ) .execute(&mut *transaction) .await?; @@ -2597,8 +2525,8 @@ pub async fn project_unfollow( DELETE FROM mod_follows WHERE follower_id = $1 AND mod_id = $2 ", - user_id as database::models::ids::UserId, - project_id as database::models::ids::ProjectId + user_id as db_ids::UserId, + project_id as db_ids::ProjectId ) .execute(&mut *transaction) .await?; diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index 75446e1a..218706e9 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -3,7 +3,7 @@ use crate::auth::{ filter_authorized_versions, get_user_from_headers, is_authorized, is_authorized_version, }; use crate::database; -use crate::database::models::version_item::DependencyBuilder; +use crate::database::models::version_item::{DependencyBuilder, LoaderVersion, VersionVersion}; use crate::database::models::{image_item, Organization}; use crate::database::redis::RedisPool; use crate::models; @@ -471,6 +471,7 @@ pub async fn version_edit( .execute(&mut *transaction) .await?; + let mut version_versions = Vec::new(); for game_version in game_versions { let game_version_id = database::models::categories::GameVersion::get_id( &game_version.0, @@ -483,17 +484,9 @@ pub async fn version_edit( ) })?; - sqlx::query!( - " - INSERT INTO game_versions_versions (game_version_id, joining_version_id) - VALUES ($1, $2) - ", - game_version_id as database::models::ids::GameVersionId, - id as database::models::ids::VersionId, - ) - .execute(&mut *transaction) - .await?; + version_versions.push(VersionVersion::new(game_version_id, id)); } + VersionVersion::insert_many(version_versions, &mut transaction).await?; database::models::Project::update_game_versions( version_item.inner.project_id, @@ -512,6 +505,7 @@ pub async fn version_edit( .execute(&mut *transaction) .await?; + let mut loader_versions = Vec::new(); for loader in loaders { let loader_id = database::models::categories::Loader::get_id(&loader.0, &mut *transaction) @@ -521,18 +515,9 @@ pub async fn version_edit( "No database entry for loader provided.".to_string(), ) })?; - - sqlx::query!( - " - INSERT INTO loaders_versions (loader_id, version_id) - VALUES ($1, $2) - ", - loader_id as database::models::ids::LoaderId, - id as database::models::ids::VersionId, - ) - .execute(&mut *transaction) - .await?; + loader_versions.push(LoaderVersion::new(loader_id, id)); } + LoaderVersion::insert_many(loader_versions, &mut transaction).await?; database::models::Project::update_loaders( version_item.inner.project_id,