Run fmt, fix dep route (#312)

This commit is contained in:
Geometrically 2022-02-27 21:44:00 -07:00 committed by GitHub
parent 725f8571bb
commit 459e36c027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1798 additions and 867 deletions

View File

@ -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": {

View File

@ -62,7 +62,10 @@ impl Category {
}
}
pub async fn get_id<'a, E>(name: &str, exec: E) -> Result<Option<CategoryId>, DatabaseError>
pub async fn get_id<'a, E>(
name: &str,
exec: E,
) -> Result<Option<CategoryId>, 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<String, DatabaseError>
pub async fn get_name<'a, E>(
id: CategoryId,
exec: E,
) -> Result<String, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
name: &str,
exec: E,
) -> Result<Option<()>, 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<CategoryBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<CategoryBuilder<'a>, 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<CategoryBuilder<'a>, DatabaseError> {
pub fn icon(
self,
icon: &'a str,
) -> Result<CategoryBuilder<'a>, DatabaseError> {
Ok(Self {
icon: Some(icon),
..self
})
}
pub async fn insert<'b, E>(self, exec: E) -> Result<CategoryId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<CategoryId, DatabaseError>
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<Option<LoaderId>, DatabaseError>
pub async fn get_id<'a, E>(
name: &str,
exec: E,
) -> Result<Option<LoaderId>, 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<String, DatabaseError>
pub async fn get_name<'a, E>(
id: LoaderId,
exec: E,
) -> Result<String, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
name: &str,
exec: E,
) -> Result<Option<()>, 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<LoaderBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<LoaderBuilder<'a>, 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<LoaderBuilder<'a>, DatabaseError> {
pub fn icon(
self,
icon: &'a str,
) -> Result<LoaderBuilder<'a>, 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<String, DatabaseError>
pub async fn get_name<'a, E>(
id: GameVersionId,
exec: E,
) -> Result<String, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
name: &str,
exec: E,
) -> Result<Option<()>, 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<GameVersionBuilder<'a>, DatabaseError> {
pub fn version(
self,
version: &'a str,
) -> Result<GameVersionBuilder<'a>, 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<chrono::Utc>) -> GameVersionBuilder<'a> {
pub fn created(
self,
created: &'a chrono::DateTime<chrono::Utc>,
) -> GameVersionBuilder<'a> {
Self {
date: Some(created),
..self
}
}
pub async fn insert<'b, E>(self, exec: E) -> Result<GameVersionId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<GameVersionId, DatabaseError>
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<Option<LicenseId>, DatabaseError>
pub async fn get_id<'a, E>(
id: &str,
exec: E,
) -> Result<Option<LicenseId>, 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<License, DatabaseError>
pub async fn get<'a, E>(
id: LicenseId,
exec: E,
) -> Result<License, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
short: &str,
exec: E,
) -> Result<Option<()>, 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<LicenseBuilder<'a>, DatabaseError> {
pub fn short(
self,
short: &'a str,
) -> Result<LicenseBuilder<'a>, 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<LicenseBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<LicenseBuilder<'a>, DatabaseError> {
Ok(Self {
name: Some(name),
..self
})
}
pub async fn insert<'b, E>(self, exec: E) -> Result<LicenseId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<LicenseId, DatabaseError>
where
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
{
@ -874,7 +940,9 @@ impl DonationPlatform {
})
}
pub async fn list<'a, E>(exec: E) -> Result<Vec<DonationPlatform>, DatabaseError>
pub async fn list<'a, E>(
exec: E,
) -> Result<Vec<DonationPlatform>, 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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
short: &str,
exec: E,
) -> Result<Option<()>, 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<DonationPlatformBuilder<'a>, DatabaseError> {
pub fn short(
self,
short: &'a str,
) -> Result<DonationPlatformBuilder<'a>, 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<DonationPlatformBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<DonationPlatformBuilder<'a>, DatabaseError> {
Ok(Self {
name: Some(name),
..self
})
}
pub async fn insert<'b, E>(self, exec: E) -> Result<DonationPlatformId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<DonationPlatformId, DatabaseError>
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<Option<ReportTypeId>, DatabaseError>
pub async fn get_id<'a, E>(
name: &str,
exec: E,
) -> Result<Option<ReportTypeId>, 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<String, DatabaseError>
pub async fn get_name<'a, E>(
id: ReportTypeId,
exec: E,
) -> Result<String, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
name: &str,
exec: E,
) -> Result<Option<()>, 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<ReportTypeBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<ReportTypeBuilder<'a>, 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<ReportTypeId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<ReportTypeId, DatabaseError>
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<Option<ProjectTypeId>, DatabaseError>
pub async fn get_id<'a, E>(
name: &str,
exec: E,
) -> Result<Option<ProjectTypeId>, 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<String, DatabaseError>
pub async fn get_name<'a, E>(
id: ProjectTypeId,
exec: E,
) -> Result<String, DatabaseError>
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<Option<()>, DatabaseError>
pub async fn remove<'a, E>(
name: &str,
exec: E,
) -> Result<Option<()>, 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<ProjectTypeBuilder<'a>, DatabaseError> {
pub fn name(
self,
name: &'a str,
) -> Result<ProjectTypeBuilder<'a>, 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<ProjectTypeId, DatabaseError>
pub async fn insert<'b, E>(
self,
exec: E,
) -> Result<ProjectTypeId, DatabaseError>
where
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
{

View File

@ -83,7 +83,10 @@ impl ids::SideTypeId {
}
impl ids::DonationPlatformId {
pub async fn get_id<'a, E>(id: &str, exec: E) -> Result<Option<Self>, DatabaseError>
pub async fn get_id<'a, E>(
id: &str,
exec: E,
) -> Result<Option<Self>, 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<Option<Self>, DatabaseError>
pub async fn get_id<'a, E>(
project_type: String,
exec: E,
) -> Result<Option<Self>, DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{

View File

@ -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<NotificationId>,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<()>, sqlx::error::Error> {
let notification_ids_parsed: Vec<i64> = notification_ids.into_iter().map(|x| x.0).collect();
let notification_ids_parsed: Vec<i64> =
notification_ids.into_iter().map(|x| x.0).collect();
sqlx::query!(
"

View File

@ -300,7 +300,8 @@ impl Project {
{
use futures::stream::TryStreamExt;
let project_ids_parsed: Vec<i64> = project_ids.into_iter().map(|x| x.0).collect();
let project_ids_parsed: Vec<i64> =
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)]

View File

@ -52,7 +52,10 @@ impl Report {
Ok(())
}
pub async fn get<'a, E>(id: ReportId, exec: E) -> Result<Option<QueryReport>, sqlx::Error>
pub async fn get<'a, E>(
id: ReportId,
exec: E,
) -> Result<Option<QueryReport>, 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<i64> = report_ids.into_iter().map(|x| x.0).collect();
let report_ids_parsed: Vec<i64> =
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<Option<()>, sqlx::Error>
pub async fn remove_full<'a, E>(
id: ReportId,
exec: E,
) -> Result<Option<()>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{

View File

@ -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,

View File

@ -42,7 +42,10 @@ impl User {
Ok(())
}
pub async fn get<'a, 'b, E>(id: UserId, executor: E) -> Result<Option<Self>, sqlx::error::Error>
pub async fn get<'a, 'b, E>(
id: UserId,
executor: E,
) -> Result<Option<Self>, 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<UserId>, exec: E) -> Result<Vec<User>, sqlx::Error>
pub async fn get_many<'a, E>(
user_ids: Vec<UserId>,
exec: E,
) -> Result<Vec<User>, sqlx::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;
let user_ids_parsed: Vec<i64> = user_ids.into_iter().map(|x| x.0).collect();
let user_ids_parsed: Vec<i64> =
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<i64> = 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);

