Run fmt, fix dep route (#312)
This commit is contained in:
parent
725f8571bb
commit
459e36c027
@ -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": {
|
||||
|
||||
@ -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>,
|
||||
{
|
||||
|
||||
@ -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>,
|
||||
{
|
||||
|
||||
@ -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!(
|
||||
"
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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,
|
||||
{
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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>,
|
||||
{
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(
|
||||
"
|
||||
|
||||
87
src/main.rs
87
src/main.rs
@ -51,7 +51,8 @@ pub struct Pepper {
|
||||
#[actix_rt::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
||||
.init();
|
||||
|
||||
let config = Config::parse_args_default_or_exit();
|
||||
|
||||
@ -102,37 +103,40 @@ async fn main() -> std::io::Result<()> {
|
||||
.await
|
||||
.expect("Database connection failed");
|
||||
|
||||
let storage_backend = dotenv::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
||||
let 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()))
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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?;
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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?;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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?;
|
||||
|
||||
|
||||
@ -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(""))
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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?;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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!(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
{
|
||||
|
||||
@ -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
|
||||
)),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -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!",
|
||||
));
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user