diff --git a/sqlx-data.json b/sqlx-data.json index a52de9cb..4eacd559 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -1655,38 +1655,6 @@ "nullable": [] } }, - "5a03c653f1ff3339a01422ee4267a66157e6da9a51cc7d9beb0f87d59c3a444c": { - "query": "\n SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n WHERE v.mod_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "dependent_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "dependency_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "mod_dependency_id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - true, - true - ] - } - }, "5a13a79ebb1ab975f88b58e6deaba9685fe16e242c0fa4a5eea54f12f9448e6b": { "query": "\n DELETE FROM reports\n WHERE version_id = $1\n ", "describe": { @@ -2424,6 +2392,32 @@ ] } }, + "7eab623af88469235cad7cdf0b37bdf51eade3f5e1de25c63a8e08e55722003f": { + "query": "\n SELECT d.dependency_id, vd.mod_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n INNER JOIN versions vd ON d.dependency_id = vd.id\n WHERE v.mod_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "dependency_id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "mod_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + true, + false + ] + } + }, "8129255d25bf0624d83f50558b668ed7b7f9c264e380d276522fc82bc871939b": { "query": "\n INSERT INTO notifications_actions (\n notification_id, title, action_route, action_route_method\n )\n VALUES (\n $1, $2, $3, $4\n )\n ", "describe": { diff --git a/src/database/models/categories.rs b/src/database/models/categories.rs index 501fda10..4af54b36 100644 --- a/src/database/models/categories.rs +++ b/src/database/models/categories.rs @@ -62,7 +62,10 @@ 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>, { @@ -115,7 +118,10 @@ impl Category { Ok(result.map(|r| CategoryId(r.id))) } - pub async fn get_name<'a, E>(id: CategoryId, exec: E) -> Result + pub async fn get_name<'a, E>( + id: CategoryId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -159,7 +165,10 @@ impl Category { Ok(result) } - pub async fn remove<'a, E>(name: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + name: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -184,7 +193,10 @@ impl Category { impl<'a> CategoryBuilder<'a> { /// The name of the category. Must be ASCII alphanumeric or `-`/`_` - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { if name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') @@ -208,20 +220,26 @@ impl<'a> CategoryBuilder<'a> { }) } - pub fn icon(self, icon: &'a str) -> Result, DatabaseError> { + pub fn icon( + self, + icon: &'a str, + ) -> Result, DatabaseError> { Ok(Self { icon: Some(icon), ..self }) } - 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>, { - let id = *self - .project_type - .ok_or_else(|| DatabaseError::Other("No project type specified.".to_string()))?; + let id = *self.project_type.ok_or_else(|| { + DatabaseError::Other("No project type specified.".to_string()) + })?; let result = sqlx::query!( " INSERT INTO categories (category, project_type, icon) @@ -254,7 +272,10 @@ 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>, { @@ -278,7 +299,10 @@ impl Loader { Ok(result.map(|r| LoaderId(r.id))) } - pub async fn get_name<'a, E>(id: LoaderId, exec: E) -> Result + pub async fn get_name<'a, E>( + id: LoaderId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -330,7 +354,10 @@ impl Loader { } // TODO: remove loaders with projects using them - pub async fn remove<'a, E>(name: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + name: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -355,7 +382,10 @@ impl Loader { impl<'a> LoaderBuilder<'a> { /// The name of the loader. Must be ASCII alphanumeric or `-`/`_` - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { if name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') @@ -369,7 +399,10 @@ impl<'a> LoaderBuilder<'a> { } } - pub fn icon(self, icon: &'a str) -> Result, DatabaseError> { + pub fn icon( + self, + icon: &'a str, + ) -> Result, DatabaseError> { Ok(Self { icon: Some(icon), ..self @@ -471,7 +504,10 @@ impl GameVersion { Ok(result.map(|r| GameVersionId(r.id))) } - pub async fn get_name<'a, E>(id: GameVersionId, exec: E) -> Result + pub async fn get_name<'a, E>( + id: GameVersionId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -589,7 +625,10 @@ impl GameVersion { Ok(result) } - pub async fn remove<'a, E>(name: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + name: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -614,7 +653,10 @@ 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> { if version .chars() .all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c)) @@ -645,14 +687,20 @@ impl<'a> GameVersionBuilder<'a> { } } - pub fn created(self, created: &'a chrono::DateTime) -> GameVersionBuilder<'a> { + pub fn created( + self, + created: &'a chrono::DateTime, + ) -> GameVersionBuilder<'a> { Self { date: Some(created), ..self } } - 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>, { @@ -690,7 +738,10 @@ impl License { LicenseBuilder::default() } - pub async fn get_id<'a, E>(id: &str, exec: E) -> Result, DatabaseError> + pub async fn get_id<'a, E>( + id: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -707,7 +758,10 @@ impl License { Ok(result.map(|r| LicenseId(r.id))) } - pub async fn get<'a, E>(id: LicenseId, exec: E) -> Result + pub async fn get<'a, E>( + id: LicenseId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -751,7 +805,10 @@ impl License { Ok(result) } - pub async fn remove<'a, E>(short: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + short: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -776,7 +833,10 @@ impl License { impl<'a> LicenseBuilder<'a> { /// The license's short name/abbreviation. Spaces must be replaced with '_' for it to be valid - pub fn short(self, short: &'a str) -> Result, DatabaseError> { + pub fn short( + self, + short: &'a str, + ) -> Result, DatabaseError> { if short .chars() .all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c)) @@ -791,14 +851,20 @@ impl<'a> LicenseBuilder<'a> { } /// The license's long name - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { Ok(Self { name: Some(name), ..self }) } - 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>, { @@ -874,7 +940,9 @@ impl DonationPlatform { }) } - 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>, { @@ -897,7 +965,10 @@ impl DonationPlatform { Ok(result) } - pub async fn remove<'a, E>(short: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + short: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -922,7 +993,10 @@ impl DonationPlatform { impl<'a> DonationPlatformBuilder<'a> { /// The donation platform short name. Spaces must be replaced with '_' for it to be valid - pub fn short(self, short: &'a str) -> Result, DatabaseError> { + pub fn short( + self, + short: &'a str, + ) -> Result, DatabaseError> { if short .chars() .all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c)) @@ -937,14 +1011,20 @@ impl<'a> DonationPlatformBuilder<'a> { } /// The donation platform long name - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { Ok(Self { name: Some(name), ..self }) } - 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>, { @@ -974,7 +1054,10 @@ impl ReportType { ReportTypeBuilder { name: None } } - 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>, { @@ -998,7 +1081,10 @@ impl ReportType { Ok(result.map(|r| ReportTypeId(r.id))) } - pub async fn get_name<'a, E>(id: ReportTypeId, exec: E) -> Result + pub async fn get_name<'a, E>( + id: ReportTypeId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -1032,7 +1118,10 @@ impl ReportType { Ok(result) } - pub async fn remove<'a, E>(name: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + name: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -1057,7 +1146,10 @@ impl ReportType { impl<'a> ReportTypeBuilder<'a> { /// The name of the report type. Must be ASCII alphanumeric or `-`/`_` - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { if name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') @@ -1068,7 +1160,10 @@ impl<'a> ReportTypeBuilder<'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>, { @@ -1097,7 +1192,10 @@ impl ProjectType { ProjectTypeBuilder { name: None } } - 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>, { @@ -1148,7 +1246,10 @@ impl ProjectType { Ok(project_types) } - pub async fn get_name<'a, E>(id: ProjectTypeId, exec: E) -> Result + pub async fn get_name<'a, E>( + id: ProjectTypeId, + exec: E, + ) -> Result where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -1183,7 +1284,10 @@ impl ProjectType { } // TODO: remove loaders with mods using them - pub async fn remove<'a, E>(name: &str, exec: E) -> Result, DatabaseError> + pub async fn remove<'a, E>( + name: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -1208,7 +1312,10 @@ impl ProjectType { impl<'a> ProjectTypeBuilder<'a> { /// The name of the project type. Must be ASCII alphanumeric or `-`/`_` - pub fn name(self, name: &'a str) -> Result, DatabaseError> { + pub fn name( + self, + name: &'a str, + ) -> Result, DatabaseError> { if name .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') @@ -1219,7 +1326,10 @@ impl<'a> ProjectTypeBuilder<'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>, { diff --git a/src/database/models/mod.rs b/src/database/models/mod.rs index 5341ba03..e12d56ab 100644 --- a/src/database/models/mod.rs +++ b/src/database/models/mod.rs @@ -83,7 +83,10 @@ impl ids::SideTypeId { } impl ids::DonationPlatformId { - pub async fn get_id<'a, E>(id: &str, exec: E) -> Result, DatabaseError> + pub async fn get_id<'a, E>( + id: &str, + exec: E, + ) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { @@ -102,7 +105,10 @@ impl ids::DonationPlatformId { } impl ids::ProjectTypeId { - pub async fn get_id<'a, E>(project_type: String, exec: E) -> Result, DatabaseError> + pub async fn get_id<'a, E>( + project_type: String, + 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 f23bc25b..8f78a39b 100644 --- a/src/database/models/notification_item.rs +++ b/src/database/models/notification_item.rs @@ -174,9 +174,11 @@ impl Notification { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - futures::future::try_join_all(notification_ids.into_iter().map(|id| Self::get(id, exec))) - .await - .map(|x| x.into_iter().flatten().collect()) + futures::future::try_join_all( + notification_ids.into_iter().map(|id| Self::get(id, exec)), + ) + .await + .map(|x| x.into_iter().flatten().collect()) } pub async fn get_many_user<'a, E>( @@ -234,7 +236,8 @@ impl Notification { notification_ids: Vec, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result, sqlx::error::Error> { - let notification_ids_parsed: Vec = notification_ids.into_iter().map(|x| x.0).collect(); + let notification_ids_parsed: Vec = + notification_ids.into_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 e30d345f..c383612e 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -300,7 +300,8 @@ impl Project { { use futures::stream::TryStreamExt; - let project_ids_parsed: Vec = project_ids.into_iter().map(|x| x.0).collect(); + let project_ids_parsed: Vec = + project_ids.into_iter().map(|x| x.0).collect(); let projects = sqlx::query!( " SELECT id, project_type, title, description, downloads, follows, @@ -542,19 +543,24 @@ 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.clone()).ok(); + let id_option = crate::models::ids::base62_impl::parse_base62( + &*slug_or_project_id.clone(), + ) + .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) } @@ -567,18 +573,25 @@ 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) } } @@ -676,8 +689,14 @@ impl Project { moderation_message_body: m.moderation_message_body, }, project_type: m.project_type_name, - categories: categories?.into_iter().map(|x| x.category).collect(), - versions: versions?.into_iter().map(|x| VersionId(x.id)).collect(), + categories: categories? + .into_iter() + .map(|x| x.category) + .collect(), + versions: versions? + .into_iter() + .map(|x| VersionId(x.id)) + .collect(), donation_urls: donations? .into_iter() .map(|x| DonationUrl { @@ -699,11 +718,17 @@ impl Project { created: x.created, }) .collect(), - status: crate::models::projects::ProjectStatus::from_str(&m.status_name), + status: crate::models::projects::ProjectStatus::from_str( + &m.status_name, + ), license_id: m.short, license_name: m.license_name, - client_side: crate::models::projects::SideType::from_str(&m.client_side_type), - server_side: crate::models::projects::SideType::from_str(&m.server_side_type), + client_side: crate::models::projects::SideType::from_str( + &m.client_side_type, + ), + server_side: crate::models::projects::SideType::from_str( + &m.server_side_type, + ), })) } else { Ok(None) @@ -717,9 +742,11 @@ impl Project { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - futures::future::try_join_all(project_ids.into_iter().map(|id| Self::get_full(id, exec))) - .await - .map(|x| x.into_iter().flatten().collect()) + futures::future::try_join_all( + project_ids.into_iter().map(|id| Self::get_full(id, exec)), + ) + .await + .map(|x| x.into_iter().flatten().collect()) } } #[derive(Clone, Debug)] diff --git a/src/database/models/report_item.rs b/src/database/models/report_item.rs index 89f7060b..9f05a9e2 100644 --- a/src/database/models/report_item.rs +++ b/src/database/models/report_item.rs @@ -52,7 +52,10 @@ 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, { @@ -93,7 +96,8 @@ impl Report { { use futures::stream::TryStreamExt; - let report_ids_parsed: Vec = report_ids.into_iter().map(|x| x.0).collect(); + let report_ids_parsed: Vec = + report_ids.into_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 @@ -123,7 +127,10 @@ impl Report { Ok(reports) } - pub async fn remove_full<'a, E>(id: ReportId, exec: E) -> Result, sqlx::Error> + pub async fn remove_full<'a, E>( + id: ReportId, + exec: E, + ) -> Result, sqlx::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { diff --git a/src/database/models/team_item.rs b/src/database/models/team_item.rs index 0542589d..f1343dec 100644 --- a/src/database/models/team_item.rs +++ b/src/database/models/team_item.rs @@ -32,7 +32,8 @@ 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, diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index 43e9813b..c3bd77e6 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -42,7 +42,10 @@ 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>, { @@ -150,13 +153,17 @@ impl User { } } - pub async fn get_many<'a, E>(user_ids: Vec, exec: E) -> Result, sqlx::Error> + pub async fn get_many<'a, E>( + user_ids: Vec, + exec: E, + ) -> Result, sqlx::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { use futures::stream::TryStreamExt; - let user_ids_parsed: Vec = user_ids.into_iter().map(|x| x.0).collect(); + let user_ids_parsed: Vec = + user_ids.into_iter().map(|x| x.0).collect(); let users = sqlx::query!( " SELECT u.id, u.github_id, u.name, u.email, @@ -365,8 +372,11 @@ 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!( @@ -445,7 +455,8 @@ 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 2a2f1b70..0ccc9820 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -29,11 +29,13 @@ impl DependencyBuilder { version_id: VersionId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), DatabaseError> { - let (version_dependency_id, project_dependency_id): (Option, Option) = - if self.version_id.is_some() { - (self.version_id, None) - } else if let Some(project_id) = self.project_id { - let version_id = sqlx::query!( + let (version_dependency_id, project_dependency_id): ( + Option, + Option, + ) = if self.version_id.is_some() { + (self.version_id, None) + } else if let Some(project_id) = self.project_id { + let version_id = sqlx::query!( " SELECT version.id id FROM ( SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v @@ -49,10 +51,10 @@ impl DependencyBuilder { ) .fetch_optional(&mut *transaction).await?.map(|x| VersionId(x.id)); - (version_id, Some(project_id)) - } else { - (None, None) - }; + (version_id, Some(project_id)) + } else { + (None, None) + }; sqlx::query!( " @@ -561,7 +563,8 @@ impl Version { { use futures::stream::TryStreamExt; - let version_ids_parsed: Vec = version_ids.into_iter().map(|x| x.0).collect(); + let version_ids_parsed: Vec = + version_ids.into_iter().map(|x| x.0).collect(); let versions = sqlx::query!( " SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number, @@ -662,7 +665,8 @@ impl Version { ); if let Some(v) = version? { - let mut hashes_map: HashMap>> = HashMap::new(); + let mut hashes_map: HashMap>> = + HashMap::new(); for hash in hashes? { let entry = hashes_map @@ -690,11 +694,17 @@ impl Version { id: FileId(x.id), url: x.url, filename: x.filename, - hashes: hashes_map.entry(FileId(x.id)).or_default().clone(), + hashes: hashes_map + .entry(FileId(x.id)) + .or_default() + .clone(), primary: x.is_primary, }) .collect(), - game_versions: game_versions?.into_iter().map(|x| x.game_version).collect(), + game_versions: game_versions? + .into_iter() + .map(|x| x.game_version) + .collect(), loaders: loaders?.into_iter().map(|x| x.loader).collect(), featured: v.featured, dependencies: dependencies? @@ -719,9 +729,11 @@ impl Version { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - futures::future::try_join_all(version_ids.into_iter().map(|id| Self::get_full(id, exec))) - .await - .map(|x| x.into_iter().flatten().collect()) + futures::future::try_join_all( + version_ids.into_iter().map(|id| Self::get_full(id, exec)), + ) + .await + .map(|x| x.into_iter().flatten().collect()) } } diff --git a/src/database/postgres_database.rs b/src/database/postgres_database.rs index 993c49dc..1a61cc5d 100644 --- a/src/database/postgres_database.rs +++ b/src/database/postgres_database.rs @@ -5,7 +5,8 @@ use sqlx::{Connection, PgConnection, Postgres}; pub async fn connect() -> Result { info!("Initializing database connection"); - let database_url = dotenv::var("DATABASE_URL").expect("`DATABASE_URL` not in .env"); + let database_url = + dotenv::var("DATABASE_URL").expect("`DATABASE_URL` not in .env"); let pool = PgPoolOptions::new() .min_connections( dotenv::var("DATABASE_MIN_CONNECTIONS") diff --git a/src/file_hosting/backblaze.rs b/src/file_hosting/backblaze.rs index 57e42554..28d30224 100644 --- a/src/file_hosting/backblaze.rs +++ b/src/file_hosting/backblaze.rs @@ -16,10 +16,12 @@ 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, @@ -38,8 +40,13 @@ 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, @@ -74,8 +81,12 @@ 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, @@ -83,7 +94,9 @@ 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 5b99348f..698083b6 100644 --- a/src/file_hosting/backblaze/authorization.rs +++ b/src/file_hosting/backblaze/authorization.rs @@ -52,7 +52,13 @@ 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 44199d32..f24d8bc0 100644 --- a/src/file_hosting/mock.rs +++ b/src/file_hosting/mock.rs @@ -19,11 +19,15 @@ impl FileHost for MockHost { file_name: &str, file_bytes: Bytes, ) -> Result { - let path = std::path::Path::new(&dotenv::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(&dotenv::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)); + let content_sha512 = + format!("{:x}", sha2::Sha512::digest(&*file_bytes)); std::fs::write(path, &*file_bytes)?; Ok(UploadFileData { @@ -43,8 +47,9 @@ impl FileHost for MockHost { file_id: &str, file_name: &str, ) -> Result { - let path = std::path::Path::new(&dotenv::var("MOCK_FILE_PATH").unwrap()) - .join(file_name.replace("../", "")); + let path = + std::path::Path::new(&dotenv::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 63ba20d6..d1b4de46 100644 --- a/src/file_hosting/s3_host.rs +++ b/src/file_hosting/s3_host.rs @@ -1,4 +1,6 @@ -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 s3::bucket::Bucket; @@ -24,12 +26,23 @@ impl S3Host { region: bucket_region.to_string(), 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(), + ) })?; bucket.add_header("x-amz-acl", "public-read"); @@ -47,13 +60,20 @@ impl FileHost for S3Host { file_bytes: Bytes, ) -> Result { let content_sha1 = sha1::Sha1::from(&file_bytes).hexdigest(); - let content_sha512 = format!("{:x}", sha2::Sha512::digest(&*file_bytes)); + let content_sha512 = + format!("{:x}", sha2::Sha512::digest(&*file_bytes)); 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 { @@ -77,7 +97,9 @@ 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 043c7d97..2b8e55c3 100644 --- a/src/health/status.rs +++ b/src/health/status.rs @@ -1,7 +1,9 @@ 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 59ccda89..6dbe0591 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,8 @@ pub struct Pepper { #[actix_rt::main] async fn main() -> std::io::Result<()> { dotenv::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(); let config = Config::parse_args_default_or_exit(); @@ -102,37 +103,40 @@ async fn main() -> std::io::Result<()> { .await .expect("Database connection failed"); - let storage_backend = dotenv::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( - &dotenv::var("BACKBLAZE_KEY_ID").unwrap(), - &dotenv::var("BACKBLAZE_KEY").unwrap(), - &dotenv::var("BACKBLAZE_BUCKET_ID").unwrap(), - ) - .await, - ), - "s3" => Arc::new( - S3Host::new( - &*dotenv::var("S3_BUCKET_NAME").unwrap(), - &*dotenv::var("S3_REGION").unwrap(), - &*dotenv::var("S3_URL").unwrap(), - &*dotenv::var("S3_ACCESS_TOKEN").unwrap(), - &*dotenv::var("S3_SECRET").unwrap(), - ) - .unwrap(), - ), - "local" => Arc::new(file_hosting::MockHost::new()), - _ => panic!("Invalid storage backend specified. Aborting startup!"), - }; + let storage_backend = + dotenv::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( + &dotenv::var("BACKBLAZE_KEY_ID").unwrap(), + &dotenv::var("BACKBLAZE_KEY").unwrap(), + &dotenv::var("BACKBLAZE_BUCKET_ID").unwrap(), + ) + .await, + ), + "s3" => Arc::new( + S3Host::new( + &*dotenv::var("S3_BUCKET_NAME").unwrap(), + &*dotenv::var("S3_REGION").unwrap(), + &*dotenv::var("S3_URL").unwrap(), + &*dotenv::var("S3_ACCESS_TOKEN").unwrap(), + &*dotenv::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 mut skip = skip_initial; let pool_ref = pool.clone(); @@ -150,7 +154,8 @@ async fn main() -> std::io::Result<()> { } 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); } @@ -185,7 +190,8 @@ async fn main() -> std::io::Result<()> { } }); - let indexing_queue = Arc::new(search::indexing::queue::CreationQueue::new()); + let indexing_queue = + Arc::new(search::indexing::queue::CreationQueue::new()); let mut skip = skip_initial; let queue_ref = indexing_queue.clone(); @@ -214,7 +220,10 @@ async fn main() -> std::io::Result<()> { scheduler::schedule_versions(&mut scheduler, pool.clone(), skip_initial); let ip_salt = Pepper { - pepper: crate::models::ids::Base62Id(crate::models::ids::random_base62(11)).to_string(), + pepper: crate::models::ids::Base62Id( + crate::models::ids::random_base62(11), + ) + .to_string(), }; let store = MemoryStore::new(); @@ -236,10 +245,16 @@ 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::IdentificationError)? + 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::IdentificationError + })? } else { connection_info .peer_addr() @@ -249,14 +264,16 @@ async fn main() -> std::io::Result<()> { connection_info .peer_addr() .ok_or(ARError::IdentificationError)? - }); + }, + ); Ok(ip) }) .with_interval(std::time::Duration::from_secs(60)) .with_max_requests(300) .with_ignore_ips( - parse_strings_from_var("RATE_LIMIT_IGNORE_IPS").unwrap_or_default(), + parse_strings_from_var("RATE_LIMIT_IGNORE_IPS") + .unwrap_or_default(), ), ) .app_data(web::Data::new(pool.clone())) diff --git a/src/models/ids.rs b/src/models/ids.rs index 15ccfe31..1bc5d9e4 100644 --- a/src/models/ids.rs +++ b/src/models/ids.rs @@ -127,7 +127,10 @@ 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") } @@ -183,7 +186,9 @@ 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/projects.rs b/src/models/projects.rs index cbaf18ce..db374a9d 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -360,10 +360,16 @@ impl From for Version { .map(|d| Dependency { version_id: d.version_id.map(|i| VersionId(i.0 as u64)), project_id: d.project_id.map(|i| ProjectId(i.0 as u64)), - 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/ratelimit/errors.rs b/src/ratelimit/errors.rs index be8f87d4..5afd33fe 100644 --- a/src/ratelimit/errors.rs +++ b/src/ratelimit/errors.rs @@ -34,12 +34,20 @@ 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.body(self.to_string()) } - _ => actix_web::HttpResponse::build(self.status_code()).body(self.to_string()), + _ => actix_web::HttpResponse::build(self.status_code()) + .body(self.to_string()), } } } diff --git a/src/ratelimit/memory.rs b/src/ratelimit/memory.rs index 988120f9..49c63985 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,10 +74,18 @@ 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(); @@ -85,7 +93,10 @@ 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 { @@ -98,7 +109,9 @@ impl Handler for MemoryStoreActor { } None => { return ActorResponse::Update(Box::pin(future::ready(Err( - ARError::ReadWriteError("memory store: read failed!".to_string()), + ARError::ReadWriteError( + "memory store: read failed!".to_string(), + ), )))) } }, @@ -107,9 +120,11 @@ impl Handler for MemoryStoreActor { let val = match self.inner.get(&key) { Some(c) => c, None => { - return ActorResponse::Get(Box::pin(future::ready(Err( - ARError::ReadWriteError("memory store: read failed!".to_string()), - )))) + return ActorResponse::Get(Box::pin(future::ready( + Err(ARError::ReadWriteError( + "memory store: read failed!".to_string(), + )), + ))) } }; let val = val.value().0; @@ -122,14 +137,17 @@ impl Handler for MemoryStoreActor { let c = match self.inner.get(&key) { Some(d) => d, None => { - return ActorResponse::Expire(Box::pin(future::ready(Err( - ARError::ReadWriteError("memory store: read failed!".to_string()), - )))) + return ActorResponse::Expire(Box::pin(future::ready( + Err(ARError::ReadWriteError( + "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) => { @@ -137,9 +155,11 @@ impl Handler for MemoryStoreActor { let val = match self.inner.remove::(&key) { Some(c) => c, None => { - return ActorResponse::Remove(Box::pin(future::ready(Err( - ARError::ReadWriteError("memory store: remove failed!".to_string()), - )))) + return ActorResponse::Remove(Box::pin(future::ready( + Err(ARError::ReadWriteError( + "memory store: remove failed!".to_string(), + )), + ))) } }; let val = val.1; diff --git a/src/ratelimit/middleware.rs b/src/ratelimit/middleware.rs index cb528f92..5a05b293 100644 --- a/src/ratelimit/middleware.rs +++ b/src/ratelimit/middleware.rs @@ -95,7 +95,9 @@ where } /// Function to get the identifier for the client request - pub fn with_identifier Result + 'static>( + pub fn with_identifier< + F: Fn(&ServiceRequest) -> Result + 'static, + >( mut self, identifier: F, ) -> Self { @@ -108,7 +110,8 @@ 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, { @@ -141,23 +144,29 @@ where // Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter max_requests: usize, interval: u64, - identifier: Rc Result + 'static>>, + identifier: + Rc Result + 'static>>, ignore_ips: Vec, } 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) } @@ -185,9 +194,15 @@ 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::ReadWriteError("Setting timeout".to_string()))?; + .map_err(|_| { + ARError::ReadWriteError( + "Setting timeout".to_string(), + ) + })?; let reset: Duration = match expiry { ActorResponse::Expire(dur) => dur.await?, _ => unreachable!(), @@ -209,7 +224,9 @@ where }) .await .map_err(|_| { - ARError::ReadWriteError("Decrementing ratelimit".to_string()) + ARError::ReadWriteError( + "Decrementing ratelimit".to_string(), + ) })?; let updated_value: usize = match res { ActorResponse::Update(c) => c.await?, @@ -222,15 +239,23 @@ 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) } @@ -245,7 +270,9 @@ where }) .await .map_err(|_| { - ARError::ReadWriteError("Creating store entry".to_string()) + ARError::ReadWriteError( + "Creating store entry".to_string(), + ) })?; match res { ActorResponse::Set(c) => c.await?, @@ -257,15 +284,24 @@ 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/auth.rs b/src/routes/auth.rs index 0aaae350..baa85be0 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -38,14 +38,26 @@ pub enum AuthorizationError { impl actix_web::ResponseError for AuthorizationError { fn status_code(&self) -> StatusCode { match self { - AuthorizationError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR, - AuthorizationError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR, - AuthorizationError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR, + AuthorizationError::EnvError(..) => { + StatusCode::INTERNAL_SERVER_ERROR + } + AuthorizationError::SqlxDatabaseError(..) => { + StatusCode::INTERNAL_SERVER_ERROR + } + AuthorizationError::DatabaseError(..) => { + StatusCode::INTERNAL_SERVER_ERROR + } AuthorizationError::SerDeError(..) => StatusCode::BAD_REQUEST, - AuthorizationError::GithubError(..) => StatusCode::FAILED_DEPENDENCY, - AuthorizationError::InvalidCredentialsError => StatusCode::UNAUTHORIZED, + AuthorizationError::GithubError(..) => { + StatusCode::FAILED_DEPENDENCY + } + AuthorizationError::InvalidCredentialsError => { + StatusCode::UNAUTHORIZED + } AuthorizationError::DecodingError(..) => StatusCode::BAD_REQUEST, - AuthorizationError::AuthenticationError(..) => StatusCode::UNAUTHORIZED, + AuthorizationError::AuthenticationError(..) => { + StatusCode::UNAUTHORIZED + } } } @@ -57,9 +69,13 @@ impl actix_web::ResponseError for AuthorizationError { AuthorizationError::DatabaseError(..) => "database_error", AuthorizationError::SerDeError(..) => "invalid_input", AuthorizationError::GithubError(..) => "github_error", - AuthorizationError::InvalidCredentialsError => "invalid_credentials", + AuthorizationError::InvalidCredentialsError => { + "invalid_credentials" + } AuthorizationError::DecodingError(..) => "decoding_error", - AuthorizationError::AuthenticationError(..) => "authentication_error", + AuthorizationError::AuthenticationError(..) => { + "authentication_error" + } }, description: &self.to_string(), }) @@ -174,11 +190,14 @@ 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 => { - 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/maven.rs b/src/routes/maven.rs index e010d61b..a04a482f 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -58,7 +58,11 @@ pub async fn maven_metadata( ) -> Result { let project_id = params.into_inner().0; 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 data = if let Some(data) = project_data { data @@ -119,7 +123,9 @@ 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); } @@ -129,7 +135,9 @@ fn find_file<'a>( _ => return None, }; - if file == format!("{}-{}.{}", &project_id, &version.version_number, fileext) { + if file + == format!("{}-{}.{}", &project_id, &version.version_number, fileext) + { version .files .iter() @@ -148,7 +156,11 @@ 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 @@ -175,9 +187,11 @@ 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 { @@ -197,10 +211,12 @@ pub async fn version_file( name: project.inner.title, description: project.inner.description, }; - return Ok(HttpResponse::Ok() - .content_type("text/xml") - .body(yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?)); - } else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) { + return Ok(HttpResponse::Ok().content_type("text/xml").body( + yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?, + )); + } else if let Some(selected_file) = + find_file(&project_id, &project, &version, &file) + { return Ok(HttpResponse::TemporaryRedirect() .append_header(("location", &*selected_file.url)) .body("")); @@ -217,7 +233,11 @@ 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 @@ -244,9 +264,11 @@ 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 { @@ -268,7 +290,11 @@ 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 @@ -295,9 +321,11 @@ 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 fd8feacc..425b24ff 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -187,38 +187,62 @@ pub enum ApiError { impl actix_web::ResponseError for ApiError { fn status_code(&self) -> actix_web::http::StatusCode { match self { - ApiError::EnvError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::DatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::SqlxDatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::AuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED, - ApiError::CustomAuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED, - ApiError::XmlError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, + ApiError::EnvError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::DatabaseError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::SqlxDatabaseError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::AuthenticationError(..) => { + actix_web::http::StatusCode::UNAUTHORIZED + } + ApiError::CustomAuthenticationError(..) => { + actix_web::http::StatusCode::UNAUTHORIZED + } + ApiError::XmlError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST, - ApiError::SearchError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::IndexingError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::FileHostingError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, - ApiError::InvalidInputError(..) => actix_web::http::StatusCode::BAD_REQUEST, - ApiError::ValidationError(..) => actix_web::http::StatusCode::BAD_REQUEST, + ApiError::SearchError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::IndexingError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::FileHostingError(..) => { + actix_web::http::StatusCode::INTERNAL_SERVER_ERROR + } + ApiError::InvalidInputError(..) => { + actix_web::http::StatusCode::BAD_REQUEST + } + ApiError::ValidationError(..) => { + actix_web::http::StatusCode::BAD_REQUEST + } } } fn error_response(&self) -> actix_web::HttpResponse { - actix_web::HttpResponse::build(self.status_code()).json(crate::models::error::ApiError { - error: match self { - ApiError::EnvError(..) => "environment_error", - ApiError::SqlxDatabaseError(..) => "database_error", - ApiError::DatabaseError(..) => "database_error", - ApiError::AuthenticationError(..) => "unauthorized", - ApiError::CustomAuthenticationError(..) => "unauthorized", - ApiError::XmlError(..) => "xml_error", - ApiError::JsonError(..) => "json_error", - ApiError::SearchError(..) => "search_error", - ApiError::IndexingError(..) => "indexing_error", - ApiError::FileHostingError(..) => "file_hosting_error", - ApiError::InvalidInputError(..) => "invalid_input", - ApiError::ValidationError(..) => "invalid_input", + actix_web::HttpResponse::build(self.status_code()).json( + crate::models::error::ApiError { + error: match self { + ApiError::EnvError(..) => "environment_error", + ApiError::SqlxDatabaseError(..) => "database_error", + ApiError::DatabaseError(..) => "database_error", + ApiError::AuthenticationError(..) => "unauthorized", + ApiError::CustomAuthenticationError(..) => "unauthorized", + ApiError::XmlError(..) => "xml_error", + ApiError::JsonError(..) => "json_error", + ApiError::SearchError(..) => "search_error", + ApiError::IndexingError(..) => "indexing_error", + ApiError::FileHostingError(..) => "file_hosting_error", + ApiError::InvalidInputError(..) => "invalid_input", + ApiError::ValidationError(..) => "invalid_input", + }, + description: &self.to_string(), }, - description: &self.to_string(), - }) + ) } } diff --git a/src/routes/moderation.rs b/src/routes/moderation.rs index e49c226a..834b157b 100644 --- a/src/routes/moderation.rs +++ b/src/routes/moderation.rs @@ -39,15 +39,18 @@ 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/notifications.rs b/src/routes/notifications.rs index f0917757..d0382467 100644 --- a/src/routes/notifications.rs +++ b/src/routes/notifications.rs @@ -31,8 +31,11 @@ pub async fn notifications_get( .collect(); let notifications_data: Vec = - database::models::notification_item::Notification::get_many(notification_ids, &**pool) - .await?; + database::models::notification_item::Notification::get_many( + notification_ids, + &**pool, + ) + .await?; let notifications: Vec = notifications_data .into_iter() @@ -54,7 +57,11 @@ 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_mod() { @@ -78,21 +85,29 @@ 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_mod() { 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::CustomAuthenticationError( - "You are not authorized to delete this notification!".to_string(), + "You are not authorized to delete this notification!" + .to_string(), )) } } else { @@ -108,18 +123,23 @@ 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(notification_ids, &**pool) - .await?; + database::models::notification_item::Notification::get_many( + notification_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_mod() { @@ -127,8 +147,11 @@ pub async fn notifications_delete( } } - database::models::notification_item::Notification::remove_many(notifications, &mut transaction) - .await?; + database::models::notification_item::Notification::remove_many( + notifications, + &mut transaction, + ) + .await?; transaction.commit().await?; diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index c227c3bf..9029d07d 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -67,10 +67,14 @@ 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, @@ -81,7 +85,9 @@ 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, @@ -297,17 +303,21 @@ pub async fn project_create_inner( let cdn_url = dotenv::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 @@ -324,9 +334,9 @@ pub 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( @@ -336,19 +346,22 @@ pub 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) @@ -393,15 +406,17 @@ pub async fn project_create_inner( project_create_data = create_data; } - let project_type_id = - models::ProjectTypeId::get_id(project_create_data.project_type.clone(), &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput(format!( - "Project Type {} does not exist.", - project_create_data.project_type.clone() - )) - })?; + let project_type_id = models::ProjectTypeId::get_id( + project_create_data.project_type.clone(), + &mut *transaction, + ) + .await? + .ok_or_else(|| { + CreateError::InvalidInput(format!( + "Project Type {} does not exist.", + project_create_data.project_type.clone() + )) + })?; let mut icon_url = None; @@ -409,9 +424,9 @@ pub async fn project_create_inner( let mut field: Field = item.map_err(CreateError::MultipartError)?; let content_disposition = field.content_disposition().clone(); - let name = content_disposition - .get_name() - .ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?; + let name = content_disposition.get_name().ok_or_else(|| { + CreateError::MissingValueError("Missing content name".to_string()) + })?; let (file_name, file_extension) = super::version_creation::get_name_ext(&content_disposition)?; @@ -454,11 +469,21 @@ pub 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) - .ok_or_else(|| CreateError::InvalidIconFormat(file_extension.to_string()))?; - - let url = format!("data/{}/images/{}.{}", project_id, hash, file_extension); + super::version_creation::get_name_ext( + &content_disposition, + )?; + let content_type = + crate::util::ext::get_image_content_type(file_extension) + .ok_or_else(|| { + CreateError::InvalidIconFormat( + file_extension.to_string(), + ) + })?; + + let url = format!( + "data/{}/images/{}.{}", + project_id, hash, file_extension + ); let upload_data = file_host .upload_file(content_type, &url, data.freeze()) .await?; @@ -491,7 +516,8 @@ pub 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( @@ -529,7 +555,8 @@ pub 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, @@ -568,44 +595,58 @@ pub async fn project_create_inner( let status_id = models::StatusId::get_id(&status, &mut *transaction) .await? .ok_or_else(|| { - CreateError::InvalidInput(format!("Status {} does not exist.", status.clone())) + CreateError::InvalidInput(format!( + "Status {} does not exist.", + status.clone() + )) })?; - let client_side_id = - models::SideTypeId::get_id(&project_create_data.client_side, &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput( - "Client side type specified does not exist.".to_string(), - ) - })?; + let client_side_id = models::SideTypeId::get_id( + &project_create_data.client_side, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + CreateError::InvalidInput( + "Client side type specified does not exist.".to_string(), + ) + })?; - let server_side_id = - models::SideTypeId::get_id(&project_create_data.server_side, &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput( - "Server side type specified does not exist.".to_string(), - ) - })?; + let server_side_id = models::SideTypeId::get_id( + &project_create_data.server_side, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + CreateError::InvalidInput( + "Server side type specified does not exist.".to_string(), + ) + })?; - let license_id = - models::categories::License::get_id(&project_create_data.license_id, &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput("License specified does not exist.".to_string()) - })?; + let license_id = models::categories::License::get_id( + &project_create_data.license_id, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + CreateError::InvalidInput( + "License specified does not exist.".to_string(), + ) + })?; let mut donation_urls = vec![]; if let Some(urls) = &project_create_data.donation_urls { for url in urls { - let platform_id = models::DonationPlatformId::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::DonationPlatformId::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 { project_id: project_id.into(), @@ -695,9 +736,12 @@ pub async fn project_create_inner( if status == ProjectStatus::Processing { if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") { - crate::util::webhook::send_discord_webhook(response.clone(), webhook_url) - .await - .ok(); + crate::util::webhook::send_discord_webhook( + response.clone(), + webhook_url, + ) + .await + .ok(); } } @@ -720,12 +764,13 @@ 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 @@ -794,8 +839,15 @@ async fn process_icon_upload( mut field: actix_multipart::Field, cdn_url: &str, ) -> Result { - 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 upload_data = file_host .upload_file( diff --git a/src/routes/projects.rs b/src/routes/projects.rs index cffb76fa..69db51e1 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -39,12 +39,14 @@ pub async fn projects_get( web::Query(ids): web::Query, pool: web::Data, ) -> Result { - let project_ids = serde_json::from_str::>(&*ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect(); + let project_ids = + serde_json::from_str::>(&*ids.ids)? + .into_iter() + .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(); @@ -71,7 +73,10 @@ 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(); @@ -96,7 +101,9 @@ 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?; if let Some(project) = result { let id = project.id; @@ -105,9 +112,10 @@ pub async fn dependency_list( let dependencies = sqlx::query!( " - SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id + SELECT d.dependency_id, vd.mod_id FROM versions v INNER JOIN dependencies d ON d.dependent_id = v.id + INNER JOIN versions vd ON d.dependency_id = vd.id WHERE v.mod_id = $1 ", id as database::models::ProjectId @@ -116,26 +124,24 @@ pub async fn dependency_list( .try_filter_map(|e| async { Ok(e.right().map(|x| { ( - database::models::VersionId(x.dependent_id), x.dependency_id.map(database::models::VersionId), - x.mod_dependency_id.map(database::models::ProjectId), + database::models::ProjectId(x.mod_id), ) })) }) .try_collect::, - Option, + database::models::ProjectId, )>>() .await?; let (projects_result, versions_result) = futures::join!( database::Project::get_many_full( - dependencies.iter().map(|x| x.2).flatten().collect(), + dependencies.iter().map(|x| x.1).collect(), &**pool, ), database::Version::get_many_full( - dependencies.iter().map(|x| x.1).flatten().collect(), + dependencies.iter().map(|x| x.0).flatten().collect(), &**pool, ) ); @@ -244,13 +250,15 @@ pub async fn project_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - new_project - .validate() - .map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?; + new_project.validate().map_err(|err| { + ApiError::ValidationError(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; @@ -324,11 +332,13 @@ pub async fn project_edit( )); } - if (status == &ProjectStatus::Rejected || status == &ProjectStatus::Approved) + if (status == &ProjectStatus::Rejected + || status == &ProjectStatus::Approved) && !user.role.is_mod() { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to set this status".to_string(), + "You don't have permission to set this status" + .to_string(), )); } @@ -361,7 +371,9 @@ pub async fn project_edit( .execute(&mut *transaction) .await?; - if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") { + if let Ok(webhook_url) = + dotenv::var("MODERATION_DISCORD_WEBHOOK") + { crate::util::webhook::send_discord_webhook( Project::from(project_item.clone()), webhook_url, @@ -371,13 +383,16 @@ pub async fn project_edit( } } - let status_id = database::models::StatusId::get_id(status, &mut *transaction) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError( - "No database entry for status provided.".to_string(), - ) - })?; + let status_id = database::models::StatusId::get_id( + status, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "No database entry for status provided.".to_string(), + ) + })?; sqlx::query!( " @@ -391,12 +406,19 @@ pub async fn project_edit( .execute(&mut *transaction) .await?; - if project_item.status.is_searchable() && !status.is_searchable() { + if project_item.status.is_searchable() + && !status.is_searchable() + { delete_from_index(id.into(), config).await?; - } else if !project_item.status.is_searchable() && status.is_searchable() { + } else if !project_item.status.is_searchable() + && status.is_searchable() + { let index_project = - crate::search::indexing::local_import::query_one(id, &mut *transaction) - .await?; + crate::search::indexing::local_import::query_one( + id, + &mut *transaction, + ) + .await?; indexing_queue.add(index_project); } @@ -422,14 +444,17 @@ 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::InvalidInputError(format!( - "Category {} does not exist.", - category.clone() - )) - })?; + database::models::categories::Category::get_id( + category, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError(format!( + "Category {} does not exist.", + category.clone() + )) + })?; sqlx::query!( " @@ -574,7 +599,8 @@ pub async fn project_edit( if results.exists.unwrap_or(true) { return Err(ApiError::InvalidInputError( - "Slug collides with other project's id!".to_string(), + "Slug collides with other project's id!" + .to_string(), )); } } @@ -601,10 +627,12 @@ pub async fn project_edit( )); } - let side_type_id = - database::models::SideTypeId::get_id(new_side, &mut *transaction) - .await? - .expect("No database entry found for side type"); + let side_type_id = database::models::SideTypeId::get_id( + new_side, + &mut *transaction, + ) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -627,10 +655,12 @@ pub async fn project_edit( )); } - let side_type_id = - database::models::SideTypeId::get_id(new_side, &mut *transaction) - .await? - .expect("No database entry found for side type"); + let side_type_id = database::models::SideTypeId::get_id( + new_side, + &mut *transaction, + ) + .await? + .expect("No database entry found for side type"); sqlx::query!( " @@ -653,10 +683,12 @@ pub async fn project_edit( )); } - let license_id = - database::models::categories::License::get_id(license, &mut *transaction) - .await? - .expect("No database entry found for license"); + let license_id = database::models::categories::License::get_id( + license, + &mut *transaction, + ) + .await? + .expect("No database entry found for license"); sqlx::query!( " @@ -690,17 +722,18 @@ pub async fn project_edit( .await?; for donation in donations { - let platform_id = database::models::DonationPlatformId::get_id( - &donation.id, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError(format!( - "Platform {} does not exist.", - donation.id.clone() - )) - })?; + let platform_id = + database::models::DonationPlatformId::get_id( + &donation.id, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError(format!( + "Platform {} does not exist.", + donation.id.clone() + )) + })?; sqlx::query!( " @@ -717,7 +750,9 @@ pub async fn project_edit( } if let Some(moderation_message) = &new_project.moderation_message { - if !user.role.is_mod() && project_item.status != ProjectStatus::Approved { + if !user.role.is_mod() + && project_item.status != ProjectStatus::Approved + { return Err(ApiError::CustomAuthenticationError( "You do not have the permissions to edit the moderation message of this project!" .to_string(), @@ -737,8 +772,12 @@ pub async fn project_edit( .await?; } - if let Some(moderation_message_body) = &new_project.moderation_message_body { - if !user.role.is_mod() && project_item.status != ProjectStatus::Approved { + if let Some(moderation_message_body) = + &new_project.moderation_message_body + { + if !user.role.is_mod() + && project_item.status != ProjectStatus::Approved + { return Err(ApiError::CustomAuthenticationError( "You do not have the permissions to edit the moderation message body of this project!" .to_string(), @@ -805,17 +844,24 @@ 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 = dotenv::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; let project_item = - database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -826,12 +872,15 @@ pub async fn project_icon_edit( .await .map_err(ApiError::DatabaseError)? .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) })?; if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's icon.".to_string(), + "You don't have permission to edit this project's icon." + .to_string(), )); } } @@ -844,8 +893,12 @@ 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 hash = sha1::Sha1::from(&bytes).hexdigest(); let project_id: ProjectId = project_item.id.into(); let upload_data = file_host @@ -891,12 +944,16 @@ 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.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -907,12 +964,15 @@ pub async fn delete_project_icon( .await .map_err(ApiError::DatabaseError)? .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) })?; if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's icon.".to_string(), + "You don't have permission to edit this project's icon." + .to_string(), )); } } @@ -962,20 +1022,28 @@ 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::ValidationError(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::ValidationError(validation_errors_to_string(err, None)) + })?; let cdn_url = dotenv::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; let string = info.into_inner().0; let project_item = - database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -986,12 +1054,15 @@ pub async fn add_gallery_item( .await .map_err(ApiError::DatabaseError)? .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) })?; if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's gallery.".to_string(), + "You don't have permission to edit this project's gallery." + .to_string(), )); } } @@ -1079,15 +1150,20 @@ 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::ValidationError(validation_errors_to_string(err, None)))?; + item.validate().map_err(|err| { + ApiError::ValidationError(validation_errors_to_string(err, None)) + })?; - let project_item = - database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -1098,12 +1174,15 @@ pub async fn edit_gallery_item( .await .map_err(ApiError::DatabaseError)? .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) })?; if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's gallery.".to_string(), + "You don't have permission to edit this project's gallery." + .to_string(), )); } } @@ -1203,12 +1282,16 @@ 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.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + let project_item = database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { let team_member = database::models::TeamMember::get_from_user_id( @@ -1219,12 +1302,15 @@ pub async fn delete_gallery_item( .await .map_err(ApiError::DatabaseError)? .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) })?; if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's gallery.".to_string(), + "You don't have permission to edit this project's gallery." + .to_string(), )); } } @@ -1280,23 +1366,31 @@ 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.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("The specified project does not exist!".to_string()) - })?; + let project = database::models::Project::get_from_slug_or_project_id( + string.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !user.role.is_mod() { - let team_member = database::models::TeamMember::get_from_user_id_project( - project.id, - user.id.into(), - &**pool, - ) - .await - .map_err(ApiError::DatabaseError)? - .ok_or_else(|| { - ApiError::InvalidInputError("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::DatabaseError)? + .ok_or_else(|| { + ApiError::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; if !team_member .permissions @@ -1310,7 +1404,9 @@ 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?; @@ -1332,11 +1428,14 @@ 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::InvalidInputError("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::InvalidInputError( + "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; @@ -1397,11 +1496,14 @@ 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::InvalidInputError("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::InvalidInputError( + "The specified project does not exist!".to_string(), + ) + })?; let user_id: database::models::ids::UserId = user.id.into(); let project_id = result.id; @@ -1457,9 +1559,11 @@ pub async fn delete_from_index( id: crate::models::projects::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: Vec = client.get_indexes().await?; + let indexes: Vec = + client.get_indexes().await?; for index in indexes { index.delete_document(format!("{}", id)).await?; } diff --git a/src/routes/reports.rs b/src/routes/reports.rs index b8cb8e9b..6f884459 100644 --- a/src/routes/reports.rs +++ b/src/routes/reports.rs @@ -1,7 +1,9 @@ use crate::models::ids::{ProjectId, UserId, VersionId}; use crate::models::reports::{ItemType, Report}; 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::StreamExt; use serde::Deserialize; @@ -23,24 +25,31 @@ 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::InvalidInputError("Error while parsing request payload!".to_string()) + ApiError::InvalidInputError( + "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::InvalidInputError(format!("Invalid report type: {}", new_report.report_type)) + ApiError::InvalidInputError(format!( + "Invalid report type: {}", + new_report.report_type + )) })?; let mut report = crate::database::models::report_item::Report { id, @@ -56,17 +65,29 @@ pub async fn report_create( match new_report.item_type { ItemType::Project => { report.project_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::Version => { report.version_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::User => { report.user_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::Unknown => { @@ -127,8 +148,10 @@ pub async fn reports( .try_collect::>() .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(); diff --git a/src/routes/tags.rs b/src/routes/tags.rs index 1d7747dc..b1319e16 100644 --- a/src/routes/tags.rs +++ b/src/routes/tags.rs @@ -1,6 +1,8 @@ use super::ApiError; use crate::database::models; -use crate::database::models::categories::{DonationPlatform, License, ProjectType, ReportType}; +use crate::database::models::categories::{ + DonationPlatform, License, ProjectType, ReportType, +}; use crate::util::auth::check_is_admin_from_headers; use actix_web::{delete, get, put, web, HttpRequest, HttpResponse}; use models::categories::{Category, GameVersion, Loader}; @@ -40,7 +42,9 @@ 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 mut results = Category::list(&**pool) .await? .into_iter() @@ -64,12 +68,16 @@ pub async fn category_create( ) -> Result { check_is_admin_from_headers(req.headers(), &**pool).await?; - let project_type = - crate::database::models::ProjectTypeId::get_id(new_category.project_type.clone(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("Specified project type does not exist!".to_string()) - })?; + let project_type = crate::database::models::ProjectTypeId::get_id( + new_category.project_type.clone(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "Specified project type does not exist!".to_string(), + ) + })?; let _id = Category::builder() .name(&new_category.name)? @@ -90,7 +98,8 @@ pub async fn category_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = category.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = Category::remove(&name, &mut transaction).await?; @@ -114,7 +123,9 @@ 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() @@ -140,13 +151,18 @@ pub async fn loader_create( let mut transaction = pool.begin().await?; - let project_types = - ProjectType::get_many_id(&new_loader.supported_project_types, &mut *transaction).await?; + let project_types = ProjectType::get_many_id( + &new_loader.supported_project_types, + &mut *transaction, + ) + .await?; let _id = Loader::builder() .name(&new_loader.name)? .icon(&new_loader.icon)? - .supported_project_types(&*project_types.into_iter().map(|x| x.id).collect::>())? + .supported_project_types( + &*project_types.into_iter().map(|x| x.id).collect::>(), + )? .insert(&mut transaction) .await?; @@ -164,7 +180,8 @@ pub async fn loader_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = loader.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = Loader::remove(&name, &mut transaction).await?; @@ -200,8 +217,11 @@ 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? } @@ -260,7 +280,8 @@ pub async fn game_version_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = game_version.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = GameVersion::remove(&name, &mut transaction).await?; @@ -283,7 +304,9 @@ pub struct LicenseQueryData { } #[get("license")] -pub async fn license_list(pool: web::Data) -> Result { +pub async fn license_list( + pool: web::Data, +) -> Result { let results: Vec = License::list(&**pool) .await? .into_iter() @@ -329,7 +352,8 @@ pub async fn license_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = license.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = License::remove(&name, &mut transaction).await?; @@ -352,15 +376,18 @@ 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)) } @@ -398,7 +425,8 @@ pub async fn donation_platform_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = loader.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = DonationPlatform::remove(&name, &mut transaction).await?; @@ -415,7 +443,9 @@ pub async fn donation_platform_delete( } #[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)) } @@ -444,7 +474,8 @@ pub async fn report_type_delete( check_is_admin_from_headers(req.headers(), &**pool).await?; let name = report_type.into_inner().0; - let mut transaction = pool.begin().await.map_err(models::DatabaseError::from)?; + let mut transaction = + pool.begin().await.map_err(models::DatabaseError::from)?; let result = ReportType::remove(&name, &mut transaction).await?; diff --git a/src/routes/teams.rs b/src/routes/teams.rs index c6a5fb6b..3ca335a5 100644 --- a/src/routes/teams.rs +++ b/src/routes/teams.rs @@ -1,4 +1,6 @@ -use crate::database::models::notification_item::{NotificationActionBuilder, NotificationBuilder}; +use crate::database::models::notification_item::{ + NotificationActionBuilder, NotificationBuilder, +}; use crate::database::models::TeamMember; use crate::models::ids::ProjectId; use crate::models::teams::{Permissions, TeamId}; @@ -17,23 +19,33 @@ 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::DatabaseError)?; + let team_member = TeamMember::get_from_user_id( + project.team_id, + user.id.into(), + &**pool, + ) + .await + .map_err(ApiError::DatabaseError)?; 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)); @@ -59,14 +71,16 @@ 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) = current_user { - let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool) - .await - .map_err(ApiError::DatabaseError)?; + let team_member = + TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool) + .await + .map_err(ApiError::DatabaseError)?; if team_member.is_some() { let team_members: Vec<_> = members_data @@ -96,8 +110,12 @@ 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 { @@ -153,17 +171,20 @@ 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::CustomAuthenticationError( - "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::CustomAuthenticationError( + "You don't have permission to edit members of this team" + .to_string(), + ) + })?; if !member.permissions.contains(Permissions::MANAGE_INVITES) { return Err(ApiError::CustomAuthenticationError( - "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) { @@ -191,16 +212,23 @@ pub async fn add_team_member( )); } else { return Err(ApiError::InvalidInputError( - "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::InvalidInputError("An invalid User ID specified".to_string()))?; + .ok_or_else(|| { + ApiError::InvalidInputError( + "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, @@ -232,11 +260,18 @@ pub async fn add_team_member( "Team invite from {} to join the team for project {}", current_user.username, result.title ), - link: format!("/{}/{}", result.project_type, ProjectId(result.id as u64)), + link: format!( + "/{}/{}", + result.project_type, + ProjectId(result.id as u64) + ), actions: vec![ NotificationActionBuilder { title: "Accept".to_string(), - action_route: ("POST".to_string(), format!("team/{}/join", team)), + action_route: ( + "POST".to_string(), + format!("team/{}/join", team), + ), }, NotificationActionBuilder { title: "Deny".to_string(), @@ -273,20 +308,24 @@ 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::CustomAuthenticationError( - "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::CustomAuthenticationError( - "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::CustomAuthenticationError( + "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::CustomAuthenticationError( + "You don't have permission to edit members of this team" + .to_string(), + ) + })?; let mut transaction = pool.begin().await?; @@ -298,14 +337,16 @@ pub async fn edit_team_member( if !member.permissions.contains(Permissions::EDIT_MEMBER) { return Err(ApiError::CustomAuthenticationError( - "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::InvalidInputError( - "The new permissions have permissions that you don't have".to_string(), + "The new permissions have permissions that you don't have" + .to_string(), )); } } @@ -346,22 +387,34 @@ pub async fn transfer_ownership( let id = info.into_inner().0; let current_user = get_user_from_headers(req.headers(), &**pool).await?; - let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool) - .await? - .ok_or_else(|| { - ApiError::CustomAuthenticationError( - "You don't have permission to edit members 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::InvalidInputError("The new owner specified does not exist".to_string()) - })?; + let member = TeamMember::get_from_user_id( + id.into(), + current_user.id.into(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::CustomAuthenticationError( + "You don't have permission to edit members 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::InvalidInputError( + "The new owner specified does not exist".to_string(), + ) + })?; if member.role != crate::models::teams::OWNER_ROLE { return Err(ApiError::CustomAuthenticationError( - "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(), )); } @@ -409,15 +462,18 @@ 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::CustomAuthenticationError( - "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 member = + TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) + .await? + .ok_or_else(|| { + ApiError::CustomAuthenticationError( + "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?; if let Some(delete_member) = delete_member { if delete_member.role == crate::models::teams::OWNER_ROLE { @@ -431,7 +487,8 @@ 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, &**pool).await?; } else { @@ -440,7 +497,8 @@ 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 @@ -448,7 +506,8 @@ pub async fn remove_team_member( TeamMember::delete(id, user_id, &**pool).await?; } else { return Err(ApiError::CustomAuthenticationError( - "You do not have permission to cancel a team invite".to_string(), + "You do not have permission to cancel a team invite" + .to_string(), )); } Ok(HttpResponse::NoContent().body("")) diff --git a/src/routes/updates.rs b/src/routes/updates.rs index 40b947b6..f4bd3803 100644 --- a/src/routes/updates.rs +++ b/src/routes/updates.rs @@ -20,9 +20,11 @@ pub async fn forge_updates( let (id,) = info.into_inner(); - let project = database::models::Project::get_full_from_slug_or_project_id(&id, &**pool) - .await? - .ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?; + let project = database::models::Project::get_full_from_slug_or_project_id( + &id, &**pool, + ) + .await? + .ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); @@ -38,7 +40,8 @@ pub async fn forge_updates( ) .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?; versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); #[derive(Serialize)] @@ -48,7 +51,11 @@ pub async fn forge_updates( } let mut response = ForgeUpdates { - homepage: format!("{}/mod/{}", dotenv::var("SITE_URL").unwrap_or_default(), id), + homepage: format!( + "{}/mod/{}", + dotenv::var("SITE_URL").unwrap_or_default(), + id + ), promos: HashMap::new(), }; diff --git a/src/routes/users.rs b/src/routes/users.rs index 81bce74e..bb7c49f8 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -20,8 +20,10 @@ pub async fn user_auth_get( req: HttpRequest, pool: web::Data, ) -> Result { - Ok(HttpResponse::Ok() - .json(get_user_from_headers(req.headers(), &mut *pool.acquire().await?).await?)) + Ok(HttpResponse::Ok().json( + get_user_from_headers(req.headers(), &mut *pool.acquire().await?) + .await?, + )) } #[derive(Serialize, Deserialize)] @@ -41,7 +43,8 @@ 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)) } @@ -52,7 +55,8 @@ 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; @@ -82,9 +86,11 @@ pub async fn projects_list( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await.ok(); - let id_option = - crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) - .await?; + let id_option = crate::database::models::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(); @@ -93,17 +99,24 @@ pub async fn projects_list( if current_user.role.is_mod() || current_user.id == user_id { User::get_projects_private(id, &**pool).await? } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await? + User::get_projects( + id, + ProjectStatus::Approved.as_str(), + &**pool, + ) + .await? } } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await? + User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool) + .await? }; - let response: Vec<_> = crate::database::Project::get_many_full(project_data, &**pool) - .await? - .into_iter() - .map(Project::from) - .collect(); + let response: Vec<_> = + crate::database::Project::get_many_full(project_data, &**pool) + .await? + .into_iter() + .map(Project::from) + .collect(); Ok(HttpResponse::Ok().json(response)) } else { @@ -152,13 +165,15 @@ pub async fn user_edit( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; - new_user - .validate() - .map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?; + new_user.validate().map_err(|err| { + ApiError::ValidationError(validation_errors_to_string(err, None)) + })?; - let id_option = - crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) - .await?; + let id_option = crate::database::models::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(); @@ -168,8 +183,10 @@ pub async fn user_edit( if let Some(username) = &new_user.username { let user_option = - crate::database::models::User::get_id_from_username_or_id(username, &**pool) - .await?; + crate::database::models::User::get_id_from_username_or_id( + username, &**pool, + ) + .await?; if user_option.is_none() { sqlx::query!( @@ -282,19 +299,23 @@ 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 = dotenv::var("CDN_URL")?; 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 = + crate::database::models::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::CustomAuthenticationError( - "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(), )); } @@ -322,8 +343,12 @@ 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 upload_data = file_host .upload_file( @@ -374,9 +399,11 @@ pub async fn user_delete( removal_type: web::Query, ) -> 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 = crate::database::models::User::get_id_from_username_or_id( + &*info.into_inner().0, + &**pool, + ) + .await?; if let Some(id) = id_option { if !user.role.is_mod() && user.id != id.into() { @@ -389,9 +416,15 @@ pub async fn user_delete( let result; if &*removal_type.removal_type == "full" { - result = crate::database::models::User::remove_full(id, &mut transaction).await?; + result = crate::database::models::User::remove_full( + id, + &mut transaction, + ) + .await?; } else { - result = crate::database::models::User::remove(id, &mut transaction).await?; + result = + crate::database::models::User::remove(id, &mut transaction) + .await?; }; transaction.commit().await?; @@ -413,9 +446,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id( + &*info.into_inner().0, + &**pool, + ) + .await?; if let Some(id) = id_option { if !user.role.is_mod() && user.id != id.into() { @@ -441,11 +476,12 @@ 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 { @@ -460,9 +496,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id( + &*info.into_inner().0, + &**pool, + ) + .await?; if let Some(id) = id_option { if !user.role.is_mod() && user.id != id.into() { diff --git a/src/routes/v1/moderation.rs b/src/routes/v1/moderation.rs index 30bc8bb6..8557a397 100644 --- a/src/routes/v1/moderation.rs +++ b/src/routes/v1/moderation.rs @@ -30,15 +30,18 @@ pub async fn get_mods( 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(Project::from) - .collect(); + let projects: Vec<_> = + database::Project::get_many_full(project_ids, &**pool) + .await? + .into_iter() + .map(Project::from) + .collect(); Ok(HttpResponse::Ok().json(projects)) } diff --git a/src/routes/v1/mods.rs b/src/routes/v1/mods.rs index 20e2ee9a..6f1c409a 100644 --- a/src/routes/v1/mods.rs +++ b/src/routes/v1/mods.rs @@ -1,6 +1,8 @@ use crate::file_hosting::FileHost; use crate::models::projects::SearchRequest; -use crate::routes::project_creation::{project_create_inner, undo_uploads, CreateError}; +use crate::routes::project_creation::{ + project_create_inner, undo_uploads, CreateError, +}; use crate::routes::projects::ProjectIds; use crate::routes::ApiError; use crate::search::{search_for_project, SearchConfig, SearchError}; @@ -89,12 +91,14 @@ pub async fn mods_get( ids: web::Query, pool: web::Data, ) -> Result { - let project_ids = serde_json::from_str::>(&*ids.ids)? - .into_iter() - .map(|x| x.into()) - .collect(); + let project_ids = + serde_json::from_str::>(&*ids.ids)? + .into_iter() + .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(); diff --git a/src/routes/v1/reports.rs b/src/routes/v1/reports.rs index f9bcd0a7..6d71a7bc 100644 --- a/src/routes/v1/reports.rs +++ b/src/routes/v1/reports.rs @@ -2,7 +2,9 @@ use crate::models::ids::ReportId; use crate::models::projects::{ProjectId, VersionId}; use crate::models::users::UserId; 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::web; use actix_web::{get, post, HttpRequest, HttpResponse}; use chrono::{DateTime, Utc}; @@ -56,24 +58,31 @@ 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::InvalidInputError("Error while parsing request payload!".to_string()) + ApiError::InvalidInputError( + "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::InvalidInputError(format!("Invalid report type: {}", new_report.report_type)) + ApiError::InvalidInputError(format!( + "Invalid report type: {}", + new_report.report_type + )) })?; let mut report = crate::database::models::report_item::Report { id, @@ -89,17 +98,29 @@ pub async fn report_create( match new_report.item_type { ItemType::Mod => { report.project_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::Version => { report.version_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::User => { report.user_id = Some( - serde_json::from_str::(&*format!("\"{}\"", new_report.item_id))?.into(), + serde_json::from_str::(&*format!( + "\"{}\"", + new_report.item_id + ))? + .into(), ) } ItemType::Unknown => { @@ -160,8 +181,10 @@ pub async fn reports( .try_collect::>() .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(); diff --git a/src/routes/v1/tags.rs b/src/routes/v1/tags.rs index a447c572..1f31fe66 100644 --- a/src/routes/v1/tags.rs +++ b/src/routes/v1/tags.rs @@ -1,4 +1,6 @@ -use crate::database::models::categories::{Category, GameVersion, Loader, ProjectType}; +use crate::database::models::categories::{ + Category, GameVersion, Loader, ProjectType, +}; use crate::routes::ApiError; use crate::util::auth::check_is_admin_from_headers; use actix_web::{get, put, web}; @@ -8,7 +10,9 @@ use sqlx::PgPool; const DEFAULT_ICON: &str = r#""#; #[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() @@ -28,11 +32,16 @@ pub async fn category_create( let name = category.into_inner().0; - let project_type = crate::database::models::ProjectTypeId::get_id("mod".to_string(), &**pool) - .await? - .ok_or_else(|| { - ApiError::InvalidInputError("Specified project type does not exist!".to_string()) - })?; + let project_type = crate::database::models::ProjectTypeId::get_id( + "mod".to_string(), + &**pool, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError( + "Specified project type does not exist!".to_string(), + ) + })?; let _id = Category::builder() .name(&name)? @@ -45,7 +54,9 @@ pub async fn category_create( } #[get("loader")] -pub async fn loader_list(pool: web::Data) -> Result { +pub async fn loader_list( + pool: web::Data, +) -> Result { let results = Loader::list(&**pool) .await? .into_iter() @@ -67,12 +78,16 @@ pub async fn loader_create( let name = loader.into_inner().0; let mut transaction = pool.begin().await?; - let project_types = ProjectType::get_many_id(&["mod".to_string()], &mut *transaction).await?; + let project_types = + ProjectType::get_many_id(&["mod".to_string()], &mut *transaction) + .await?; let _id = Loader::builder() .name(&name)? .icon(DEFAULT_ICON)? - .supported_project_types(&*project_types.into_iter().map(|x| x.id).collect::>())? + .supported_project_types( + &*project_types.into_iter().map(|x| x.id).collect::>(), + )? .insert(&mut transaction) .await?; @@ -92,11 +107,15 @@ pub async fn game_version_list( query: web::Query, ) -> Result { if query.type_.is_some() || query.major.is_some() { - let results = GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool) - .await? - .into_iter() - .map(|x| x.version) - .collect::>(); + let results = GameVersion::list_filter( + query.type_.as_deref(), + query.major, + &**pool, + ) + .await? + .into_iter() + .map(|x| x.version) + .collect::>(); Ok(HttpResponse::Ok().json(results)) } else { let results = GameVersion::list(&**pool) diff --git a/src/routes/v1/teams.rs b/src/routes/v1/teams.rs index fe125d7b..d4d6a28a 100644 --- a/src/routes/v1/teams.rs +++ b/src/routes/v1/teams.rs @@ -29,18 +29,20 @@ pub async fn team_members_get( ) -> Result { let id = info.into_inner().0; let members_data = - crate::database::models::TeamMember::get_from_team(id.into(), &**pool).await?; + crate::database::models::TeamMember::get_from_team(id.into(), &**pool) + .await?; let current_user = get_user_from_headers(req.headers(), &**pool).await.ok(); if let Some(user) = current_user { - let team_member = crate::database::models::TeamMember::get_from_user_id( - id.into(), - user.id.into(), - &**pool, - ) - .await - .map_err(ApiError::DatabaseError)?; + let team_member = + crate::database::models::TeamMember::get_from_user_id( + id.into(), + user.id.into(), + &**pool, + ) + .await + .map_err(ApiError::DatabaseError)?; if team_member.is_some() { let team_members: Vec = members_data diff --git a/src/routes/v1/users.rs b/src/routes/v1/users.rs index ad09066c..523f153f 100644 --- a/src/routes/v1/users.rs +++ b/src/routes/v1/users.rs @@ -15,9 +15,11 @@ pub async fn mods_list( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await.ok(); - let id_option = - crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) - .await?; + let id_option = crate::database::models::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(); @@ -26,10 +28,16 @@ pub async fn mods_list( if current_user.role.is_mod() || current_user.id == user_id { User::get_projects_private(id, &**pool).await? } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await? + User::get_projects( + id, + ProjectStatus::Approved.as_str(), + &**pool, + ) + .await? } } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await? + User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool) + .await? }; let response = project_data @@ -50,9 +58,11 @@ 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 = crate::database::models::User::get_id_from_username_or_id( + &*info.into_inner().0, + &**pool, + ) + .await?; if let Some(id) = id_option { if !user.role.is_mod() && user.id != id.into() { @@ -71,7 +81,9 @@ pub async fn user_follows( id as crate::database::models::ids::UserId, ) .fetch_many(&**pool) - .try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.mod_id as u64))) }) + .try_filter_map(|e| async { + Ok(e.right().map(|m| ProjectId(m.mod_id as u64))) + }) .try_collect::>() .await?; diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index 545d0514..b9a29ecd 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -1,9 +1,12 @@ use crate::database::models; use crate::database::models::notification_item::NotificationBuilder; -use crate::database::models::version_item::{VersionBuilder, VersionFileBuilder}; +use crate::database::models::version_item::{ + VersionBuilder, VersionFileBuilder, +}; use crate::file_hosting::FileHost; use crate::models::projects::{ - Dependency, GameVersion, Loader, ProjectId, Version, VersionFile, VersionId, VersionType, + Dependency, GameVersion, Loader, ProjectId, Version, VersionFile, + VersionId, VersionType, }; use crate::models::teams::Permissions; use crate::routes::project_creation::{CreateError, UploadedFile}; @@ -74,8 +77,11 @@ 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; if let Err(e) = undo_result { @@ -103,25 +109,30 @@ 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?; while let Some(item) = payload.next().await { let mut field: Field = item.map_err(CreateError::MultipartError)?; let content_disposition = field.content_disposition().clone(); - let name = content_disposition - .get_name() - .ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?; + let name = content_disposition.get_name().ok_or_else(|| { + CreateError::MissingValueError("Missing content name".to_string()) + })?; if name == "data" { 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 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(); if version_create_data.project_id.is_none() { @@ -131,10 +142,13 @@ 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, + )) })?; - 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!( @@ -162,7 +176,8 @@ async fn version_create_inner( if results.exists.unwrap_or(true) { return Err(CreateError::InvalidInput( - "A version with that version_number already exists".to_string(), + "A version with that version_number already exists" + .to_string(), )); } @@ -176,7 +191,8 @@ 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(), ) })?; @@ -185,11 +201,13 @@ 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!( " @@ -210,7 +228,9 @@ 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>>()?; @@ -222,7 +242,9 @@ async fn version_create_inner( all_loaders .iter() .find(|y| { - y.loader == x.0 && y.supported_project_types.contains(&project_type) + y.loader == x.0 + && y.supported_project_types + .contains(&project_type) }) .ok_or_else(|| CreateError::InvalidLoader(x.0.clone())) .map(|y| y.id) @@ -261,7 +283,9 @@ 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!( @@ -276,9 +300,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, @@ -300,10 +324,12 @@ async fn version_create_inner( .await?; } - 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( @@ -433,8 +459,11 @@ 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; if let Err(e) = undo_result { @@ -477,21 +506,26 @@ async fn upload_file_to_version_inner( } }; - let team_member = - models::TeamMember::get_from_user_id_version(version_id, user.id.into(), &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::CustomAuthenticationError( - "You don't have permission to upload files to this version!".to_string(), - ) - })?; + let team_member = models::TeamMember::get_from_user_id_version( + version_id, + user.id.into(), + &mut *transaction, + ) + .await? + .ok_or_else(|| { + CreateError::CustomAuthenticationError( + "You don't have permission to upload files to this version!" + .to_string(), + ) + })?; if !team_member .permissions .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(), )); } @@ -510,19 +544,22 @@ 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?; while let Some(item) = payload.next().await { let mut field: Field = item.map_err(CreateError::MultipartError)?; let content_disposition = field.content_disposition().clone(); - let name = content_disposition - .get_name() - .ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?; + let name = content_disposition.get_name().ok_or_else(|| { + CreateError::MissingValueError("Missing content name".to_string()) + })?; if name == "data" { 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 file_data: InitialFileData = serde_json::from_slice(&data)?; // TODO: currently no data here, but still required @@ -532,7 +569,9 @@ 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", + )) })?; upload_file( @@ -596,7 +635,9 @@ pub async fn upload_file( let (file_name, file_extension) = get_name_ext(content_disposition)?; 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, 100 * (1 << 20), @@ -619,7 +660,8 @@ 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(), )); } @@ -683,9 +725,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/version_file.rs b/src/routes/version_file.rs index fe191152..e92793fd 100644 --- a/src/routes/version_file.rs +++ b/src/routes/version_file.rs @@ -128,25 +128,28 @@ pub async fn delete_file( if let Some(row) = result { if !user.role.is_mod() { - 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::DatabaseError)? - .ok_or_else(|| { - ApiError::CustomAuthenticationError( - "You don't have permission to delete this file!".to_string(), + 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::DatabaseError)? + .ok_or_else(|| { + ApiError::CustomAuthenticationError( + "You don't have permission to delete this file!" + .to_string(), + ) + })?; if !team_member .permissions .contains(Permissions::DELETE_VERSION) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to delete this file!".to_string(), + "You don't have permission to delete this file!" + .to_string(), )); } } @@ -167,7 +170,8 @@ pub async fn delete_file( if files.len() < 2 { return Err(ApiError::InvalidInputError( - "Versions must have at least one file uploaded to them".to_string(), + "Versions must have at least one file uploaded to them" + .to_string(), )); } @@ -269,7 +273,9 @@ pub async fn get_update_from_hash( .await?; if let Some(version_id) = version_ids.last() { - 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 { @@ -436,12 +442,15 @@ pub async fn update_files( } } - 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 response = HashMap::new(); for row in &result { - if let Some(version) = versions.iter().find(|x| x.id.0 == row.version_id) { + if let Some(version) = + versions.iter().find(|x| x.id.0 == row.version_id) + { response.insert( hex::encode(&row.hash), models::projects::Version::from(version.clone()), diff --git a/src/scheduler.rs b/src/scheduler.rs index ef16ca98..34dc9ae4 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -18,8 +18,8 @@ impl Scheduler { F: FnMut() -> R + Send + 'static, R: std::future::Future + Send + 'static, { - let future = - IntervalStream::new(time::interval(interval)).for_each_concurrent(2, move |_| task()); + let future = IntervalStream::new(time::interval(interval)) + .for_each_concurrent(2, move |_| task()); self.arbiter.spawn(future); } @@ -38,8 +38,9 @@ pub fn schedule_versions( pool: sqlx::Pool, skip_initial: bool, ) { - let version_index_interval = - std::time::Duration::from_secs(parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800)); + let version_index_interval = std::time::Duration::from_secs( + parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800), + ); let mut skip = skip_initial; scheduler.run(version_index_interval, move || { @@ -90,11 +91,15 @@ struct VersionFormat<'a> { release_time: chrono::DateTime, } -async fn update_versions(pool: &sqlx::Pool) -> Result<(), VersionIndexingError> { - let input = reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json") - .await? - .json::() - .await?; +async fn update_versions( + pool: &sqlx::Pool, +) -> Result<(), VersionIndexingError> { + let input = reqwest::get( + "https://launchermeta.mojang.com/mc/game/version_manifest.json", + ) + .await? + .json::() + .await?; let mut skipped_versions_count = 0u32; @@ -156,7 +161,8 @@ async fn update_versions(pool: &sqlx::Pool) -> Result<(), Versio .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 4475904c..1143564b 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -7,7 +7,9 @@ use crate::search::UploadSearchProject; use sqlx::postgres::PgPool; // TODO: Move this away from STRING_AGG to multiple queries - however this may be more efficient? -pub async fn index_local(pool: PgPool) -> Result, IndexingError> { +pub async fn index_local( + pool: PgPool, +) -> Result, IndexingError> { info!("Indexing local projects!"); Ok( sqlx::query!( diff --git a/src/search/indexing/mod.rs b/src/search/indexing/mod.rs index af5828b4..3bacaaba 100644 --- a/src/search/indexing/mod.rs +++ b/src/search/indexing/mod.rs @@ -88,15 +88,25 @@ async fn update_index_helper<'a>( .await } -pub async fn reconfigure_indices(config: &SearchConfig) -> Result<(), IndexingError> { +pub async fn reconfigure_indices( + config: &SearchConfig, +) -> Result<(), IndexingError> { let client = config.make_client(); // Relevance Index - update_index_helper(&client, "relevance_projects", "desc(downloads)").await?; - update_index_helper(&client, "downloads_projects", "desc(downloads)").await?; + update_index_helper(&client, "relevance_projects", "desc(downloads)") + .await?; + update_index_helper(&client, "downloads_projects", "desc(downloads)") + .await?; update_index_helper(&client, "follows_projects", "desc(follows)").await?; - update_index_helper(&client, "updated_projects", "desc(modified_timestamp)").await?; - update_index_helper(&client, "newest_projects", "desc(created_timestamp)").await?; + update_index_helper( + &client, + "updated_projects", + "desc(modified_timestamp)", + ) + .await?; + update_index_helper(&client, "newest_projects", "desc(created_timestamp)") + .await?; Ok(()) } @@ -150,7 +160,10 @@ async fn create_index<'a>( } } -async fn add_to_index(index: Index<'_>, mods: &[UploadSearchProject]) -> Result<(), IndexingError> { +async fn add_to_index( + index: Index<'_>, + mods: &[UploadSearchProject], +) -> Result<(), IndexingError> { for chunk in mods.chunks(MEILISEARCH_CHUNK_SIZE) { index.add_documents(chunk, Some("project_id")).await?; } @@ -179,9 +192,27 @@ pub async fn add_projects( ) -> Result<(), IndexingError> { let client = config.make_client(); - create_and_add_to_index(&client, &projects, "relevance_projects", "desc(downloads)").await?; - create_and_add_to_index(&client, &projects, "downloads_projects", "desc(downloads)").await?; - create_and_add_to_index(&client, &projects, "follows_projects", "desc(follows)").await?; + create_and_add_to_index( + &client, + &projects, + "relevance_projects", + "desc(downloads)", + ) + .await?; + create_and_add_to_index( + &client, + &projects, + "downloads_projects", + "desc(downloads)", + ) + .await?; + create_and_add_to_index( + &client, + &projects, + "follows_projects", + "desc(follows)", + ) + .await?; create_and_add_to_index( &client, &projects, diff --git a/src/search/indexing/queue.rs b/src/search/indexing/queue.rs index 05f34634..5db19eed 100644 --- a/src/search/indexing/queue.rs +++ b/src/search/indexing/queue.rs @@ -21,9 +21,15 @@ impl CreationQueue { self.queue.lock().unwrap().push(search_project); } pub fn take(&self) -> Vec { - std::mem::replace(&mut *self.queue.lock().unwrap(), Vec::with_capacity(10)) + std::mem::replace( + &mut *self.queue.lock().unwrap(), + Vec::with_capacity(10), + ) } - pub async fn index(&self, config: &SearchConfig) -> Result<(), IndexingError> { + pub async fn index( + &self, + config: &SearchConfig, + ) -> Result<(), IndexingError> { let queue = self.take(); add_projects(queue, config).await } diff --git a/src/search/mod.rs b/src/search/mod.rs index 20e7262d..a8914567 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -149,12 +149,13 @@ pub async fn search_for_project( ) -> Result { let client = Client::new(&*config.address, &*config.key); - let filters: Cow<_> = match (info.filters.as_deref(), info.version.as_deref()) { - (Some(f), Some(v)) => format!("({}) AND ({})", f, 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!("({}) AND ({})", f, v).into(), + (Some(f), None) => f.into(), + (None, Some(v)) => v.into(), + (None, None) => "".into(), + }; let offset = info.offset.as_deref().unwrap_or("0").parse()?; let index = info.index.as_deref().unwrap_or("relevance"); diff --git a/src/util/auth.rs b/src/util/auth.rs index 335aea4d..9b8042db 100644 --- a/src/util/auth.rs +++ b/src/util/auth.rs @@ -58,7 +58,8 @@ 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 { diff --git a/src/util/routes.rs b/src/util/routes.rs index e88da564..b785b5ab 100644 --- a/src/util/routes.rs +++ b/src/util/routes.rs @@ -18,7 +18,9 @@ pub async fn read_from_payload( return Err(ApiError::InvalidInputError(String::from(err_msg))); } else { bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInputError("Unable to parse bytes in payload sent!".to_string()) + ApiError::InvalidInputError( + "Unable to parse bytes in payload sent!".to_string(), + ) })?); } } @@ -35,13 +37,17 @@ pub async fn read_from_field( if bytes.len() >= cap { return Err(CreateError::InvalidInput(String::from(err_msg))); } else { - bytes.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); + bytes.extend_from_slice( + &chunk.map_err(CreateError::MultipartError)?, + ); } } 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 d5dbd3e7..a280cc19 100644 --- a/src/util/validate.rs +++ b/src/util/validate.rs @@ -4,11 +4,15 @@ 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(); @@ -19,13 +23,19 @@ pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option { - 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() { output.push_str(&*validation_errors_to_string( *errors.clone(), - Some(format!("of list {} with index {}", index, field)), + Some(format!( + "of list {} with index {}", + index, field + )), )); } diff --git a/src/validate/fabric.rs b/src/validate/fabric.rs index d9dd3232..1293c601 100644 --- a/src/validate/fabric.rs +++ b/src/validate/fabric.rs @@ -1,4 +1,6 @@ -use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; +use crate::validate::{ + SupportedGameVersions, ValidationError, ValidationResult, +}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; @@ -31,13 +33,14 @@ impl super::Validator for FabricValidator { archive: &mut ZipArchive>, ) -> Result { archive.by_name("fabric.mod.json").map_err(|_| { - ValidationError::InvalidInputError("No fabric.mod.json present for Fabric file.".into()) + ValidationError::InvalidInputError( + "No fabric.mod.json present for Fabric file.".into(), + ) })?; - 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 43333f7f..038edbe0 100644 --- a/src/validate/forge.rs +++ b/src/validate/forge.rs @@ -1,4 +1,6 @@ -use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; +use crate::validate::{ + SupportedGameVersions, ValidationError, ValidationResult, +}; use chrono::{DateTime, NaiveDateTime, Utc}; use std::io::Cursor; use zip::ZipArchive; @@ -31,7 +33,9 @@ impl super::Validator for ForgeValidator { archive: &mut ZipArchive>, ) -> Result { archive.by_name("META-INF/mods.toml").map_err(|_| { - ValidationError::InvalidInputError("No mods.toml present for Forge file.".into()) + ValidationError::InvalidInputError( + "No mods.toml present for Forge file.".into(), + ) })?; if !archive.file_names().any(|name| name.ends_with(".class")) { @@ -64,8 +68,14 @@ impl super::Validator for LegacyForgeValidator { fn get_supported_game_versions(&self) -> SupportedGameVersions { // Times between versions 1.5.2 to 1.12.2, which all use the legacy way of defining mods SupportedGameVersions::Range( - DateTime::from_utc(NaiveDateTime::from_timestamp(1366818300, 0), Utc), - DateTime::from_utc(NaiveDateTime::from_timestamp(1505810340, 0), Utc), + DateTime::from_utc( + NaiveDateTime::from_timestamp(1366818300, 0), + Utc, + ), + DateTime::from_utc( + NaiveDateTime::from_timestamp(1505810340, 0), + Utc, + ), ) } @@ -74,7 +84,9 @@ impl super::Validator for LegacyForgeValidator { archive: &mut ZipArchive>, ) -> Result { archive.by_name("mcmod.info").map_err(|_| { - ValidationError::InvalidInputError("No mcmod.info present for Forge file.".into()) + ValidationError::InvalidInputError( + "No mcmod.info present for Forge file.".into(), + ) })?; if !archive.file_names().any(|name| name.ends_with(".class")) { diff --git a/src/validate/mod.rs b/src/validate/mod.rs index 279ae22f..e83c50e9 100644 --- a/src/validate/mod.rs +++ b/src/validate/mod.rs @@ -114,20 +114,24 @@ 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.date > 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.date > before && x.date < 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.date > 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.date > before && x.date < after) + .unwrap_or(false) + }) + } SupportedGameVersions::Custom(versions) => { versions.iter().any(|x| game_versions.contains(x)) } diff --git a/src/validate/pack.rs b/src/validate/pack.rs index e89714f0..17cc9039 100644 --- a/src/validate/pack.rs +++ b/src/validate/pack.rs @@ -1,7 +1,9 @@ use crate::models::projects::SideType; use crate::util::env::parse_strings_from_var; use crate::util::validate::validation_errors_to_string; -use crate::validate::{SupportedGameVersions, ValidationError, ValidationResult}; +use crate::validate::{ + SupportedGameVersions, ValidationError, ValidationResult, +}; use serde::{Deserialize, Serialize}; use std::io::{Cursor, Read}; use std::path::Component; @@ -33,7 +35,9 @@ pub struct PackFile<'a> { pub downloads: Vec<&'a str>, } -fn validate_download_url(values: &[&str]) -> Result<(), validator::ValidationError> { +fn validate_download_url( + values: &[&str], +) -> Result<(), validator::ValidationError> { for value in values { let url = url::Url::parse(value) .ok() @@ -43,7 +47,8 @@ fn validate_download_url(values: &[&str]) -> Result<(), validator::ValidationErr 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"))? @@ -131,9 +136,12 @@ impl super::Validator for PackValidator { &self, archive: &mut ZipArchive>, ) -> Result { - let mut file = archive - .by_name("modrinth.index.json") - .map_err(|_| ValidationError::InvalidInputError("Pack manifest is missing.".into()))?; + let mut file = + archive.by_name("modrinth.index.json").map_err(|_| { + ValidationError::InvalidInputError( + "Pack manifest is missing.".into(), + ) + })?; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -141,7 +149,9 @@ impl super::Validator for PackValidator { let pack: PackFormat = serde_json::from_str(&contents)?; pack.validate().map_err(|err| { - ValidationError::InvalidInputError(validation_errors_to_string(err, None).into()) + ValidationError::InvalidInputError( + validation_errors_to_string(err, None).into(), + ) })?; if pack.game != "minecraft" { @@ -161,7 +171,9 @@ impl super::Validator for PackValidator { .components() .next() .ok_or_else(|| { - ValidationError::InvalidInputError("Invalid pack file path!".into()) + ValidationError::InvalidInputError( + "Invalid pack file path!".into(), + ) })?; match path {