View File

@ -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<VersionId>, Option<ProjectId>) =
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<VersionId>,
Option<ProjectId>,
) = 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<i64> = version_ids.into_iter().map(|x| x.0).collect();
let version_ids_parsed: Vec<i64> =
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<FileId, HashMap<String, Vec<u8>>> = HashMap::new();
let mut hashes_map: HashMap<FileId, HashMap<String, Vec<u8>>> =
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())
}
}

View File

@ -5,7 +5,8 @@ use sqlx::{Connection, PgConnection, Postgres};
pub async fn connect() -> Result<PgPool, sqlx::Error> {
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")

View File

@ -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<UploadFileData, FileHostingError> {
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<DeleteFileData, FileHostingError> {
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<T>(response: Response) -> Result<T, FileHostingError>
pub async fn process_response<T>(
response: Response,
) -> Result<T, FileHostingError>
where
T: for<'de> Deserialize<'de>,
{

View File

@ -52,7 +52,13 @@ pub async fn get_upload_url(
bucket_id: &str,
) -> Result<UploadUrlData, FileHostingError> {
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,

View File

@ -19,11 +19,15 @@ impl FileHost for MockHost {
file_name: &str,
file_bytes: Bytes,
) -> Result<UploadFileData, FileHostingError> {
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<DeleteFileData, FileHostingError> {
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 {

View File

@ -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<UploadFileData, FileHostingError> {
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 {

View File

@ -1,7 +1,9 @@
use actix_web::web;
use sqlx::PgPool;
pub async fn test_database(postgres: web::Data<PgPool>) -> Result<(), sqlx::Error> {
pub async fn test_database(
postgres: web::Data<PgPool>,
) -> Result<(), sqlx::Error> {
let mut transaction = postgres.acquire().await?;
sqlx::query(
"

View File

@ -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 storage_backend =
dotenv::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> = 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 file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
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()))

View File

@ -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);

View File

@ -360,10 +360,16 @@ impl From<QueryVersion> 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(),
}
}

View File

@ -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()),
}
}
}

View File

@ -36,9 +36,9 @@ impl MemoryStore {
pub fn with_capacity(capacity: usize) -> Self {
debug!("Creating new MemoryStore");
MemoryStore {
inner: Arc::new(DashMap::<String, (usize, Duration)>::with_capacity(
capacity,
)),
inner: Arc::new(
DashMap::<String, (usize, Duration)>::with_capacity(capacity),
),
}
}
}
@ -74,10 +74,18 @@ impl Supervised for MemoryStoreActor {
impl Handler<ActorMessage> 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<ActorMessage> 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<ActorMessage> 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<ActorMessage> 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<ActorMessage> 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<ActorMessage> for MemoryStoreActor {
let val = match self.inner.remove::<String>(&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;

View File

@ -95,7 +95,9 @@ where
}
/// Function to get the identifier for the client request
pub fn with_identifier<F: Fn(&ServiceRequest) -> Result<String, ARError> + 'static>(
pub fn with_identifier<
F: Fn(&ServiceRequest) -> Result<String, ARError> + 'static,
>(
mut self,
identifier: F,
) -> Self {
@ -108,7 +110,8 @@ impl<T, S, B> Transform<S, ServiceRequest> for RateLimiter<T>
where
T: Handler<ActorMessage> + Send + Sync + 'static,
T::Context: ToEnvelope<T, ActorMessage>,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError> + 'static,
S: Service<ServiceRequest, Response = ServiceResponse<B>, 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<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>,
identifier:
Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>,
ignore_ips: Vec<String>,
}
impl<T, S, B> Service<ServiceRequest> for RateLimitMiddleware<S, T>
where
T: Handler<ActorMessage> + 'static,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError> + 'static,
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = AWError>
+ 'static,
S::Future: 'static,
B: 'static,
T::Context: ToEnvelope<T, ActorMessage>,
{
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
fn poll_ready(
&self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
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)
}

View File

@ -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;

View File

@ -58,7 +58,11 @@ pub async fn maven_metadata(
) -> Result<HttpResponse, ApiError> {
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<HttpResponse, ApiError> {
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<HttpResponse, ApiError> {
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<HttpResponse, ApiError> {
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 {

View File

@ -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(),
})
)
}
}

View File

@ -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::<Vec<database::models::ProjectId>>()
.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))
}

View File

@ -31,8 +31,11 @@ pub async fn notifications_get(
.collect();
let notifications_data: Vec<DBNotification> =
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<Notification> = 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<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool).await?;
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let notification_ids =
serde_json::from_str::<Vec<NotificationId>>(&*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<database::models::ids::NotificationId> = Vec::new();
let mut notifications: Vec<database::models::ids::NotificationId> =
Vec::new();
for notification in notifications_data {
if notification.user_id == user.id.into() || user.role.is_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?;

View File

@ -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<ProjectId> =
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()))?;
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 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<String, CreateError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
if let Some(content_type) =
crate::util::ext::get_image_content_type(file_extension)
{
let data = read_from_field(
&mut field,
262144,
"Icons must be smaller than 256KiB",
)
.await?;
let upload_data = file_host
.upload_file(

View File

@ -39,12 +39,14 @@ pub async fn projects_get(
web::Query(ids): web::Query<ProjectIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let project_ids =
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*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<HttpResponse, ApiError> {
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::<Vec<(
database::models::VersionId,
Option<database::models::VersionId>,
Option<database::models::ProjectId>,
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<HttpResponse, ApiError> {
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<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
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<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
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<SearchConfig>,
) -> 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<meilisearch_sdk::indexes::Index> = client.get_indexes().await?;
let indexes: Vec<meilisearch_sdk::indexes::Index> =
client.get_indexes().await?;
for index in indexes {
index.delete_document(format!("{}", id)).await?;
}

View File

@ -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<HttpResponse, ApiError> {
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::<ProjectId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<ProjectId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Version => {
report.version_id = Some(
serde_json::from_str::<VersionId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<VersionId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::User => {
report.user_id = Some(
serde_json::from_str::<UserId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<UserId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Unknown => {
@ -127,8 +148,10 @@ pub async fn reports(
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
.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();

View File

@ -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<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn category_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let mut results = Category::list(&**pool)
.await?
.into_iter()
@ -64,12 +68,16 @@ pub async fn category_create(
) -> Result<HttpResponse, ApiError> {
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<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn loader_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
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::<Vec<_>>())?
.supported_project_types(
&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>(),
)?
.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<PgPool>,
query: web::Query<GameVersionQuery>,
) -> Result<HttpResponse, ApiError> {
let results: Vec<GameVersionQueryData> = if query.type_.is_some() || query.major.is_some() {
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool).await?
let results: Vec<GameVersionQueryData> = 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<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn license_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let results: Vec<LicenseQueryData> = 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<PgPool>) -> Result<HttpResponse, ApiError> {
let results: Vec<DonationPlatformQueryData> = 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<PgPool>,
) -> Result<HttpResponse, ApiError> {
let results: Vec<DonationPlatformQueryData> =
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<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn report_type_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
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?;

View File

@ -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<HttpResponse, ApiError> {
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<PgPool>,
) -> Result<HttpResponse, ApiError> {
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 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 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(""))

View File

@ -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(),
};

View File

@ -20,8 +20,10 @@ pub async fn user_auth_get(
req: HttpRequest,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
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<crate::models::users::User> = users_data.into_iter().map(From::from).collect();
let users: Vec<crate::models::users::User> =
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<PgPool>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let id_option: Option<UserId> = serde_json::from_str(&*format!("\"{}\"", string)).ok();
let id_option: Option<UserId> =
serde_json::from_str(&*format!("\"{}\"", string)).ok();
let mut user_data;
@ -82,9 +86,11 @@ pub async fn projects_list(
) -> Result<HttpResponse, ApiError> {
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<HttpResponse, ApiError> {
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<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
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<RemovalType>,
) -> Result<HttpResponse, ApiError> {
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<PgPool>,
) -> Result<HttpResponse, ApiError> {
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::<Vec<crate::database::models::ProjectId>>()
.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<PgPool>,
) -> Result<HttpResponse, ApiError> {
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() {

View File

@ -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::<Vec<database::models::ProjectId>>()
.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))
}

View File

@ -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<ProjectIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
let project_ids =
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*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();

View File

@ -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<HttpResponse, ApiError> {
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::<ProjectId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<ProjectId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Version => {
report.version_id = Some(
serde_json::from_str::<VersionId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<VersionId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::User => {
report.user_id = Some(
serde_json::from_str::<UserId>(&*format!("\"{}\"", new_report.item_id))?.into(),
serde_json::from_str::<UserId>(&*format!(
"\"{}\"",
new_report.item_id
))?
.into(),
)
}
ItemType::Unknown => {
@ -160,8 +181,10 @@ pub async fn reports(
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
.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();

View File

@ -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#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>"#;
#[get("category")]
pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn category_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
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<PgPool>) -> Result<HttpResponse, ApiError> {
pub async fn loader_list(
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
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::<Vec<_>>())?
.supported_project_types(
&*project_types.into_iter().map(|x| x.id).collect::<Vec<_>>(),
)?
.insert(&mut transaction)
.await?;
@ -92,11 +107,15 @@ pub async fn game_version_list(
query: web::Query<GameVersionQueryData>,
) -> Result<HttpResponse, ApiError> {
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::<Vec<String>>();
let results = GameVersion::list_filter(
query.type_.as_deref(),
query.major,
&**pool,
)
.await?
.into_iter()
.map(|x| x.version)
.collect::<Vec<String>>();
Ok(HttpResponse::Ok().json(results))
} else {
let results = GameVersion::list(&**pool)

View File

@ -29,18 +29,20 @@ pub async fn team_members_get(
) -> Result<HttpResponse, ApiError> {
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<TeamMember> = members_data

View File

@ -15,9 +15,11 @@ pub async fn mods_list(
) -> Result<HttpResponse, ApiError> {
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<PgPool>,
) -> Result<HttpResponse, ApiError> {
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::<Vec<ProjectId>>()
.await?;

View File

@ -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::<Result<Vec<models::GameVersionId>, 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 {

View File

@ -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::<QueryVersion, Version>(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()),

View File

@ -18,8 +18,8 @@ impl Scheduler {
F: FnMut() -> R + Send + 'static,
R: std::future::Future<Output = ()> + 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<sqlx::Postgres>,
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<chrono::Utc>,
}
async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> Result<(), VersionIndexingError> {
let input = reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
.await?
.json::<InputFormat>()
.await?;
async fn update_versions(
pool: &sqlx::Pool<sqlx::Postgres>,
) -> Result<(), VersionIndexingError> {
let input = reqwest::get(
"https://launchermeta.mojang.com/mc/game/version_manifest.json",
)
.await?
.json::<InputFormat>()
.await?;
let mut skipped_versions_count = 0u32;
@ -156,7 +161,8 @@ async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> 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 {

View File

@ -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<Vec<UploadSearchProject>, IndexingError> {
pub async fn index_local(
pool: PgPool,
) -> Result<Vec<UploadSearchProject>, IndexingError> {
info!("Indexing local projects!");
Ok(
sqlx::query!(

View File

@ -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,

View File

@ -21,9 +21,15 @@ impl CreationQueue {
self.queue.lock().unwrap().push(search_project);
}
pub fn take(&self) -> Vec<UploadSearchProject> {
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
}

View File

@ -149,12 +149,13 @@ pub async fn search_for_project(
) -> Result<SearchResults, SearchError> {
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");

View File

@ -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 {

View File

@ -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<T, U>(version_data: Option<T>) -> Result<HttpResponse, ApiError>
pub(crate) fn ok_or_not_found<T, U>(
version_data: Option<T>,
) -> Result<HttpResponse, ApiError>
where
U: From<T> + Serialize,
{

View File

@ -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>) -> String {
pub fn validation_errors_to_string(
errors: ValidationErrors,
adder: Option<String>,
) -> 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<Strin
if let Some(error) = map.get(field) {
return match error {
ValidationErrorsKind::Struct(errors) => {
validation_errors_to_string(*errors.clone(), Some(format!("of item {}", field)))
validation_errors_to_string(
*errors.clone(),
Some(format!("of item {}", field)),
)
}
ValidationErrorsKind::List(list) => {
if let Some((index, errors)) = list.iter().next() {
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
)),
));
}

View File

@ -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<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> {
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!",
));

View File

@ -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<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> {
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<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> {
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")) {

View File

@ -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))
}

View File

@ -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<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> {
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 {