You've already forked AstralRinth
forked from didirus/AstralRinth
Run fmt, fix dep route (#312)
This commit is contained in:
@@ -1655,38 +1655,6 @@
|
|||||||
"nullable": []
|
"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": {
|
"5a13a79ebb1ab975f88b58e6deaba9685fe16e242c0fa4a5eea54f12f9448e6b": {
|
||||||
"query": "\n DELETE FROM reports\n WHERE version_id = $1\n ",
|
"query": "\n DELETE FROM reports\n WHERE version_id = $1\n ",
|
||||||
"describe": {
|
"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": {
|
"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 ",
|
"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": {
|
"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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -115,7 +118,10 @@ impl Category {
|
|||||||
Ok(result.map(|r| CategoryId(r.id)))
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -159,7 +165,10 @@ impl Category {
|
|||||||
Ok(result)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -184,7 +193,10 @@ impl Category {
|
|||||||
|
|
||||||
impl<'a> CategoryBuilder<'a> {
|
impl<'a> CategoryBuilder<'a> {
|
||||||
/// The name of the category. Must be ASCII alphanumeric or `-`/`_`
|
/// 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
|
if name
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
.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 {
|
Ok(Self {
|
||||||
icon: Some(icon),
|
icon: Some(icon),
|
||||||
..self
|
..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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
let id = *self
|
let id = *self.project_type.ok_or_else(|| {
|
||||||
.project_type
|
DatabaseError::Other("No project type specified.".to_string())
|
||||||
.ok_or_else(|| DatabaseError::Other("No project type specified.".to_string()))?;
|
})?;
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO categories (category, project_type, icon)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -278,7 +299,10 @@ impl Loader {
|
|||||||
Ok(result.map(|r| LoaderId(r.id)))
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -330,7 +354,10 @@ impl Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove loaders with projects using them
|
// 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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -355,7 +382,10 @@ impl Loader {
|
|||||||
|
|
||||||
impl<'a> LoaderBuilder<'a> {
|
impl<'a> LoaderBuilder<'a> {
|
||||||
/// The name of the loader. Must be ASCII alphanumeric or `-`/`_`
|
/// 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
|
if name
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
.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 {
|
Ok(Self {
|
||||||
icon: Some(icon),
|
icon: Some(icon),
|
||||||
..self
|
..self
|
||||||
@@ -471,7 +504,10 @@ impl GameVersion {
|
|||||||
Ok(result.map(|r| GameVersionId(r.id)))
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -589,7 +625,10 @@ impl GameVersion {
|
|||||||
Ok(result)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -614,7 +653,10 @@ impl GameVersion {
|
|||||||
|
|
||||||
impl<'a> GameVersionBuilder<'a> {
|
impl<'a> GameVersionBuilder<'a> {
|
||||||
/// The game version. Spaces must be replaced with '_' for it to be valid
|
/// 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
|
if version
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
.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 {
|
Self {
|
||||||
date: Some(created),
|
date: Some(created),
|
||||||
..self
|
..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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -690,7 +738,10 @@ impl License {
|
|||||||
LicenseBuilder::default()
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -707,7 +758,10 @@ impl License {
|
|||||||
Ok(result.map(|r| LicenseId(r.id)))
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -751,7 +805,10 @@ impl License {
|
|||||||
Ok(result)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -776,7 +833,10 @@ impl License {
|
|||||||
|
|
||||||
impl<'a> LicenseBuilder<'a> {
|
impl<'a> LicenseBuilder<'a> {
|
||||||
/// The license's short name/abbreviation. Spaces must be replaced with '_' for it to be valid
|
/// 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
|
if short
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
||||||
@@ -791,14 +851,20 @@ impl<'a> LicenseBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The license's long name
|
/// 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 {
|
Ok(Self {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
..self
|
..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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -897,7 +965,10 @@ impl DonationPlatform {
|
|||||||
Ok(result)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -922,7 +993,10 @@ impl DonationPlatform {
|
|||||||
|
|
||||||
impl<'a> DonationPlatformBuilder<'a> {
|
impl<'a> DonationPlatformBuilder<'a> {
|
||||||
/// The donation platform short name. Spaces must be replaced with '_' for it to be valid
|
/// 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
|
if short
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
||||||
@@ -937,14 +1011,20 @@ impl<'a> DonationPlatformBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The donation platform long name
|
/// 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 {
|
Ok(Self {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
..self
|
..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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -974,7 +1054,10 @@ impl ReportType {
|
|||||||
ReportTypeBuilder { name: None }
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -998,7 +1081,10 @@ impl ReportType {
|
|||||||
Ok(result.map(|r| ReportTypeId(r.id)))
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1032,7 +1118,10 @@ impl ReportType {
|
|||||||
Ok(result)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1057,7 +1146,10 @@ impl ReportType {
|
|||||||
|
|
||||||
impl<'a> ReportTypeBuilder<'a> {
|
impl<'a> ReportTypeBuilder<'a> {
|
||||||
/// The name of the report type. Must be ASCII alphanumeric or `-`/`_`
|
/// 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
|
if name
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
.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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1097,7 +1192,10 @@ impl ProjectType {
|
|||||||
ProjectTypeBuilder { name: None }
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1148,7 +1246,10 @@ impl ProjectType {
|
|||||||
Ok(project_types)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1183,7 +1284,10 @@ impl ProjectType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove loaders with mods using them
|
// 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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -1208,7 +1312,10 @@ impl ProjectType {
|
|||||||
|
|
||||||
impl<'a> ProjectTypeBuilder<'a> {
|
impl<'a> ProjectTypeBuilder<'a> {
|
||||||
/// The name of the project type. Must be ASCII alphanumeric or `-`/`_`
|
/// 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
|
if name
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
.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
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -83,7 +83,10 @@ impl ids::SideTypeId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ids::DonationPlatformId {
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -102,7 +105,10 @@ impl ids::DonationPlatformId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ids::ProjectTypeId {
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -174,9 +174,11 @@ impl Notification {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
futures::future::try_join_all(notification_ids.into_iter().map(|id| Self::get(id, exec)))
|
futures::future::try_join_all(
|
||||||
.await
|
notification_ids.into_iter().map(|id| Self::get(id, exec)),
|
||||||
.map(|x| x.into_iter().flatten().collect())
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.into_iter().flatten().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_many_user<'a, E>(
|
pub async fn get_many_user<'a, E>(
|
||||||
@@ -234,7 +236,8 @@ impl Notification {
|
|||||||
notification_ids: Vec<NotificationId>,
|
notification_ids: Vec<NotificationId>,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<Option<()>, sqlx::error::Error> {
|
) -> 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!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -300,7 +300,8 @@ impl Project {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
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!(
|
let projects = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id, project_type, title, description, downloads, follows,
|
SELECT id, project_type, title, description, downloads, follows,
|
||||||
@@ -542,19 +543,24 @@ impl Project {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
let id_option =
|
let id_option = crate::models::ids::base62_impl::parse_base62(
|
||||||
crate::models::ids::base62_impl::parse_base62(&*slug_or_project_id.clone()).ok();
|
&*slug_or_project_id.clone(),
|
||||||
|
)
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
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() {
|
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)
|
Ok(project)
|
||||||
} else {
|
} 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)
|
Ok(project)
|
||||||
}
|
}
|
||||||
@@ -567,18 +573,25 @@ impl Project {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
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 {
|
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() {
|
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)
|
Ok(project)
|
||||||
} else {
|
} 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)
|
Ok(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,8 +689,14 @@ impl Project {
|
|||||||
moderation_message_body: m.moderation_message_body,
|
moderation_message_body: m.moderation_message_body,
|
||||||
},
|
},
|
||||||
project_type: m.project_type_name,
|
project_type: m.project_type_name,
|
||||||
categories: categories?.into_iter().map(|x| x.category).collect(),
|
categories: categories?
|
||||||
versions: versions?.into_iter().map(|x| VersionId(x.id)).collect(),
|
.into_iter()
|
||||||
|
.map(|x| x.category)
|
||||||
|
.collect(),
|
||||||
|
versions: versions?
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| VersionId(x.id))
|
||||||
|
.collect(),
|
||||||
donation_urls: donations?
|
donation_urls: donations?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| DonationUrl {
|
.map(|x| DonationUrl {
|
||||||
@@ -699,11 +718,17 @@ impl Project {
|
|||||||
created: x.created,
|
created: x.created,
|
||||||
})
|
})
|
||||||
.collect(),
|
.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_id: m.short,
|
||||||
license_name: m.license_name,
|
license_name: m.license_name,
|
||||||
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
|
client_side: crate::models::projects::SideType::from_str(
|
||||||
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
|
&m.client_side_type,
|
||||||
|
),
|
||||||
|
server_side: crate::models::projects::SideType::from_str(
|
||||||
|
&m.server_side_type,
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -717,9 +742,11 @@ impl Project {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
futures::future::try_join_all(project_ids.into_iter().map(|id| Self::get_full(id, exec)))
|
futures::future::try_join_all(
|
||||||
.await
|
project_ids.into_iter().map(|id| Self::get_full(id, exec)),
|
||||||
.map(|x| x.into_iter().flatten().collect())
|
)
|
||||||
|
.await
|
||||||
|
.map(|x| x.into_iter().flatten().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -52,7 +52,10 @@ impl Report {
|
|||||||
Ok(())
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
@@ -93,7 +96,8 @@ impl Report {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
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!(
|
let reports = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created
|
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)
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ impl TeamBuilder {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for member in self.members {
|
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 {
|
let team_member = TeamMember {
|
||||||
id: team_member_id,
|
id: team_member_id,
|
||||||
team_id,
|
team_id,
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ impl User {
|
|||||||
|
|
||||||
Ok(())
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
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
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
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!(
|
let users = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT u.id, u.github_id, u.name, u.email,
|
SELECT u.id, u.github_id, u.name, u.email,
|
||||||
@@ -365,8 +372,11 @@ impl User {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for project_id in projects {
|
for project_id in projects {
|
||||||
let _result =
|
let _result = super::project_item::Project::remove_full(
|
||||||
super::project_item::Project::remove_full(project_id, transaction).await?;
|
project_id,
|
||||||
|
transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifications: Vec<i64> = sqlx::query!(
|
let notifications: Vec<i64> = sqlx::query!(
|
||||||
@@ -445,7 +455,8 @@ impl User {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
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 {
|
if let Some(id) = id_option {
|
||||||
let id = UserId(id as i64);
|
let id = UserId(id as i64);
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ impl DependencyBuilder {
|
|||||||
version_id: VersionId,
|
version_id: VersionId,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
let (version_dependency_id, project_dependency_id): (Option<VersionId>, Option<ProjectId>) =
|
let (version_dependency_id, project_dependency_id): (
|
||||||
if self.version_id.is_some() {
|
Option<VersionId>,
|
||||||
(self.version_id, None)
|
Option<ProjectId>,
|
||||||
} else if let Some(project_id) = self.project_id {
|
) = if self.version_id.is_some() {
|
||||||
let version_id = sqlx::query!(
|
(self.version_id, None)
|
||||||
|
} else if let Some(project_id) = self.project_id {
|
||||||
|
let version_id = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT version.id id FROM (
|
SELECT version.id id FROM (
|
||||||
SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v
|
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));
|
.fetch_optional(&mut *transaction).await?.map(|x| VersionId(x.id));
|
||||||
|
|
||||||
(version_id, Some(project_id))
|
(version_id, Some(project_id))
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -561,7 +563,8 @@ impl Version {
|
|||||||
{
|
{
|
||||||
use futures::stream::TryStreamExt;
|
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!(
|
let versions = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
|
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? {
|
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? {
|
for hash in hashes? {
|
||||||
let entry = hashes_map
|
let entry = hashes_map
|
||||||
@@ -690,11 +694,17 @@ impl Version {
|
|||||||
id: FileId(x.id),
|
id: FileId(x.id),
|
||||||
url: x.url,
|
url: x.url,
|
||||||
filename: x.filename,
|
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,
|
primary: x.is_primary,
|
||||||
})
|
})
|
||||||
.collect(),
|
.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(),
|
loaders: loaders?.into_iter().map(|x| x.loader).collect(),
|
||||||
featured: v.featured,
|
featured: v.featured,
|
||||||
dependencies: dependencies?
|
dependencies: dependencies?
|
||||||
@@ -719,9 +729,11 @@ impl Version {
|
|||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
||||||
{
|
{
|
||||||
futures::future::try_join_all(version_ids.into_iter().map(|id| Self::get_full(id, exec)))
|
futures::future::try_join_all(
|
||||||
.await
|
version_ids.into_iter().map(|id| Self::get_full(id, exec)),
|
||||||
.map(|x| x.into_iter().flatten().collect())
|
)
|
||||||
|
.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> {
|
pub async fn connect() -> Result<PgPool, sqlx::Error> {
|
||||||
info!("Initializing database connection");
|
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()
|
let pool = PgPoolOptions::new()
|
||||||
.min_connections(
|
.min_connections(
|
||||||
dotenv::var("DATABASE_MIN_CONNECTIONS")
|
dotenv::var("DATABASE_MIN_CONNECTIONS")
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ pub struct BackblazeHost {
|
|||||||
|
|
||||||
impl BackblazeHost {
|
impl BackblazeHost {
|
||||||
pub async fn new(key_id: &str, key: &str, bucket_id: &str) -> Self {
|
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 authorization_data =
|
||||||
let upload_url_data = authorization::get_upload_url(&authorization_data, bucket_id)
|
authorization::authorize_account(key_id, key).await.unwrap();
|
||||||
.await
|
let upload_url_data =
|
||||||
.unwrap();
|
authorization::get_upload_url(&authorization_data, bucket_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
BackblazeHost {
|
BackblazeHost {
|
||||||
upload_url_data,
|
upload_url_data,
|
||||||
@@ -38,8 +40,13 @@ impl FileHost for BackblazeHost {
|
|||||||
) -> Result<UploadFileData, FileHostingError> {
|
) -> Result<UploadFileData, FileHostingError> {
|
||||||
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
let content_sha512 = format!("{:x}", sha2::Sha512::digest(&file_bytes));
|
||||||
|
|
||||||
let upload_data =
|
let upload_data = upload::upload_file(
|
||||||
upload::upload_file(&self.upload_url_data, content_type, file_name, file_bytes).await?;
|
&self.upload_url_data,
|
||||||
|
content_type,
|
||||||
|
file_name,
|
||||||
|
file_bytes,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(UploadFileData {
|
Ok(UploadFileData {
|
||||||
file_id: upload_data.file_id,
|
file_id: upload_data.file_id,
|
||||||
file_name: upload_data.file_name,
|
file_name: upload_data.file_name,
|
||||||
@@ -74,8 +81,12 @@ impl FileHost for BackblazeHost {
|
|||||||
file_id: &str,
|
file_id: &str,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
) -> Result<DeleteFileData, FileHostingError> {
|
) -> Result<DeleteFileData, FileHostingError> {
|
||||||
let delete_data =
|
let delete_data = delete::delete_file_version(
|
||||||
delete::delete_file_version(&self.authorization_data, file_id, file_name).await?;
|
&self.authorization_data,
|
||||||
|
file_id,
|
||||||
|
file_name,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(DeleteFileData {
|
Ok(DeleteFileData {
|
||||||
file_id: delete_data.file_id,
|
file_id: delete_data.file_id,
|
||||||
file_name: delete_data.file_name,
|
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
|
where
|
||||||
T: for<'de> Deserialize<'de>,
|
T: for<'de> Deserialize<'de>,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,13 @@ pub async fn get_upload_url(
|
|||||||
bucket_id: &str,
|
bucket_id: &str,
|
||||||
) -> Result<UploadUrlData, FileHostingError> {
|
) -> Result<UploadUrlData, FileHostingError> {
|
||||||
let response = reqwest::Client::new()
|
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::CONTENT_TYPE, "application/json")
|
||||||
.header(
|
.header(
|
||||||
reqwest::header::AUTHORIZATION,
|
reqwest::header::AUTHORIZATION,
|
||||||
|
|||||||
@@ -19,11 +19,15 @@ impl FileHost for MockHost {
|
|||||||
file_name: &str,
|
file_name: &str,
|
||||||
file_bytes: Bytes,
|
file_bytes: Bytes,
|
||||||
) -> Result<UploadFileData, FileHostingError> {
|
) -> Result<UploadFileData, FileHostingError> {
|
||||||
let path = std::path::Path::new(&dotenv::var("MOCK_FILE_PATH").unwrap())
|
let path =
|
||||||
.join(file_name.replace("../", ""));
|
std::path::Path::new(&dotenv::var("MOCK_FILE_PATH").unwrap())
|
||||||
std::fs::create_dir_all(path.parent().ok_or(FileHostingError::InvalidFilename)?)?;
|
.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_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)?;
|
std::fs::write(path, &*file_bytes)?;
|
||||||
Ok(UploadFileData {
|
Ok(UploadFileData {
|
||||||
@@ -43,8 +47,9 @@ impl FileHost for MockHost {
|
|||||||
file_id: &str,
|
file_id: &str,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
) -> Result<DeleteFileData, FileHostingError> {
|
) -> Result<DeleteFileData, FileHostingError> {
|
||||||
let path = std::path::Path::new(&dotenv::var("MOCK_FILE_PATH").unwrap())
|
let path =
|
||||||
.join(file_name.replace("../", ""));
|
std::path::Path::new(&dotenv::var("MOCK_FILE_PATH").unwrap())
|
||||||
|
.join(file_name.replace("../", ""));
|
||||||
std::fs::remove_file(path)?;
|
std::fs::remove_file(path)?;
|
||||||
|
|
||||||
Ok(DeleteFileData {
|
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 async_trait::async_trait;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use s3::bucket::Bucket;
|
use s3::bucket::Bucket;
|
||||||
@@ -24,12 +26,23 @@ impl S3Host {
|
|||||||
region: bucket_region.to_string(),
|
region: bucket_region.to_string(),
|
||||||
endpoint: url.to_string(),
|
endpoint: url.to_string(),
|
||||||
},
|
},
|
||||||
Credentials::new(Some(access_token), Some(secret), None, None, None).map_err(|_| {
|
Credentials::new(
|
||||||
FileHostingError::S3Error("Error while creating credentials".to_string())
|
Some(access_token),
|
||||||
|
Some(secret),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
FileHostingError::S3Error(
|
||||||
|
"Error while creating credentials".to_string(),
|
||||||
|
)
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
.map_err(|_| {
|
.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");
|
bucket.add_header("x-amz-acl", "public-read");
|
||||||
@@ -47,13 +60,20 @@ impl FileHost for S3Host {
|
|||||||
file_bytes: Bytes,
|
file_bytes: Bytes,
|
||||||
) -> Result<UploadFileData, FileHostingError> {
|
) -> Result<UploadFileData, FileHostingError> {
|
||||||
let content_sha1 = sha1::Sha1::from(&file_bytes).hexdigest();
|
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
|
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
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
FileHostingError::S3Error("Error while uploading file to S3".to_string())
|
FileHostingError::S3Error(
|
||||||
|
"Error while uploading file to S3".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(UploadFileData {
|
Ok(UploadFileData {
|
||||||
@@ -77,7 +97,9 @@ impl FileHost for S3Host {
|
|||||||
.delete_object(format!("/{}", file_name))
|
.delete_object(format!("/{}", file_name))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
FileHostingError::S3Error("Error while deleting file from S3".to_string())
|
FileHostingError::S3Error(
|
||||||
|
"Error while deleting file from S3".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(DeleteFileData {
|
Ok(DeleteFileData {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use sqlx::PgPool;
|
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?;
|
let mut transaction = postgres.acquire().await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"
|
"
|
||||||
|
|||||||
87
src/main.rs
87
src/main.rs
@@ -51,7 +51,8 @@ pub struct Pepper {
|
|||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
dotenv::dotenv().ok();
|
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();
|
let config = Config::parse_args_default_or_exit();
|
||||||
|
|
||||||
@@ -102,37 +103,40 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.await
|
.await
|
||||||
.expect("Database connection failed");
|
.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() {
|
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
|
||||||
"backblaze" => Arc::new(
|
match storage_backend.as_str() {
|
||||||
file_hosting::BackblazeHost::new(
|
"backblaze" => Arc::new(
|
||||||
&dotenv::var("BACKBLAZE_KEY_ID").unwrap(),
|
file_hosting::BackblazeHost::new(
|
||||||
&dotenv::var("BACKBLAZE_KEY").unwrap(),
|
&dotenv::var("BACKBLAZE_KEY_ID").unwrap(),
|
||||||
&dotenv::var("BACKBLAZE_BUCKET_ID").unwrap(),
|
&dotenv::var("BACKBLAZE_KEY").unwrap(),
|
||||||
)
|
&dotenv::var("BACKBLAZE_BUCKET_ID").unwrap(),
|
||||||
.await,
|
)
|
||||||
),
|
.await,
|
||||||
"s3" => Arc::new(
|
),
|
||||||
S3Host::new(
|
"s3" => Arc::new(
|
||||||
&*dotenv::var("S3_BUCKET_NAME").unwrap(),
|
S3Host::new(
|
||||||
&*dotenv::var("S3_REGION").unwrap(),
|
&*dotenv::var("S3_BUCKET_NAME").unwrap(),
|
||||||
&*dotenv::var("S3_URL").unwrap(),
|
&*dotenv::var("S3_REGION").unwrap(),
|
||||||
&*dotenv::var("S3_ACCESS_TOKEN").unwrap(),
|
&*dotenv::var("S3_URL").unwrap(),
|
||||||
&*dotenv::var("S3_SECRET").unwrap(),
|
&*dotenv::var("S3_ACCESS_TOKEN").unwrap(),
|
||||||
)
|
&*dotenv::var("S3_SECRET").unwrap(),
|
||||||
.unwrap(),
|
)
|
||||||
),
|
.unwrap(),
|
||||||
"local" => Arc::new(file_hosting::MockHost::new()),
|
),
|
||||||
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
"local" => Arc::new(file_hosting::MockHost::new()),
|
||||||
};
|
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
||||||
|
};
|
||||||
|
|
||||||
let mut scheduler = scheduler::Scheduler::new();
|
let mut scheduler = scheduler::Scheduler::new();
|
||||||
|
|
||||||
// The interval in seconds at which the local database is indexed
|
// The interval in seconds at which the local database is indexed
|
||||||
// for searching. Defaults to 1 hour if unset.
|
// for searching. Defaults to 1 hour if unset.
|
||||||
let local_index_interval =
|
let local_index_interval = std::time::Duration::from_secs(
|
||||||
std::time::Duration::from_secs(parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600));
|
parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600),
|
||||||
|
);
|
||||||
|
|
||||||
let mut skip = skip_initial;
|
let mut skip = skip_initial;
|
||||||
let pool_ref = pool.clone();
|
let pool_ref = pool.clone();
|
||||||
@@ -150,7 +154,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
info!("Indexing local database");
|
info!("Indexing local database");
|
||||||
let settings = IndexingSettings { index_local: true };
|
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 {
|
if let Err(e) = result {
|
||||||
warn!("Local project indexing failed: {:?}", e);
|
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 mut skip = skip_initial;
|
||||||
let queue_ref = indexing_queue.clone();
|
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);
|
scheduler::schedule_versions(&mut scheduler, pool.clone(), skip_initial);
|
||||||
|
|
||||||
let ip_salt = Pepper {
|
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();
|
let store = MemoryStore::new();
|
||||||
@@ -236,10 +245,16 @@ async fn main() -> std::io::Result<()> {
|
|||||||
RateLimiter::new(MemoryStoreActor::from(store.clone()).start())
|
RateLimiter::new(MemoryStoreActor::from(store.clone()).start())
|
||||||
.with_identifier(|req| {
|
.with_identifier(|req| {
|
||||||
let connection_info = req.connection_info();
|
let connection_info = req.connection_info();
|
||||||
let ip =
|
let ip = String::from(
|
||||||
String::from(if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
if parse_var("CLOUDFLARE_INTEGRATION")
|
||||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
.unwrap_or(false)
|
||||||
header.to_str().map_err(|_| ARError::IdentificationError)?
|
{
|
||||||
|
if let Some(header) =
|
||||||
|
req.headers().get("CF-Connecting-IP")
|
||||||
|
{
|
||||||
|
header.to_str().map_err(|_| {
|
||||||
|
ARError::IdentificationError
|
||||||
|
})?
|
||||||
} else {
|
} else {
|
||||||
connection_info
|
connection_info
|
||||||
.peer_addr()
|
.peer_addr()
|
||||||
@@ -249,14 +264,16 @@ async fn main() -> std::io::Result<()> {
|
|||||||
connection_info
|
connection_info
|
||||||
.peer_addr()
|
.peer_addr()
|
||||||
.ok_or(ARError::IdentificationError)?
|
.ok_or(ARError::IdentificationError)?
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok(ip)
|
Ok(ip)
|
||||||
})
|
})
|
||||||
.with_interval(std::time::Duration::from_secs(60))
|
.with_interval(std::time::Duration::from_secs(60))
|
||||||
.with_max_requests(300)
|
.with_max_requests(300)
|
||||||
.with_ignore_ips(
|
.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()))
|
.app_data(web::Data::new(pool.clone()))
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ pub mod base62_impl {
|
|||||||
impl<'de> Visitor<'de> for Base62Visitor {
|
impl<'de> Visitor<'de> for Base62Visitor {
|
||||||
type Value = Base62Id;
|
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")
|
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
|
// 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;
|
num = n;
|
||||||
} else {
|
} else {
|
||||||
return Err(DecodingError::Overflow);
|
return Err(DecodingError::Overflow);
|
||||||
|
|||||||
@@ -360,10 +360,16 @@ impl From<QueryVersion> for Version {
|
|||||||
.map(|d| Dependency {
|
.map(|d| Dependency {
|
||||||
version_id: d.version_id.map(|i| VersionId(i.0 as u64)),
|
version_id: d.version_id.map(|i| VersionId(i.0 as u64)),
|
||||||
project_id: d.project_id.map(|i| ProjectId(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(),
|
.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(),
|
loaders: data.loaders.into_iter().map(Loader).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,20 @@ impl ResponseError for ARError {
|
|||||||
reset,
|
reset,
|
||||||
} => {
|
} => {
|
||||||
let mut response = actix_web::HttpResponse::TooManyRequests();
|
let mut response = actix_web::HttpResponse::TooManyRequests();
|
||||||
response.insert_header(("x-ratelimit-limit", max_requests.to_string()));
|
response.insert_header((
|
||||||
response.insert_header(("x-ratelimit-remaining", remaining.to_string()));
|
"x-ratelimit-limit",
|
||||||
response.insert_header(("x-ratelimit-reset", reset.to_string()));
|
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())
|
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 {
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
debug!("Creating new MemoryStore");
|
debug!("Creating new MemoryStore");
|
||||||
MemoryStore {
|
MemoryStore {
|
||||||
inner: Arc::new(DashMap::<String, (usize, Duration)>::with_capacity(
|
inner: Arc::new(
|
||||||
capacity,
|
DashMap::<String, (usize, Duration)>::with_capacity(capacity),
|
||||||
)),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,10 +74,18 @@ impl Supervised for MemoryStoreActor {
|
|||||||
|
|
||||||
impl Handler<ActorMessage> for MemoryStoreActor {
|
impl Handler<ActorMessage> for MemoryStoreActor {
|
||||||
type Result = ActorResponse;
|
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 {
|
match msg {
|
||||||
ActorMessage::Set { key, value, expiry } => {
|
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 future_key = String::from(&key);
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
let now = now.duration_since(UNIX_EPOCH).unwrap();
|
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);
|
ctx.notify_later(ActorMessage::Remove(future_key), expiry);
|
||||||
ActorResponse::Set(Box::pin(future::ready(Ok(()))))
|
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) => {
|
Some(mut c) => {
|
||||||
let val_mut: &mut (usize, Duration) = c.value_mut();
|
let val_mut: &mut (usize, Duration) = c.value_mut();
|
||||||
if val_mut.0 > value {
|
if val_mut.0 > value {
|
||||||
@@ -98,7 +109,9 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Update(Box::pin(future::ready(Err(
|
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) {
|
let val = match self.inner.get(&key) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Get(Box::pin(future::ready(Err(
|
return ActorResponse::Get(Box::pin(future::ready(
|
||||||
ARError::ReadWriteError("memory store: read failed!".to_string()),
|
Err(ARError::ReadWriteError(
|
||||||
))))
|
"memory store: read failed!".to_string(),
|
||||||
|
)),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = val.value().0;
|
let val = val.value().0;
|
||||||
@@ -122,14 +137,17 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
let c = match self.inner.get(&key) {
|
let c = match self.inner.get(&key) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Expire(Box::pin(future::ready(Err(
|
return ActorResponse::Expire(Box::pin(future::ready(
|
||||||
ARError::ReadWriteError("memory store: read failed!".to_string()),
|
Err(ARError::ReadWriteError(
|
||||||
))))
|
"memory store: read failed!".to_string(),
|
||||||
|
)),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let dur = c.value().1;
|
let dur = c.value().1;
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
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))))
|
ActorResponse::Expire(Box::pin(future::ready(Ok(res))))
|
||||||
}
|
}
|
||||||
ActorMessage::Remove(key) => {
|
ActorMessage::Remove(key) => {
|
||||||
@@ -137,9 +155,11 @@ impl Handler<ActorMessage> for MemoryStoreActor {
|
|||||||
let val = match self.inner.remove::<String>(&key) {
|
let val = match self.inner.remove::<String>(&key) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
None => {
|
None => {
|
||||||
return ActorResponse::Remove(Box::pin(future::ready(Err(
|
return ActorResponse::Remove(Box::pin(future::ready(
|
||||||
ARError::ReadWriteError("memory store: remove failed!".to_string()),
|
Err(ARError::ReadWriteError(
|
||||||
))))
|
"memory store: remove failed!".to_string(),
|
||||||
|
)),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let val = val.1;
|
let val = val.1;
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Function to get the identifier for the client request
|
/// 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,
|
mut self,
|
||||||
identifier: F,
|
identifier: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -108,7 +110,8 @@ impl<T, S, B> Transform<S, ServiceRequest> for RateLimiter<T>
|
|||||||
where
|
where
|
||||||
T: Handler<ActorMessage> + Send + Sync + 'static,
|
T: Handler<ActorMessage> + Send + Sync + 'static,
|
||||||
T::Context: ToEnvelope<T, ActorMessage>,
|
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,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
{
|
{
|
||||||
@@ -141,23 +144,29 @@ where
|
|||||||
// Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter
|
// Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter
|
||||||
max_requests: usize,
|
max_requests: usize,
|
||||||
interval: u64,
|
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>,
|
ignore_ips: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, S, B> Service<ServiceRequest> for RateLimitMiddleware<S, T>
|
impl<T, S, B> Service<ServiceRequest> for RateLimitMiddleware<S, T>
|
||||||
where
|
where
|
||||||
T: Handler<ActorMessage> + 'static,
|
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,
|
S::Future: 'static,
|
||||||
B: 'static,
|
B: 'static,
|
||||||
T::Context: ToEnvelope<T, ActorMessage>,
|
T::Context: ToEnvelope<T, ActorMessage>,
|
||||||
{
|
{
|
||||||
type Response = ServiceResponse<B>;
|
type Response = ServiceResponse<B>;
|
||||||
type Error = S::Error;
|
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)
|
self.service.borrow_mut().poll_ready(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,9 +194,15 @@ where
|
|||||||
if let Some(c) = opt {
|
if let Some(c) = opt {
|
||||||
// Existing entry in store
|
// Existing entry in store
|
||||||
let expiry = store
|
let expiry = store
|
||||||
.send(ActorMessage::Expire(String::from(&identifier)))
|
.send(ActorMessage::Expire(String::from(
|
||||||
|
&identifier,
|
||||||
|
)))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ARError::ReadWriteError("Setting timeout".to_string()))?;
|
.map_err(|_| {
|
||||||
|
ARError::ReadWriteError(
|
||||||
|
"Setting timeout".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let reset: Duration = match expiry {
|
let reset: Duration = match expiry {
|
||||||
ActorResponse::Expire(dur) => dur.await?,
|
ActorResponse::Expire(dur) => dur.await?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@@ -209,7 +224,9 @@ where
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ARError::ReadWriteError("Decrementing ratelimit".to_string())
|
ARError::ReadWriteError(
|
||||||
|
"Decrementing ratelimit".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
let updated_value: usize = match res {
|
let updated_value: usize = match res {
|
||||||
ActorResponse::Update(c) => c.await?,
|
ActorResponse::Update(c) => c.await?,
|
||||||
@@ -222,15 +239,23 @@ where
|
|||||||
// Safe unwraps, since usize is always convertible to string
|
// Safe unwraps, since usize is always convertible to string
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-limit"),
|
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(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-remaining"),
|
HeaderName::from_static(
|
||||||
HeaderValue::from_str(updated_value.to_string().as_str())?,
|
"x-ratelimit-remaining",
|
||||||
|
),
|
||||||
|
HeaderValue::from_str(
|
||||||
|
updated_value.to_string().as_str(),
|
||||||
|
)?,
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-reset"),
|
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)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -245,7 +270,9 @@ where
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ARError::ReadWriteError("Creating store entry".to_string())
|
ARError::ReadWriteError(
|
||||||
|
"Creating store entry".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
match res {
|
match res {
|
||||||
ActorResponse::Set(c) => c.await?,
|
ActorResponse::Set(c) => c.await?,
|
||||||
@@ -257,15 +284,24 @@ where
|
|||||||
// Safe unwraps, since usize is always convertible to string
|
// Safe unwraps, since usize is always convertible to string
|
||||||
headers.insert(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-limit"),
|
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(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-remaining"),
|
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(
|
headers.insert(
|
||||||
HeaderName::from_static("x-ratelimit-reset"),
|
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)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,14 +38,26 @@ pub enum AuthorizationError {
|
|||||||
impl actix_web::ResponseError for AuthorizationError {
|
impl actix_web::ResponseError for AuthorizationError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
AuthorizationError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
AuthorizationError::EnvError(..) => {
|
||||||
AuthorizationError::SqlxDatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
AuthorizationError::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
}
|
||||||
|
AuthorizationError::SqlxDatabaseError(..) => {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
|
AuthorizationError::DatabaseError(..) => {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
AuthorizationError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
AuthorizationError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||||
AuthorizationError::GithubError(..) => StatusCode::FAILED_DEPENDENCY,
|
AuthorizationError::GithubError(..) => {
|
||||||
AuthorizationError::InvalidCredentialsError => StatusCode::UNAUTHORIZED,
|
StatusCode::FAILED_DEPENDENCY
|
||||||
|
}
|
||||||
|
AuthorizationError::InvalidCredentialsError => {
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
}
|
||||||
AuthorizationError::DecodingError(..) => StatusCode::BAD_REQUEST,
|
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::DatabaseError(..) => "database_error",
|
||||||
AuthorizationError::SerDeError(..) => "invalid_input",
|
AuthorizationError::SerDeError(..) => "invalid_input",
|
||||||
AuthorizationError::GithubError(..) => "github_error",
|
AuthorizationError::GithubError(..) => "github_error",
|
||||||
AuthorizationError::InvalidCredentialsError => "invalid_credentials",
|
AuthorizationError::InvalidCredentialsError => {
|
||||||
|
"invalid_credentials"
|
||||||
|
}
|
||||||
AuthorizationError::DecodingError(..) => "decoding_error",
|
AuthorizationError::DecodingError(..) => "decoding_error",
|
||||||
AuthorizationError::AuthenticationError(..) => "authentication_error",
|
AuthorizationError::AuthenticationError(..) => {
|
||||||
|
"authentication_error"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: &self.to_string(),
|
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 = 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 {
|
match user_result {
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None => {
|
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_increment: i32 = 0;
|
||||||
let mut username = None;
|
let mut username = None;
|
||||||
|
|||||||
@@ -58,7 +58,11 @@ pub async fn maven_metadata(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_id = params.into_inner().0;
|
let project_id = params.into_inner().0;
|
||||||
let project_data =
|
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 {
|
let data = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -119,7 +123,9 @@ fn find_file<'a>(
|
|||||||
version: &'a QueryVersion,
|
version: &'a QueryVersion,
|
||||||
file: &str,
|
file: &str,
|
||||||
) -> Option<&'a QueryFile> {
|
) -> 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);
|
return Some(selected_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +135,9 @@ fn find_file<'a>(
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if file == format!("{}-{}.{}", &project_id, &version.version_number, fileext) {
|
if file
|
||||||
|
== format!("{}-{}.{}", &project_id, &version.version_number, fileext)
|
||||||
|
{
|
||||||
version
|
version
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
@@ -148,7 +156,11 @@ pub async fn version_file(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
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 {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -175,9 +187,11 @@ pub async fn version_file(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) =
|
let version = if let Some(version) = database::models::Version::get_full(
|
||||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
database::models::ids::VersionId(vid.id),
|
||||||
.await?
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
@@ -197,10 +211,12 @@ pub async fn version_file(
|
|||||||
name: project.inner.title,
|
name: project.inner.title,
|
||||||
description: project.inner.description,
|
description: project.inner.description,
|
||||||
};
|
};
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok().content_type("text/xml").body(
|
||||||
.content_type("text/xml")
|
yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?,
|
||||||
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?));
|
));
|
||||||
} else if let Some(selected_file) = find_file(&project_id, &project, &version, &file) {
|
} else if let Some(selected_file) =
|
||||||
|
find_file(&project_id, &project, &version, &file)
|
||||||
|
{
|
||||||
return Ok(HttpResponse::TemporaryRedirect()
|
return Ok(HttpResponse::TemporaryRedirect()
|
||||||
.append_header(("location", &*selected_file.url))
|
.append_header(("location", &*selected_file.url))
|
||||||
.body(""));
|
.body(""));
|
||||||
@@ -217,7 +233,11 @@ pub async fn version_file_sha1(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
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 {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -244,9 +264,11 @@ pub async fn version_file_sha1(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) =
|
let version = if let Some(version) = database::models::Version::get_full(
|
||||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
database::models::ids::VersionId(vid.id),
|
||||||
.await?
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
@@ -268,7 +290,11 @@ pub async fn version_file_sha512(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let (project_id, vnum, file) = params.into_inner();
|
let (project_id, vnum, file) = params.into_inner();
|
||||||
let project_data =
|
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 {
|
let project = if let Some(data) = project_data {
|
||||||
data
|
data
|
||||||
@@ -295,9 +321,11 @@ pub async fn version_file_sha512(
|
|||||||
return Ok(HttpResponse::NotFound().body(""));
|
return Ok(HttpResponse::NotFound().body(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
let version = if let Some(version) =
|
let version = if let Some(version) = database::models::Version::get_full(
|
||||||
database::models::Version::get_full(database::models::ids::VersionId(vid.id), &**pool)
|
database::models::ids::VersionId(vid.id),
|
||||||
.await?
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
version
|
version
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -187,38 +187,62 @@ pub enum ApiError {
|
|||||||
impl actix_web::ResponseError for ApiError {
|
impl actix_web::ResponseError for ApiError {
|
||||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||||
match self {
|
match self {
|
||||||
ApiError::EnvError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
ApiError::EnvError(..) => {
|
||||||
ApiError::DatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||||
ApiError::SqlxDatabaseError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
}
|
||||||
ApiError::AuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
|
ApiError::DatabaseError(..) => {
|
||||||
ApiError::CustomAuthenticationError(..) => actix_web::http::StatusCode::UNAUTHORIZED,
|
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
|
||||||
ApiError::XmlError(..) => 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::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST,
|
||||||
ApiError::SearchError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
ApiError::SearchError(..) => {
|
||||||
ApiError::IndexingError(..) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
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::IndexingError(..) => {
|
||||||
ApiError::ValidationError(..) => actix_web::http::StatusCode::BAD_REQUEST,
|
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 {
|
fn error_response(&self) -> actix_web::HttpResponse {
|
||||||
actix_web::HttpResponse::build(self.status_code()).json(crate::models::error::ApiError {
|
actix_web::HttpResponse::build(self.status_code()).json(
|
||||||
error: match self {
|
crate::models::error::ApiError {
|
||||||
ApiError::EnvError(..) => "environment_error",
|
error: match self {
|
||||||
ApiError::SqlxDatabaseError(..) => "database_error",
|
ApiError::EnvError(..) => "environment_error",
|
||||||
ApiError::DatabaseError(..) => "database_error",
|
ApiError::SqlxDatabaseError(..) => "database_error",
|
||||||
ApiError::AuthenticationError(..) => "unauthorized",
|
ApiError::DatabaseError(..) => "database_error",
|
||||||
ApiError::CustomAuthenticationError(..) => "unauthorized",
|
ApiError::AuthenticationError(..) => "unauthorized",
|
||||||
ApiError::XmlError(..) => "xml_error",
|
ApiError::CustomAuthenticationError(..) => "unauthorized",
|
||||||
ApiError::JsonError(..) => "json_error",
|
ApiError::XmlError(..) => "xml_error",
|
||||||
ApiError::SearchError(..) => "search_error",
|
ApiError::JsonError(..) => "json_error",
|
||||||
ApiError::IndexingError(..) => "indexing_error",
|
ApiError::SearchError(..) => "search_error",
|
||||||
ApiError::FileHostingError(..) => "file_hosting_error",
|
ApiError::IndexingError(..) => "indexing_error",
|
||||||
ApiError::InvalidInputError(..) => "invalid_input",
|
ApiError::FileHostingError(..) => "file_hosting_error",
|
||||||
ApiError::ValidationError(..) => "invalid_input",
|
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
|
count.count as i64
|
||||||
)
|
)
|
||||||
.fetch_many(&**pool)
|
.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>>()
|
.try_collect::<Vec<database::models::ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let projects: Vec<_> = database::Project::get_many_full(project_ids, &**pool)
|
let projects: Vec<_> =
|
||||||
.await?
|
database::Project::get_many_full(project_ids, &**pool)
|
||||||
.into_iter()
|
.await?
|
||||||
.map(crate::models::projects::Project::from)
|
.into_iter()
|
||||||
.collect();
|
.map(crate::models::projects::Project::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ pub async fn notifications_get(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let notifications_data: Vec<DBNotification> =
|
let notifications_data: Vec<DBNotification> =
|
||||||
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
|
database::models::notification_item::Notification::get_many(
|
||||||
.await?;
|
notification_ids,
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let notifications: Vec<Notification> = notifications_data
|
let notifications: Vec<Notification> = notifications_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -54,7 +57,11 @@ pub async fn notification_get(
|
|||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
|
|
||||||
let notification_data =
|
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 let Some(data) = notification_data {
|
||||||
if user.id == data.user_id.into() || user.role.is_mod() {
|
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 id = info.into_inner().0;
|
||||||
|
|
||||||
let notification_data =
|
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 let Some(data) = notification_data {
|
||||||
if data.user_id == user.id.into() || user.role.is_mod() {
|
if data.user_id == user.id.into() || user.role.is_mod() {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
database::models::notification_item::Notification::remove(id.into(), &mut transaction)
|
database::models::notification_item::Notification::remove(
|
||||||
.await?;
|
id.into(),
|
||||||
|
&mut transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::CustomAuthenticationError(
|
Err(ApiError::CustomAuthenticationError(
|
||||||
"You are not authorized to delete this notification!".to_string(),
|
"You are not authorized to delete this notification!"
|
||||||
|
.to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -108,18 +123,23 @@ pub async fn notifications_delete(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
|
let notification_ids =
|
||||||
.into_iter()
|
serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
|
||||||
.map(|x| x.into())
|
.into_iter()
|
||||||
.collect();
|
.map(|x| x.into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let notifications_data =
|
let notifications_data =
|
||||||
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
|
database::models::notification_item::Notification::get_many(
|
||||||
.await?;
|
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 {
|
for notification in notifications_data {
|
||||||
if notification.user_id == user.id.into() || user.role.is_mod() {
|
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)
|
database::models::notification_item::Notification::remove_many(
|
||||||
.await?;
|
notifications,
|
||||||
|
&mut transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -67,10 +67,14 @@ impl actix_web::ResponseError for CreateError {
|
|||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
CreateError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
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::DatabaseError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
CreateError::IndexingError(..) => 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::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
CreateError::MultipartError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
CreateError::MissingValueError(..) => StatusCode::BAD_REQUEST,
|
||||||
@@ -81,7 +85,9 @@ impl actix_web::ResponseError for CreateError {
|
|||||||
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
CreateError::InvalidCategory(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
CreateError::InvalidFileType(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
CreateError::Unauthorized(..) => StatusCode::UNAUTHORIZED,
|
||||||
CreateError::CustomAuthenticationError(..) => StatusCode::UNAUTHORIZED,
|
CreateError::CustomAuthenticationError(..) => {
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
}
|
||||||
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
CreateError::SlugCollision => StatusCode::BAD_REQUEST,
|
||||||
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
CreateError::ValidationError(..) => StatusCode::BAD_REQUEST,
|
||||||
CreateError::FileValidationError(..) => 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")?;
|
let cdn_url = dotenv::var("CDN_URL")?;
|
||||||
|
|
||||||
// The currently logged in user
|
// 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 project_create_data;
|
||||||
let mut versions;
|
let mut versions;
|
||||||
let mut versions_map = std::collections::HashMap::new();
|
let mut versions_map = std::collections::HashMap::new();
|
||||||
let mut gallery_urls = Vec::new();
|
let mut gallery_urls = Vec::new();
|
||||||
|
|
||||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_game_versions =
|
||||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
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
|
// 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 content_disposition = field.content_disposition();
|
||||||
let name = content_disposition
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
.get_name()
|
CreateError::MissingValueError(String::from("Missing content name"))
|
||||||
.ok_or_else(|| CreateError::MissingValueError(String::from("Missing content name")))?;
|
})?;
|
||||||
|
|
||||||
if name != "data" {
|
if name != "data" {
|
||||||
return Err(CreateError::InvalidInput(String::from(
|
return Err(CreateError::InvalidInput(String::from(
|
||||||
@@ -336,19 +346,22 @@ pub async fn project_create_inner(
|
|||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
while let Some(chunk) = field.next().await {
|
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)?;
|
let create_data: ProjectCreateData = serde_json::from_slice(&data)?;
|
||||||
|
|
||||||
create_data
|
create_data.validate().map_err(|err| {
|
||||||
.validate()
|
CreateError::InvalidInput(validation_errors_to_string(err, None))
|
||||||
.map_err(|err| CreateError::InvalidInput(validation_errors_to_string(err, None)))?;
|
})?;
|
||||||
|
|
||||||
let slug_project_id_option: Option<ProjectId> =
|
let slug_project_id_option: Option<ProjectId> =
|
||||||
serde_json::from_str(&*format!("\"{}\"", create_data.slug)).ok();
|
serde_json::from_str(&*format!("\"{}\"", create_data.slug)).ok();
|
||||||
|
|
||||||
if let Some(slug_project_id) = slug_project_id_option {
|
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!(
|
let results = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)
|
||||||
@@ -393,15 +406,17 @@ pub async fn project_create_inner(
|
|||||||
project_create_data = create_data;
|
project_create_data = create_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_type_id =
|
let project_type_id = models::ProjectTypeId::get_id(
|
||||||
models::ProjectTypeId::get_id(project_create_data.project_type.clone(), &mut *transaction)
|
project_create_data.project_type.clone(),
|
||||||
.await?
|
&mut *transaction,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
CreateError::InvalidInput(format!(
|
.await?
|
||||||
"Project Type {} does not exist.",
|
.ok_or_else(|| {
|
||||||
project_create_data.project_type.clone()
|
CreateError::InvalidInput(format!(
|
||||||
))
|
"Project Type {} does not exist.",
|
||||||
})?;
|
project_create_data.project_type.clone()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut icon_url = None;
|
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 mut field: Field = item.map_err(CreateError::MultipartError)?;
|
||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
|
|
||||||
let name = content_disposition
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
.get_name()
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
|
})?;
|
||||||
|
|
||||||
let (file_name, file_extension) =
|
let (file_name, file_extension) =
|
||||||
super::version_creation::get_name_ext(&content_disposition)?;
|
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 hash = sha1::Sha1::from(&data).hexdigest();
|
||||||
let (_, file_extension) =
|
let (_, file_extension) =
|
||||||
super::version_creation::get_name_ext(&content_disposition)?;
|
super::version_creation::get_name_ext(
|
||||||
let content_type = crate::util::ext::get_image_content_type(file_extension)
|
&content_disposition,
|
||||||
.ok_or_else(|| CreateError::InvalidIconFormat(file_extension.to_string()))?;
|
)?;
|
||||||
|
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
|
let upload_data = file_host
|
||||||
.upload_file(content_type, &url, data.freeze())
|
.upload_file(content_type, &url, data.freeze())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -491,7 +516,8 @@ pub async fn project_create_inner(
|
|||||||
|
|
||||||
// `index` is always valid for these lists
|
// `index` is always valid for these lists
|
||||||
let created_version = versions.get_mut(index).unwrap();
|
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
|
// Upload the new jar file
|
||||||
super::version_creation::upload_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
|
// 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 {
|
for category in &project_create_data.categories {
|
||||||
let id = models::categories::Category::get_id_project(
|
let id = models::categories::Category::get_id_project(
|
||||||
category,
|
category,
|
||||||
@@ -568,44 +595,58 @@ pub async fn project_create_inner(
|
|||||||
let status_id = models::StatusId::get_id(&status, &mut *transaction)
|
let status_id = models::StatusId::get_id(&status, &mut *transaction)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.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 =
|
let client_side_id = models::SideTypeId::get_id(
|
||||||
models::SideTypeId::get_id(&project_create_data.client_side, &mut *transaction)
|
&project_create_data.client_side,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
CreateError::InvalidInput(
|
.await?
|
||||||
"Client side type specified does not exist.".to_string(),
|
.ok_or_else(|| {
|
||||||
)
|
CreateError::InvalidInput(
|
||||||
})?;
|
"Client side type specified does not exist.".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let server_side_id =
|
let server_side_id = models::SideTypeId::get_id(
|
||||||
models::SideTypeId::get_id(&project_create_data.server_side, &mut *transaction)
|
&project_create_data.server_side,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
CreateError::InvalidInput(
|
.await?
|
||||||
"Server side type specified does not exist.".to_string(),
|
.ok_or_else(|| {
|
||||||
)
|
CreateError::InvalidInput(
|
||||||
})?;
|
"Server side type specified does not exist.".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let license_id =
|
let license_id = models::categories::License::get_id(
|
||||||
models::categories::License::get_id(&project_create_data.license_id, &mut *transaction)
|
&project_create_data.license_id,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
CreateError::InvalidInput("License specified does not exist.".to_string())
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
|
CreateError::InvalidInput(
|
||||||
|
"License specified does not exist.".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let mut donation_urls = vec![];
|
let mut donation_urls = vec![];
|
||||||
|
|
||||||
if let Some(urls) = &project_create_data.donation_urls {
|
if let Some(urls) = &project_create_data.donation_urls {
|
||||||
for url in urls {
|
for url in urls {
|
||||||
let platform_id = models::DonationPlatformId::get_id(&url.id, &mut *transaction)
|
let platform_id = models::DonationPlatformId::get_id(
|
||||||
.await?
|
&url.id,
|
||||||
.ok_or_else(|| {
|
&mut *transaction,
|
||||||
CreateError::InvalidInput(format!(
|
)
|
||||||
"Donation platform {} does not exist.",
|
.await?
|
||||||
url.id.clone()
|
.ok_or_else(|| {
|
||||||
))
|
CreateError::InvalidInput(format!(
|
||||||
})?;
|
"Donation platform {} does not exist.",
|
||||||
|
url.id.clone()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
donation_urls.push(models::project_item::DonationUrl {
|
donation_urls.push(models::project_item::DonationUrl {
|
||||||
project_id: project_id.into(),
|
project_id: project_id.into(),
|
||||||
@@ -695,9 +736,12 @@ pub async fn project_create_inner(
|
|||||||
|
|
||||||
if status == ProjectStatus::Processing {
|
if status == ProjectStatus::Processing {
|
||||||
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(response.clone(), webhook_url)
|
crate::util::webhook::send_discord_webhook(
|
||||||
.await
|
response.clone(),
|
||||||
.ok();
|
webhook_url,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,12 +764,13 @@ async fn create_initial_version(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
version_data
|
version_data.validate().map_err(|err| {
|
||||||
.validate()
|
CreateError::ValidationError(validation_errors_to_string(err, None))
|
||||||
.map_err(|err| CreateError::ValidationError(validation_errors_to_string(err, None)))?;
|
})?;
|
||||||
|
|
||||||
// Randomly generate a new id to be used for the version
|
// 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
|
let game_versions = version_data
|
||||||
.game_versions
|
.game_versions
|
||||||
@@ -794,8 +839,15 @@ async fn process_icon_upload(
|
|||||||
mut field: actix_multipart::Field,
|
mut field: actix_multipart::Field,
|
||||||
cdn_url: &str,
|
cdn_url: &str,
|
||||||
) -> Result<String, CreateError> {
|
) -> Result<String, CreateError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
|
if let Some(content_type) =
|
||||||
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
|
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
|
let upload_data = file_host
|
||||||
.upload_file(
|
.upload_file(
|
||||||
|
|||||||
@@ -39,12 +39,14 @@ pub async fn projects_get(
|
|||||||
web::Query(ids): web::Query<ProjectIds>,
|
web::Query(ids): web::Query<ProjectIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
|
let project_ids =
|
||||||
.into_iter()
|
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
|
||||||
.map(|x| x.into())
|
.into_iter()
|
||||||
.collect();
|
.map(|x| x.into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let projects_data = database::models::Project::get_many_full(project_ids, &**pool).await?;
|
let projects_data =
|
||||||
|
database::models::Project::get_many_full(project_ids, &**pool).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let 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 string = info.into_inner().0;
|
||||||
|
|
||||||
let project_data =
|
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();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -96,7 +101,9 @@ pub async fn dependency_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
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 {
|
if let Some(project) = result {
|
||||||
let id = project.id;
|
let id = project.id;
|
||||||
@@ -105,9 +112,10 @@ pub async fn dependency_list(
|
|||||||
|
|
||||||
let dependencies = sqlx::query!(
|
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
|
FROM versions v
|
||||||
INNER JOIN dependencies d ON d.dependent_id = v.id
|
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
|
WHERE v.mod_id = $1
|
||||||
",
|
",
|
||||||
id as database::models::ProjectId
|
id as database::models::ProjectId
|
||||||
@@ -116,26 +124,24 @@ pub async fn dependency_list(
|
|||||||
.try_filter_map(|e| async {
|
.try_filter_map(|e| async {
|
||||||
Ok(e.right().map(|x| {
|
Ok(e.right().map(|x| {
|
||||||
(
|
(
|
||||||
database::models::VersionId(x.dependent_id),
|
|
||||||
x.dependency_id.map(database::models::VersionId),
|
x.dependency_id.map(database::models::VersionId),
|
||||||
x.mod_dependency_id.map(database::models::ProjectId),
|
database::models::ProjectId(x.mod_id),
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<(
|
.try_collect::<Vec<(
|
||||||
database::models::VersionId,
|
|
||||||
Option<database::models::VersionId>,
|
Option<database::models::VersionId>,
|
||||||
Option<database::models::ProjectId>,
|
database::models::ProjectId,
|
||||||
)>>()
|
)>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (projects_result, versions_result) = futures::join!(
|
let (projects_result, versions_result) = futures::join!(
|
||||||
database::Project::get_many_full(
|
database::Project::get_many_full(
|
||||||
dependencies.iter().map(|x| x.2).flatten().collect(),
|
dependencies.iter().map(|x| x.1).collect(),
|
||||||
&**pool,
|
&**pool,
|
||||||
),
|
),
|
||||||
database::Version::get_many_full(
|
database::Version::get_many_full(
|
||||||
dependencies.iter().map(|x| x.1).flatten().collect(),
|
dependencies.iter().map(|x| x.0).flatten().collect(),
|
||||||
&**pool,
|
&**pool,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -244,13 +250,15 @@ pub async fn project_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
new_project
|
new_project.validate().map_err(|err| {
|
||||||
.validate()
|
ApiError::ValidationError(validation_errors_to_string(err, None))
|
||||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
})?;
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let result =
|
let result = database::models::Project::get_full_from_slug_or_project_id(
|
||||||
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool).await?;
|
&string, &**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(project_item) = result {
|
if let Some(project_item) = result {
|
||||||
let id = project_item.inner.id;
|
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()
|
&& !user.role.is_mod()
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.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(
|
crate::util::webhook::send_discord_webhook(
|
||||||
Project::from(project_item.clone()),
|
Project::from(project_item.clone()),
|
||||||
webhook_url,
|
webhook_url,
|
||||||
@@ -371,13 +383,16 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_id = database::models::StatusId::get_id(status, &mut *transaction)
|
let status_id = database::models::StatusId::get_id(
|
||||||
.await?
|
status,
|
||||||
.ok_or_else(|| {
|
&mut *transaction,
|
||||||
ApiError::InvalidInputError(
|
)
|
||||||
"No database entry for status provided.".to_string(),
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
})?;
|
ApiError::InvalidInputError(
|
||||||
|
"No database entry for status provided.".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -391,12 +406,19 @@ pub async fn project_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.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?;
|
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 =
|
let index_project =
|
||||||
crate::search::indexing::local_import::query_one(id, &mut *transaction)
|
crate::search::indexing::local_import::query_one(
|
||||||
.await?;
|
id,
|
||||||
|
&mut *transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
indexing_queue.add(index_project);
|
indexing_queue.add(index_project);
|
||||||
}
|
}
|
||||||
@@ -422,14 +444,17 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
for category in categories {
|
for category in categories {
|
||||||
let category_id =
|
let category_id =
|
||||||
database::models::categories::Category::get_id(category, &mut *transaction)
|
database::models::categories::Category::get_id(
|
||||||
.await?
|
category,
|
||||||
.ok_or_else(|| {
|
&mut *transaction,
|
||||||
ApiError::InvalidInputError(format!(
|
)
|
||||||
"Category {} does not exist.",
|
.await?
|
||||||
category.clone()
|
.ok_or_else(|| {
|
||||||
))
|
ApiError::InvalidInputError(format!(
|
||||||
})?;
|
"Category {} does not exist.",
|
||||||
|
category.clone()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -574,7 +599,8 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
if results.exists.unwrap_or(true) {
|
if results.exists.unwrap_or(true) {
|
||||||
return Err(ApiError::InvalidInputError(
|
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 =
|
let side_type_id = database::models::SideTypeId::get_id(
|
||||||
database::models::SideTypeId::get_id(new_side, &mut *transaction)
|
new_side,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.expect("No database entry found for side type");
|
)
|
||||||
|
.await?
|
||||||
|
.expect("No database entry found for side type");
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -627,10 +655,12 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let side_type_id =
|
let side_type_id = database::models::SideTypeId::get_id(
|
||||||
database::models::SideTypeId::get_id(new_side, &mut *transaction)
|
new_side,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.expect("No database entry found for side type");
|
)
|
||||||
|
.await?
|
||||||
|
.expect("No database entry found for side type");
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -653,10 +683,12 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let license_id =
|
let license_id = database::models::categories::License::get_id(
|
||||||
database::models::categories::License::get_id(license, &mut *transaction)
|
license,
|
||||||
.await?
|
&mut *transaction,
|
||||||
.expect("No database entry found for license");
|
)
|
||||||
|
.await?
|
||||||
|
.expect("No database entry found for license");
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -690,17 +722,18 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for donation in donations {
|
for donation in donations {
|
||||||
let platform_id = database::models::DonationPlatformId::get_id(
|
let platform_id =
|
||||||
&donation.id,
|
database::models::DonationPlatformId::get_id(
|
||||||
&mut *transaction,
|
&donation.id,
|
||||||
)
|
&mut *transaction,
|
||||||
.await?
|
)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::InvalidInputError(format!(
|
.ok_or_else(|| {
|
||||||
"Platform {} does not exist.",
|
ApiError::InvalidInputError(format!(
|
||||||
donation.id.clone()
|
"Platform {} does not exist.",
|
||||||
))
|
donation.id.clone()
|
||||||
})?;
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -717,7 +750,9 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(moderation_message) = &new_project.moderation_message {
|
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(
|
return Err(ApiError::CustomAuthenticationError(
|
||||||
"You do not have the permissions to edit the moderation message of this project!"
|
"You do not have the permissions to edit the moderation message of this project!"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@@ -737,8 +772,12 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(moderation_message_body) = &new_project.moderation_message_body {
|
if let Some(moderation_message_body) =
|
||||||
if !user.role.is_mod() && project_item.status != ProjectStatus::Approved {
|
&new_project.moderation_message_body
|
||||||
|
{
|
||||||
|
if !user.role.is_mod()
|
||||||
|
&& project_item.status != ProjectStatus::Approved
|
||||||
|
{
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
return Err(ApiError::CustomAuthenticationError(
|
||||||
"You do not have the permissions to edit the moderation message body of this project!"
|
"You do not have the permissions to edit the moderation message body of this project!"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@@ -805,17 +844,24 @@ pub async fn project_icon_edit(
|
|||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> 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 cdn_url = dotenv::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item =
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
database::models::Project::get_from_slug_or_project_id(
|
||||||
.await?
|
string.clone(),
|
||||||
.ok_or_else(|| {
|
&**pool,
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
)
|
||||||
})?;
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -826,12 +872,15 @@ pub async fn project_icon_edit(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::DatabaseError)?
|
.map_err(ApiError::DatabaseError)?
|
||||||
.ok_or_else(|| {
|
.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) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 =
|
let bytes = read_from_payload(
|
||||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
&mut payload,
|
||||||
|
262144,
|
||||||
|
"Icons must be smaller than 256KiB",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||||
let project_id: ProjectId = project_item.id.into();
|
let project_id: ProjectId = project_item.id.into();
|
||||||
let upload_data = file_host
|
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 user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
string.clone(),
|
||||||
.await?
|
&**pool,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -907,12 +964,15 @@ pub async fn delete_project_icon(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::DatabaseError)?
|
.map_err(ApiError::DatabaseError)?
|
||||||
.ok_or_else(|| {
|
.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) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
if let Some(content_type) =
|
||||||
item.validate()
|
crate::util::ext::get_image_content_type(&*ext.ext)
|
||||||
.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 cdn_url = dotenv::var("CDN_URL")?;
|
let cdn_url = dotenv::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item =
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
database::models::Project::get_from_slug_or_project_id(
|
||||||
.await?
|
string.clone(),
|
||||||
.ok_or_else(|| {
|
&**pool,
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
)
|
||||||
})?;
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -986,12 +1054,15 @@ pub async fn add_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::DatabaseError)?
|
.map_err(ApiError::DatabaseError)?
|
||||||
.ok_or_else(|| {
|
.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) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
item.validate()
|
item.validate().map_err(|err| {
|
||||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
ApiError::ValidationError(validation_errors_to_string(err, None))
|
||||||
|
})?;
|
||||||
|
|
||||||
let project_item =
|
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
string.clone(),
|
||||||
.await?
|
&**pool,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -1098,12 +1174,15 @@ pub async fn edit_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::DatabaseError)?
|
.map_err(ApiError::DatabaseError)?
|
||||||
.ok_or_else(|| {
|
.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) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project_item =
|
let project_item = database::models::Project::get_from_slug_or_project_id(
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
string.clone(),
|
||||||
.await?
|
&**pool,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id(
|
let team_member = database::models::TeamMember::get_from_user_id(
|
||||||
@@ -1219,12 +1302,15 @@ pub async fn delete_gallery_item(
|
|||||||
.await
|
.await
|
||||||
.map_err(ApiError::DatabaseError)?
|
.map_err(ApiError::DatabaseError)?
|
||||||
.ok_or_else(|| {
|
.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) {
|
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let project = database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
let project = database::models::Project::get_from_slug_or_project_id(
|
||||||
.await?
|
string.clone(),
|
||||||
.ok_or_else(|| {
|
&**pool,
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
)
|
||||||
})?;
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id_project(
|
let team_member =
|
||||||
project.id,
|
database::models::TeamMember::get_from_user_id_project(
|
||||||
user.id.into(),
|
project.id,
|
||||||
&**pool,
|
user.id.into(),
|
||||||
)
|
&**pool,
|
||||||
.await
|
)
|
||||||
.map_err(ApiError::DatabaseError)?
|
.await
|
||||||
.ok_or_else(|| {
|
.map_err(ApiError::DatabaseError)?
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.ok_or_else(|| {
|
||||||
})?;
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !team_member
|
if !team_member
|
||||||
.permissions
|
.permissions
|
||||||
@@ -1310,7 +1404,9 @@ pub async fn project_delete(
|
|||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
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?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
@@ -1332,11 +1428,14 @@ pub async fn project_follow(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
let result =
|
||||||
.await?
|
database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.ok_or_else(|| {
|
||||||
})?;
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let user_id: database::models::ids::UserId = user.id.into();
|
let user_id: database::models::ids::UserId = user.id.into();
|
||||||
let project_id: database::models::ids::ProjectId = result.id;
|
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 user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
let result = database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
let result =
|
||||||
.await?
|
database::models::Project::get_from_slug_or_project_id(string, &**pool)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::InvalidInputError("The specified project does not exist!".to_string())
|
.ok_or_else(|| {
|
||||||
})?;
|
ApiError::InvalidInputError(
|
||||||
|
"The specified project does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let user_id: database::models::ids::UserId = user.id.into();
|
let user_id: database::models::ids::UserId = user.id.into();
|
||||||
let project_id = result.id;
|
let project_id = result.id;
|
||||||
@@ -1457,9 +1559,11 @@ pub async fn delete_from_index(
|
|||||||
id: crate::models::projects::ProjectId,
|
id: crate::models::projects::ProjectId,
|
||||||
config: web::Data<SearchConfig>,
|
config: web::Data<SearchConfig>,
|
||||||
) -> Result<(), meilisearch_sdk::errors::Error> {
|
) -> 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 {
|
for index in indexes {
|
||||||
index.delete_document(format!("{}", id)).await?;
|
index.delete_document(format!("{}", id)).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::models::ids::{ProjectId, UserId, VersionId};
|
use crate::models::ids::{ProjectId, UserId, VersionId};
|
||||||
use crate::models::reports::{ItemType, Report};
|
use crate::models::reports::{ItemType, Report};
|
||||||
use crate::routes::ApiError;
|
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 actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -23,24 +25,31 @@ pub async fn report_create(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut transaction = pool.begin().await?;
|
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();
|
let mut bytes = web::BytesMut::new();
|
||||||
while let Some(item) = body.next().await {
|
while let Some(item) = body.next().await {
|
||||||
bytes.extend_from_slice(&item.map_err(|_| {
|
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 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(
|
let report_type = crate::database::models::categories::ReportType::get_id(
|
||||||
&*new_report.report_type,
|
&*new_report.report_type,
|
||||||
&mut *transaction,
|
&mut *transaction,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.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 {
|
let mut report = crate::database::models::report_item::Report {
|
||||||
id,
|
id,
|
||||||
@@ -56,17 +65,29 @@ pub async fn report_create(
|
|||||||
match new_report.item_type {
|
match new_report.item_type {
|
||||||
ItemType::Project => {
|
ItemType::Project => {
|
||||||
report.project_id = Some(
|
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 => {
|
ItemType::Version => {
|
||||||
report.version_id = Some(
|
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 => {
|
ItemType::User => {
|
||||||
report.user_id = Some(
|
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 => {
|
ItemType::Unknown => {
|
||||||
@@ -127,8 +148,10 @@ pub async fn reports(
|
|||||||
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let query_reports =
|
let query_reports = crate::database::models::report_item::Report::get_many(
|
||||||
crate::database::models::report_item::Report::get_many(report_ids, &**pool).await?;
|
report_ids, &**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut reports = Vec::new();
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database::models;
|
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 crate::util::auth::check_is_admin_from_headers;
|
||||||
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
||||||
use models::categories::{Category, GameVersion, Loader};
|
use models::categories::{Category, GameVersion, Loader};
|
||||||
@@ -40,7 +42,9 @@ pub struct CategoryData {
|
|||||||
// TODO: searching / filtering? Could be used to implement a live
|
// TODO: searching / filtering? Could be used to implement a live
|
||||||
// searching category list
|
// searching category list
|
||||||
#[get("category")]
|
#[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)
|
let mut results = Category::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -64,12 +68,16 @@ pub async fn category_create(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let project_type =
|
let project_type = crate::database::models::ProjectTypeId::get_id(
|
||||||
crate::database::models::ProjectTypeId::get_id(new_category.project_type.clone(), &**pool)
|
new_category.project_type.clone(),
|
||||||
.await?
|
&**pool,
|
||||||
.ok_or_else(|| {
|
)
|
||||||
ApiError::InvalidInputError("Specified project type does not exist!".to_string())
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"Specified project type does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let _id = Category::builder()
|
let _id = Category::builder()
|
||||||
.name(&new_category.name)?
|
.name(&new_category.name)?
|
||||||
@@ -90,7 +98,8 @@ pub async fn category_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = category.into_inner().0;
|
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?;
|
let result = Category::remove(&name, &mut transaction).await?;
|
||||||
|
|
||||||
@@ -114,7 +123,9 @@ pub struct LoaderData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("loader")]
|
#[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)
|
let mut results = Loader::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -140,13 +151,18 @@ pub async fn loader_create(
|
|||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let project_types =
|
let project_types = ProjectType::get_many_id(
|
||||||
ProjectType::get_many_id(&new_loader.supported_project_types, &mut *transaction).await?;
|
&new_loader.supported_project_types,
|
||||||
|
&mut *transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let _id = Loader::builder()
|
let _id = Loader::builder()
|
||||||
.name(&new_loader.name)?
|
.name(&new_loader.name)?
|
||||||
.icon(&new_loader.icon)?
|
.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)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -164,7 +180,8 @@ pub async fn loader_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = loader.into_inner().0;
|
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?;
|
let result = Loader::remove(&name, &mut transaction).await?;
|
||||||
|
|
||||||
@@ -200,8 +217,11 @@ pub async fn game_version_list(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
query: web::Query<GameVersionQuery>,
|
query: web::Query<GameVersionQuery>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let results: Vec<GameVersionQueryData> = if query.type_.is_some() || query.major.is_some() {
|
let results: Vec<GameVersionQueryData> = if query.type_.is_some()
|
||||||
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool).await?
|
|| query.major.is_some()
|
||||||
|
{
|
||||||
|
GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
GameVersion::list(&**pool).await?
|
GameVersion::list(&**pool).await?
|
||||||
}
|
}
|
||||||
@@ -260,7 +280,8 @@ pub async fn game_version_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = game_version.into_inner().0;
|
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?;
|
let result = GameVersion::remove(&name, &mut transaction).await?;
|
||||||
|
|
||||||
@@ -283,7 +304,9 @@ pub struct LicenseQueryData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("license")]
|
#[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)
|
let results: Vec<LicenseQueryData> = License::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -329,7 +352,8 @@ pub async fn license_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = license.into_inner().0;
|
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?;
|
let result = License::remove(&name, &mut transaction).await?;
|
||||||
|
|
||||||
@@ -352,15 +376,18 @@ pub struct DonationPlatformQueryData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("donation_platform")]
|
#[get("donation_platform")]
|
||||||
pub async fn donation_platform_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
pub async fn donation_platform_list(
|
||||||
let results: Vec<DonationPlatformQueryData> = DonationPlatform::list(&**pool)
|
pool: web::Data<PgPool>,
|
||||||
.await?
|
) -> Result<HttpResponse, ApiError> {
|
||||||
.into_iter()
|
let results: Vec<DonationPlatformQueryData> =
|
||||||
.map(|x| DonationPlatformQueryData {
|
DonationPlatform::list(&**pool)
|
||||||
short: x.short,
|
.await?
|
||||||
name: x.name,
|
.into_iter()
|
||||||
})
|
.map(|x| DonationPlatformQueryData {
|
||||||
.collect();
|
short: x.short,
|
||||||
|
name: x.name,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +425,8 @@ pub async fn donation_platform_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = loader.into_inner().0;
|
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?;
|
let result = DonationPlatform::remove(&name, &mut transaction).await?;
|
||||||
|
|
||||||
@@ -415,7 +443,9 @@ pub async fn donation_platform_delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("report_type")]
|
#[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?;
|
let results = ReportType::list(&**pool).await?;
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
@@ -444,7 +474,8 @@ pub async fn report_type_delete(
|
|||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let name = report_type.into_inner().0;
|
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?;
|
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::database::models::TeamMember;
|
||||||
use crate::models::ids::ProjectId;
|
use crate::models::ids::ProjectId;
|
||||||
use crate::models::teams::{Permissions, TeamId};
|
use crate::models::teams::{Permissions, TeamId};
|
||||||
@@ -17,23 +19,33 @@ pub async fn team_members_get_project(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let project_data =
|
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 {
|
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 {
|
if let Some(user) = current_user {
|
||||||
let team_member =
|
let team_member = TeamMember::get_from_user_id(
|
||||||
TeamMember::get_from_user_id(project.team_id, user.id.into(), &**pool)
|
project.team_id,
|
||||||
.await
|
user.id.into(),
|
||||||
.map_err(ApiError::DatabaseError)?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::DatabaseError)?;
|
||||||
|
|
||||||
if team_member.is_some() {
|
if team_member.is_some() {
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
.map(|data| {
|
||||||
|
crate::models::teams::TeamMember::from(data, false)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok().json(team_members));
|
return Ok(HttpResponse::Ok().json(team_members));
|
||||||
@@ -59,14 +71,16 @@ pub async fn team_members_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
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();
|
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(user) = current_user {
|
if let Some(user) = current_user {
|
||||||
let team_member = TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
let team_member =
|
||||||
.await
|
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
|
||||||
.map_err(ApiError::DatabaseError)?;
|
.await
|
||||||
|
.map_err(ApiError::DatabaseError)?;
|
||||||
|
|
||||||
if team_member.is_some() {
|
if team_member.is_some() {
|
||||||
let team_members: Vec<_> = members_data
|
let team_members: Vec<_> = members_data
|
||||||
@@ -96,8 +110,12 @@ pub async fn join_team(
|
|||||||
let team_id = info.into_inner().0.into();
|
let team_id = info.into_inner().0.into();
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
let member =
|
let member = TeamMember::get_from_user_id_pending(
|
||||||
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
|
team_id,
|
||||||
|
current_user.id.into(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(member) = member {
|
if let Some(member) = member {
|
||||||
if member.accepted {
|
if member.accepted {
|
||||||
@@ -153,17 +171,20 @@ pub async fn add_team_member(
|
|||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).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)
|
let member =
|
||||||
.await?
|
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::CustomAuthenticationError(
|
.ok_or_else(|| {
|
||||||
"You don't have permission to edit members of this team".to_string(),
|
ApiError::CustomAuthenticationError(
|
||||||
)
|
"You don't have permission to edit members of this team"
|
||||||
})?;
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
if !member.permissions.contains(Permissions::MANAGE_INVITES) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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) {
|
if !member.permissions.contains(new_member.permissions) {
|
||||||
@@ -191,16 +212,23 @@ pub async fn add_team_member(
|
|||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::InvalidInputError(
|
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)
|
crate::database::models::User::get(member.user_id, &**pool)
|
||||||
.await?
|
.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 {
|
TeamMember {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
team_id,
|
team_id,
|
||||||
@@ -232,11 +260,18 @@ pub async fn add_team_member(
|
|||||||
"Team invite from {} to join the team for project {}",
|
"Team invite from {} to join the team for project {}",
|
||||||
current_user.username, result.title
|
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![
|
actions: vec![
|
||||||
NotificationActionBuilder {
|
NotificationActionBuilder {
|
||||||
title: "Accept".to_string(),
|
title: "Accept".to_string(),
|
||||||
action_route: ("POST".to_string(), format!("team/{}/join", team)),
|
action_route: (
|
||||||
|
"POST".to_string(),
|
||||||
|
format!("team/{}/join", team),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
NotificationActionBuilder {
|
NotificationActionBuilder {
|
||||||
title: "Deny".to_string(),
|
title: "Deny".to_string(),
|
||||||
@@ -273,20 +308,24 @@ pub async fn edit_team_member(
|
|||||||
let user_id = ids.1.into();
|
let user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member =
|
||||||
.await?
|
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::CustomAuthenticationError(
|
.ok_or_else(|| {
|
||||||
"You don't have permission to edit members of this team".to_string(),
|
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(|| {
|
let edit_member_db =
|
||||||
ApiError::CustomAuthenticationError(
|
TeamMember::get_from_user_id_pending(id, user_id, &**pool)
|
||||||
"You don't have permission to edit members of this team".to_string(),
|
.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?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
@@ -298,14 +337,16 @@ pub async fn edit_team_member(
|
|||||||
|
|
||||||
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
if !member.permissions.contains(Permissions::EDIT_MEMBER) {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 let Some(new_permissions) = edit_member.permissions {
|
||||||
if !member.permissions.contains(new_permissions) {
|
if !member.permissions.contains(new_permissions) {
|
||||||
return Err(ApiError::InvalidInputError(
|
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 id = info.into_inner().0;
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
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)
|
let member = TeamMember::get_from_user_id(
|
||||||
.await?
|
id.into(),
|
||||||
.ok_or_else(|| {
|
current_user.id.into(),
|
||||||
ApiError::CustomAuthenticationError(
|
&**pool,
|
||||||
"You don't have permission to edit members of this team".to_string(),
|
)
|
||||||
)
|
.await?
|
||||||
})?;
|
.ok_or_else(|| {
|
||||||
let new_member = TeamMember::get_from_user_id(id.into(), new_owner.user_id.into(), &**pool)
|
ApiError::CustomAuthenticationError(
|
||||||
.await?
|
"You don't have permission to edit members of this team"
|
||||||
.ok_or_else(|| {
|
.to_string(),
|
||||||
ApiError::InvalidInputError("The new owner specified does not exist".to_string())
|
)
|
||||||
})?;
|
})?;
|
||||||
|
let new_member = TeamMember::get_from_user_id(
|
||||||
|
id.into(),
|
||||||
|
new_owner.user_id.into(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"The new owner specified does not exist".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if member.role != crate::models::teams::OWNER_ROLE {
|
if member.role != crate::models::teams::OWNER_ROLE {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 user_id = ids.1.into();
|
||||||
|
|
||||||
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
let current_user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
let member =
|
||||||
.await?
|
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
|
||||||
.ok_or_else(|| {
|
.await?
|
||||||
ApiError::CustomAuthenticationError(
|
.ok_or_else(|| {
|
||||||
"You don't have permission to edit members of this team".to_string(),
|
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 let Some(delete_member) = delete_member {
|
||||||
if delete_member.role == crate::models::teams::OWNER_ROLE {
|
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
|
// Members other than the owner can either leave the team, or be
|
||||||
// removed by a member with the REMOVE_MEMBER permission.
|
// removed by a member with the REMOVE_MEMBER permission.
|
||||||
if delete_member.user_id == member.user_id
|
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?;
|
TeamMember::delete(id, user_id, &**pool).await?;
|
||||||
} else {
|
} else {
|
||||||
@@ -440,7 +497,8 @@ pub async fn remove_team_member(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if delete_member.user_id == member.user_id
|
} 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
|
// This is a pending invite rather than a member, so the
|
||||||
// user being invited or team members with the MANAGE_INVITES
|
// 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?;
|
TeamMember::delete(id, user_id, &**pool).await?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ pub async fn forge_updates(
|
|||||||
|
|
||||||
let (id,) = info.into_inner();
|
let (id,) = info.into_inner();
|
||||||
|
|
||||||
let project = database::models::Project::get_full_from_slug_or_project_id(&id, &**pool)
|
let project = database::models::Project::get_full_from_slug_or_project_id(
|
||||||
.await?
|
&id, &**pool,
|
||||||
.ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?;
|
)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
@@ -38,7 +40,8 @@ pub async fn forge_updates(
|
|||||||
)
|
)
|
||||||
.await?;
|
.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));
|
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -48,7 +51,11 @@ pub async fn forge_updates(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut response = ForgeUpdates {
|
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(),
|
promos: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ pub async fn user_auth_get(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok().json(
|
||||||
.json(get_user_from_headers(req.headers(), &mut *pool.acquire().await?).await?))
|
get_user_from_headers(req.headers(), &mut *pool.acquire().await?)
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -41,7 +43,8 @@ pub async fn users_get(
|
|||||||
|
|
||||||
let users_data = User::get_many(user_ids, &**pool).await?;
|
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))
|
Ok(HttpResponse::Ok().json(users))
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,8 @@ pub async fn user_get(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let string = info.into_inner().0;
|
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;
|
let mut user_data;
|
||||||
|
|
||||||
@@ -82,9 +86,11 @@ pub async fn projects_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
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 {
|
if current_user.role.is_mod() || current_user.id == user_id {
|
||||||
User::get_projects_private(id, &**pool).await?
|
User::get_projects_private(id, &**pool).await?
|
||||||
} else {
|
} else {
|
||||||
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
|
User::get_projects(
|
||||||
|
id,
|
||||||
|
ProjectStatus::Approved.as_str(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
let response: Vec<_> =
|
||||||
.await?
|
crate::database::Project::get_many_full(project_data, &**pool)
|
||||||
.into_iter()
|
.await?
|
||||||
.map(Project::from)
|
.into_iter()
|
||||||
.collect();
|
.map(Project::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
} else {
|
} else {
|
||||||
@@ -152,13 +165,15 @@ pub async fn user_edit(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
|
|
||||||
new_user
|
new_user.validate().map_err(|err| {
|
||||||
.validate()
|
ApiError::ValidationError(validation_errors_to_string(err, None))
|
||||||
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
})?;
|
||||||
|
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
let user_id: UserId = id.into();
|
||||||
@@ -168,8 +183,10 @@ pub async fn user_edit(
|
|||||||
|
|
||||||
if let Some(username) = &new_user.username {
|
if let Some(username) = &new_user.username {
|
||||||
let user_option =
|
let user_option =
|
||||||
crate::database::models::User::get_id_from_username_or_id(username, &**pool)
|
crate::database::models::User::get_id_from_username_or_id(
|
||||||
.await?;
|
username, &**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if user_option.is_none() {
|
if user_option.is_none() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@@ -282,19 +299,23 @@ pub async fn user_icon_edit(
|
|||||||
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
|
||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> 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 cdn_url = dotenv::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
let id_option =
|
||||||
&*info.into_inner().0,
|
crate::database::models::User::get_id_from_username_or_id(
|
||||||
&**pool,
|
&*info.into_inner().0,
|
||||||
)
|
&**pool,
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if user.id != id.into() && !user.role.is_mod() {
|
if user.id != id.into() && !user.role.is_mod() {
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 =
|
let bytes = read_from_payload(
|
||||||
read_from_payload(&mut payload, 2097152, "Icons must be smaller than 2MiB").await?;
|
&mut payload,
|
||||||
|
2097152,
|
||||||
|
"Icons must be smaller than 2MiB",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let upload_data = file_host
|
let upload_data = file_host
|
||||||
.upload_file(
|
.upload_file(
|
||||||
@@ -374,9 +399,11 @@ pub async fn user_delete(
|
|||||||
removal_type: web::Query<RemovalType>,
|
removal_type: web::Query<RemovalType>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_mod() && user.id != id.into() {
|
if !user.role.is_mod() && user.id != id.into() {
|
||||||
@@ -389,9 +416,15 @@ pub async fn user_delete(
|
|||||||
|
|
||||||
let result;
|
let result;
|
||||||
if &*removal_type.removal_type == "full" {
|
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 {
|
} else {
|
||||||
result = crate::database::models::User::remove(id, &mut transaction).await?;
|
result =
|
||||||
|
crate::database::models::User::remove(id, &mut transaction)
|
||||||
|
.await?;
|
||||||
};
|
};
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
@@ -413,9 +446,11 @@ pub async fn user_follows(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_mod() && user.id != id.into() {
|
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>>()
|
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let projects: Vec<_> = crate::database::Project::get_many_full(project_ids, &**pool)
|
let projects: Vec<_> =
|
||||||
.await?
|
crate::database::Project::get_many_full(project_ids, &**pool)
|
||||||
.into_iter()
|
.await?
|
||||||
.map(Project::from)
|
.into_iter()
|
||||||
.collect();
|
.map(Project::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
} else {
|
} else {
|
||||||
@@ -460,9 +496,11 @@ pub async fn user_notifications(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_mod() && user.id != id.into() {
|
if !user.role.is_mod() && user.id != id.into() {
|
||||||
|
|||||||
@@ -30,15 +30,18 @@ pub async fn get_mods(
|
|||||||
count.count as i64
|
count.count as i64
|
||||||
)
|
)
|
||||||
.fetch_many(&**pool)
|
.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>>()
|
.try_collect::<Vec<database::models::ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let projects: Vec<_> = database::Project::get_many_full(project_ids, &**pool)
|
let projects: Vec<_> =
|
||||||
.await?
|
database::Project::get_many_full(project_ids, &**pool)
|
||||||
.into_iter()
|
.await?
|
||||||
.map(Project::from)
|
.into_iter()
|
||||||
.collect();
|
.map(Project::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(projects))
|
Ok(HttpResponse::Ok().json(projects))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::file_hosting::FileHost;
|
use crate::file_hosting::FileHost;
|
||||||
use crate::models::projects::SearchRequest;
|
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::projects::ProjectIds;
|
||||||
use crate::routes::ApiError;
|
use crate::routes::ApiError;
|
||||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||||
@@ -89,12 +91,14 @@ pub async fn mods_get(
|
|||||||
ids: web::Query<ProjectIds>,
|
ids: web::Query<ProjectIds>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let project_ids = serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
|
let project_ids =
|
||||||
.into_iter()
|
serde_json::from_str::<Vec<models::ids::ProjectId>>(&*ids.ids)?
|
||||||
.map(|x| x.into())
|
.into_iter()
|
||||||
.collect();
|
.map(|x| x.into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let projects_data = database::models::Project::get_many_full(project_ids, &**pool).await?;
|
let projects_data =
|
||||||
|
database::models::Project::get_many_full(project_ids, &**pool).await?;
|
||||||
|
|
||||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let 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::projects::{ProjectId, VersionId};
|
||||||
use crate::models::users::UserId;
|
use crate::models::users::UserId;
|
||||||
use crate::routes::ApiError;
|
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::web;
|
||||||
use actix_web::{get, post, HttpRequest, HttpResponse};
|
use actix_web::{get, post, HttpRequest, HttpResponse};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -56,24 +58,31 @@ pub async fn report_create(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let mut transaction = pool.begin().await?;
|
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();
|
let mut bytes = web::BytesMut::new();
|
||||||
while let Some(item) = body.next().await {
|
while let Some(item) = body.next().await {
|
||||||
bytes.extend_from_slice(&item.map_err(|_| {
|
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 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(
|
let report_type = crate::database::models::categories::ReportType::get_id(
|
||||||
&*new_report.report_type,
|
&*new_report.report_type,
|
||||||
&mut *transaction,
|
&mut *transaction,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.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 {
|
let mut report = crate::database::models::report_item::Report {
|
||||||
id,
|
id,
|
||||||
@@ -89,17 +98,29 @@ pub async fn report_create(
|
|||||||
match new_report.item_type {
|
match new_report.item_type {
|
||||||
ItemType::Mod => {
|
ItemType::Mod => {
|
||||||
report.project_id = Some(
|
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 => {
|
ItemType::Version => {
|
||||||
report.version_id = Some(
|
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 => {
|
ItemType::User => {
|
||||||
report.user_id = Some(
|
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 => {
|
ItemType::Unknown => {
|
||||||
@@ -160,8 +181,10 @@ pub async fn reports(
|
|||||||
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let query_reports =
|
let query_reports = crate::database::models::report_item::Report::get_many(
|
||||||
crate::database::models::report_item::Report::get_many(report_ids, &**pool).await?;
|
report_ids, &**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut reports = Vec::new();
|
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::routes::ApiError;
|
||||||
use crate::util::auth::check_is_admin_from_headers;
|
use crate::util::auth::check_is_admin_from_headers;
|
||||||
use actix_web::{get, put, web};
|
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>"#;
|
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")]
|
#[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)
|
let results = Category::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -28,11 +32,16 @@ pub async fn category_create(
|
|||||||
|
|
||||||
let name = category.into_inner().0;
|
let name = category.into_inner().0;
|
||||||
|
|
||||||
let project_type = crate::database::models::ProjectTypeId::get_id("mod".to_string(), &**pool)
|
let project_type = crate::database::models::ProjectTypeId::get_id(
|
||||||
.await?
|
"mod".to_string(),
|
||||||
.ok_or_else(|| {
|
&**pool,
|
||||||
ApiError::InvalidInputError("Specified project type does not exist!".to_string())
|
)
|
||||||
})?;
|
.await?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidInputError(
|
||||||
|
"Specified project type does not exist!".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let _id = Category::builder()
|
let _id = Category::builder()
|
||||||
.name(&name)?
|
.name(&name)?
|
||||||
@@ -45,7 +54,9 @@ pub async fn category_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("loader")]
|
#[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)
|
let results = Loader::list(&**pool)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -67,12 +78,16 @@ pub async fn loader_create(
|
|||||||
let name = loader.into_inner().0;
|
let name = loader.into_inner().0;
|
||||||
let mut transaction = pool.begin().await?;
|
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()
|
let _id = Loader::builder()
|
||||||
.name(&name)?
|
.name(&name)?
|
||||||
.icon(DEFAULT_ICON)?
|
.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)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -92,11 +107,15 @@ pub async fn game_version_list(
|
|||||||
query: web::Query<GameVersionQueryData>,
|
query: web::Query<GameVersionQueryData>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if query.type_.is_some() || query.major.is_some() {
|
if query.type_.is_some() || query.major.is_some() {
|
||||||
let results = GameVersion::list_filter(query.type_.as_deref(), query.major, &**pool)
|
let results = GameVersion::list_filter(
|
||||||
.await?
|
query.type_.as_deref(),
|
||||||
.into_iter()
|
query.major,
|
||||||
.map(|x| x.version)
|
&**pool,
|
||||||
.collect::<Vec<String>>();
|
)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.version)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
} else {
|
} else {
|
||||||
let results = GameVersion::list(&**pool)
|
let results = GameVersion::list(&**pool)
|
||||||
|
|||||||
@@ -29,18 +29,20 @@ pub async fn team_members_get(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let id = info.into_inner().0;
|
let id = info.into_inner().0;
|
||||||
let members_data =
|
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();
|
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
if let Some(user) = current_user {
|
if let Some(user) = current_user {
|
||||||
let team_member = crate::database::models::TeamMember::get_from_user_id(
|
let team_member =
|
||||||
id.into(),
|
crate::database::models::TeamMember::get_from_user_id(
|
||||||
user.id.into(),
|
id.into(),
|
||||||
&**pool,
|
user.id.into(),
|
||||||
)
|
&**pool,
|
||||||
.await
|
)
|
||||||
.map_err(ApiError::DatabaseError)?;
|
.await
|
||||||
|
.map_err(ApiError::DatabaseError)?;
|
||||||
|
|
||||||
if team_member.is_some() {
|
if team_member.is_some() {
|
||||||
let team_members: Vec<TeamMember> = members_data
|
let team_members: Vec<TeamMember> = members_data
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ pub async fn mods_list(
|
|||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
let user = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||||
|
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
let user_id: UserId = id.into();
|
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 {
|
if current_user.role.is_mod() || current_user.id == user_id {
|
||||||
User::get_projects_private(id, &**pool).await?
|
User::get_projects_private(id, &**pool).await?
|
||||||
} else {
|
} else {
|
||||||
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
|
User::get_projects(
|
||||||
|
id,
|
||||||
|
ProjectStatus::Approved.as_str(),
|
||||||
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
let response = project_data
|
||||||
@@ -50,9 +58,11 @@ pub async fn user_follows(
|
|||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let id_option =
|
let id_option = crate::database::models::User::get_id_from_username_or_id(
|
||||||
crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool)
|
&*info.into_inner().0,
|
||||||
.await?;
|
&**pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if let Some(id) = id_option {
|
if let Some(id) = id_option {
|
||||||
if !user.role.is_mod() && user.id != id.into() {
|
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,
|
id as crate::database::models::ids::UserId,
|
||||||
)
|
)
|
||||||
.fetch_many(&**pool)
|
.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>>()
|
.try_collect::<Vec<ProjectId>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use crate::database::models;
|
use crate::database::models;
|
||||||
use crate::database::models::notification_item::NotificationBuilder;
|
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::file_hosting::FileHost;
|
||||||
use crate::models::projects::{
|
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::models::teams::Permissions;
|
||||||
use crate::routes::project_creation::{CreateError, UploadedFile};
|
use crate::routes::project_creation::{CreateError, UploadedFile};
|
||||||
@@ -74,8 +77,11 @@ pub async fn version_create(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
let undo_result =
|
let undo_result = super::project_creation::undo_uploads(
|
||||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
&***file_host,
|
||||||
|
&uploaded_files,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let rollback_result = transaction.rollback().await;
|
let rollback_result = transaction.rollback().await;
|
||||||
|
|
||||||
if let Err(e) = undo_result {
|
if let Err(e) = undo_result {
|
||||||
@@ -103,25 +109,30 @@ async fn version_create_inner(
|
|||||||
let mut initial_version_data = None;
|
let mut initial_version_data = None;
|
||||||
let mut version_builder = None;
|
let mut version_builder = None;
|
||||||
|
|
||||||
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
|
let all_game_versions =
|
||||||
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
|
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?;
|
let user = get_user_from_headers(req.headers(), &mut *transaction).await?;
|
||||||
|
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
let mut field: Field = item.map_err(CreateError::MultipartError)?;
|
let mut field: Field = item.map_err(CreateError::MultipartError)?;
|
||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
let name = content_disposition
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
.get_name()
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
|
})?;
|
||||||
|
|
||||||
if name == "data" {
|
if name == "data" {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
while let Some(chunk) = field.next().await {
|
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);
|
initial_version_data = Some(version_create_data);
|
||||||
let version_create_data = initial_version_data.as_ref().unwrap();
|
let version_create_data = initial_version_data.as_ref().unwrap();
|
||||||
if version_create_data.project_id.is_none() {
|
if version_create_data.project_id.is_none() {
|
||||||
@@ -131,10 +142,13 @@ async fn version_create_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
version_create_data.validate().map_err(|err| {
|
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
|
// Ensure that the project this version is being added to exists
|
||||||
let results = sqlx::query!(
|
let results = sqlx::query!(
|
||||||
@@ -162,7 +176,8 @@ async fn version_create_inner(
|
|||||||
|
|
||||||
if results.exists.unwrap_or(true) {
|
if results.exists.unwrap_or(true) {
|
||||||
return Err(CreateError::InvalidInput(
|
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?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
CreateError::CustomAuthenticationError(
|
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)
|
.contains(Permissions::UPLOAD_VERSION)
|
||||||
{
|
{
|
||||||
return Err(CreateError::CustomAuthenticationError(
|
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!(
|
let project_type = sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -210,7 +228,9 @@ async fn version_create_inner(
|
|||||||
all_game_versions
|
all_game_versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|y| y.version == x.0)
|
.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)
|
.map(|y| y.id)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<models::GameVersionId>, CreateError>>()?;
|
.collect::<Result<Vec<models::GameVersionId>, CreateError>>()?;
|
||||||
@@ -222,7 +242,9 @@ async fn version_create_inner(
|
|||||||
all_loaders
|
all_loaders
|
||||||
.iter()
|
.iter()
|
||||||
.find(|y| {
|
.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()))
|
.ok_or_else(|| CreateError::InvalidLoader(x.0.clone()))
|
||||||
.map(|y| y.id)
|
.map(|y| y.id)
|
||||||
@@ -261,7 +283,9 @@ async fn version_create_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let version = version_builder.as_mut().ok_or_else(|| {
|
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!(
|
let project_type = sqlx::query!(
|
||||||
@@ -276,9 +300,9 @@ async fn version_create_inner(
|
|||||||
.await?
|
.await?
|
||||||
.name;
|
.name;
|
||||||
|
|
||||||
let version_data = initial_version_data
|
let version_data = initial_version_data.clone().ok_or_else(|| {
|
||||||
.clone()
|
CreateError::InvalidInput("`data` field is required".to_string())
|
||||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
})?;
|
||||||
|
|
||||||
upload_file(
|
upload_file(
|
||||||
&mut field,
|
&mut field,
|
||||||
@@ -300,10 +324,12 @@ async fn version_create_inner(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let version_data = initial_version_data
|
let version_data = initial_version_data.ok_or_else(|| {
|
||||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
CreateError::InvalidInput("`data` field is required".to_string())
|
||||||
let builder = version_builder
|
})?;
|
||||||
.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() {
|
if builder.files.is_empty() {
|
||||||
return Err(CreateError::InvalidInput(
|
return Err(CreateError::InvalidInput(
|
||||||
@@ -433,8 +459,11 @@ pub async fn upload_file_to_version(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
let undo_result =
|
let undo_result = super::project_creation::undo_uploads(
|
||||||
super::project_creation::undo_uploads(&***file_host, &uploaded_files).await;
|
&***file_host,
|
||||||
|
&uploaded_files,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let rollback_result = transaction.rollback().await;
|
let rollback_result = transaction.rollback().await;
|
||||||
|
|
||||||
if let Err(e) = undo_result {
|
if let Err(e) = undo_result {
|
||||||
@@ -477,21 +506,26 @@ async fn upload_file_to_version_inner(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let team_member =
|
let team_member = models::TeamMember::get_from_user_id_version(
|
||||||
models::TeamMember::get_from_user_id_version(version_id, user.id.into(), &mut *transaction)
|
version_id,
|
||||||
.await?
|
user.id.into(),
|
||||||
.ok_or_else(|| {
|
&mut *transaction,
|
||||||
CreateError::CustomAuthenticationError(
|
)
|
||||||
"You don't have permission to upload files to this version!".to_string(),
|
.await?
|
||||||
)
|
.ok_or_else(|| {
|
||||||
})?;
|
CreateError::CustomAuthenticationError(
|
||||||
|
"You don't have permission to upload files to this version!"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !team_member
|
if !team_member
|
||||||
.permissions
|
.permissions
|
||||||
.contains(Permissions::UPLOAD_VERSION)
|
.contains(Permissions::UPLOAD_VERSION)
|
||||||
{
|
{
|
||||||
return Err(CreateError::CustomAuthenticationError(
|
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?
|
.await?
|
||||||
.name;
|
.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 {
|
while let Some(item) = payload.next().await {
|
||||||
let mut field: Field = item.map_err(CreateError::MultipartError)?;
|
let mut field: Field = item.map_err(CreateError::MultipartError)?;
|
||||||
let content_disposition = field.content_disposition().clone();
|
let content_disposition = field.content_disposition().clone();
|
||||||
let name = content_disposition
|
let name = content_disposition.get_name().ok_or_else(|| {
|
||||||
.get_name()
|
CreateError::MissingValueError("Missing content name".to_string())
|
||||||
.ok_or_else(|| CreateError::MissingValueError("Missing content name".to_string()))?;
|
})?;
|
||||||
|
|
||||||
if name == "data" {
|
if name == "data" {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
while let Some(chunk) = field.next().await {
|
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)?;
|
let file_data: InitialFileData = serde_json::from_slice(&data)?;
|
||||||
// TODO: currently no data here, but still required
|
// 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(|| {
|
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(
|
upload_file(
|
||||||
@@ -596,7 +635,9 @@ pub async fn upload_file(
|
|||||||
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
||||||
|
|
||||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
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(
|
let data = read_from_field(
|
||||||
field, 100 * (1 << 20),
|
field, 100 * (1 << 20),
|
||||||
@@ -619,7 +660,8 @@ pub async fn upload_file(
|
|||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
return Err(CreateError::InvalidInput(
|
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(
|
pub fn get_name_ext(
|
||||||
content_disposition: &actix_web::http::header::ContentDisposition,
|
content_disposition: &actix_web::http::header::ContentDisposition,
|
||||||
) -> Result<(&str, &str), CreateError> {
|
) -> Result<(&str, &str), CreateError> {
|
||||||
let file_name = content_disposition
|
let file_name = content_disposition.get_filename().ok_or_else(|| {
|
||||||
.get_filename()
|
CreateError::MissingValueError("Missing content file name".to_string())
|
||||||
.ok_or_else(|| CreateError::MissingValueError("Missing content file name".to_string()))?;
|
})?;
|
||||||
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
let file_extension = if let Some(last_period) = file_name.rfind('.') {
|
||||||
file_name.get((last_period + 1)..).unwrap_or("")
|
file_name.get((last_period + 1)..).unwrap_or("")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -128,25 +128,28 @@ pub async fn delete_file(
|
|||||||
|
|
||||||
if let Some(row) = result {
|
if let Some(row) = result {
|
||||||
if !user.role.is_mod() {
|
if !user.role.is_mod() {
|
||||||
let team_member = database::models::TeamMember::get_from_user_id_version(
|
let team_member =
|
||||||
database::models::ids::VersionId(row.version_id),
|
database::models::TeamMember::get_from_user_id_version(
|
||||||
user.id.into(),
|
database::models::ids::VersionId(row.version_id),
|
||||||
&**pool,
|
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(),
|
|
||||||
)
|
)
|
||||||
})?;
|
.await
|
||||||
|
.map_err(ApiError::DatabaseError)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::CustomAuthenticationError(
|
||||||
|
"You don't have permission to delete this file!"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if !team_member
|
if !team_member
|
||||||
.permissions
|
.permissions
|
||||||
.contains(Permissions::DELETE_VERSION)
|
.contains(Permissions::DELETE_VERSION)
|
||||||
{
|
{
|
||||||
return Err(ApiError::CustomAuthenticationError(
|
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 {
|
if files.len() < 2 {
|
||||||
return Err(ApiError::InvalidInputError(
|
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?;
|
.await?;
|
||||||
|
|
||||||
if let Some(version_id) = version_ids.last() {
|
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)
|
ok_or_not_found::<QueryVersion, Version>(version_data)
|
||||||
} else {
|
} 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();
|
let mut response = HashMap::new();
|
||||||
|
|
||||||
for row in &result {
|
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(
|
response.insert(
|
||||||
hex::encode(&row.hash),
|
hex::encode(&row.hash),
|
||||||
models::projects::Version::from(version.clone()),
|
models::projects::Version::from(version.clone()),
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ impl Scheduler {
|
|||||||
F: FnMut() -> R + Send + 'static,
|
F: FnMut() -> R + Send + 'static,
|
||||||
R: std::future::Future<Output = ()> + Send + 'static,
|
R: std::future::Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
let future =
|
let future = IntervalStream::new(time::interval(interval))
|
||||||
IntervalStream::new(time::interval(interval)).for_each_concurrent(2, move |_| task());
|
.for_each_concurrent(2, move |_| task());
|
||||||
|
|
||||||
self.arbiter.spawn(future);
|
self.arbiter.spawn(future);
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,9 @@ pub fn schedule_versions(
|
|||||||
pool: sqlx::Pool<sqlx::Postgres>,
|
pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
skip_initial: bool,
|
skip_initial: bool,
|
||||||
) {
|
) {
|
||||||
let version_index_interval =
|
let version_index_interval = std::time::Duration::from_secs(
|
||||||
std::time::Duration::from_secs(parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800));
|
parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800),
|
||||||
|
);
|
||||||
|
|
||||||
let mut skip = skip_initial;
|
let mut skip = skip_initial;
|
||||||
scheduler.run(version_index_interval, move || {
|
scheduler.run(version_index_interval, move || {
|
||||||
@@ -90,11 +91,15 @@ struct VersionFormat<'a> {
|
|||||||
release_time: chrono::DateTime<chrono::Utc>,
|
release_time: chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> Result<(), VersionIndexingError> {
|
async fn update_versions(
|
||||||
let input = reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
pool: &sqlx::Pool<sqlx::Postgres>,
|
||||||
.await?
|
) -> Result<(), VersionIndexingError> {
|
||||||
.json::<InputFormat>()
|
let input = reqwest::get(
|
||||||
.await?;
|
"https://launchermeta.mojang.com/mc/game/version_manifest.json",
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.json::<InputFormat>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut skipped_versions_count = 0u32;
|
let mut skipped_versions_count = 0u32;
|
||||||
|
|
||||||
@@ -156,7 +161,8 @@ async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> Result<(), Versio
|
|||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
.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);
|
name = String::from(*alternate);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use crate::search::UploadSearchProject;
|
|||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
|
||||||
// TODO: Move this away from STRING_AGG to multiple queries - however this may be more efficient?
|
// 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!");
|
info!("Indexing local projects!");
|
||||||
Ok(
|
Ok(
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
|
|||||||
@@ -88,15 +88,25 @@ async fn update_index_helper<'a>(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reconfigure_indices(config: &SearchConfig) -> Result<(), IndexingError> {
|
pub async fn reconfigure_indices(
|
||||||
|
config: &SearchConfig,
|
||||||
|
) -> Result<(), IndexingError> {
|
||||||
let client = config.make_client();
|
let client = config.make_client();
|
||||||
|
|
||||||
// Relevance Index
|
// Relevance Index
|
||||||
update_index_helper(&client, "relevance_projects", "desc(downloads)").await?;
|
update_index_helper(&client, "relevance_projects", "desc(downloads)")
|
||||||
update_index_helper(&client, "downloads_projects", "desc(downloads)").await?;
|
.await?;
|
||||||
|
update_index_helper(&client, "downloads_projects", "desc(downloads)")
|
||||||
|
.await?;
|
||||||
update_index_helper(&client, "follows_projects", "desc(follows)").await?;
|
update_index_helper(&client, "follows_projects", "desc(follows)").await?;
|
||||||
update_index_helper(&client, "updated_projects", "desc(modified_timestamp)").await?;
|
update_index_helper(
|
||||||
update_index_helper(&client, "newest_projects", "desc(created_timestamp)").await?;
|
&client,
|
||||||
|
"updated_projects",
|
||||||
|
"desc(modified_timestamp)",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
update_index_helper(&client, "newest_projects", "desc(created_timestamp)")
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
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) {
|
for chunk in mods.chunks(MEILISEARCH_CHUNK_SIZE) {
|
||||||
index.add_documents(chunk, Some("project_id")).await?;
|
index.add_documents(chunk, Some("project_id")).await?;
|
||||||
}
|
}
|
||||||
@@ -179,9 +192,27 @@ pub async fn add_projects(
|
|||||||
) -> Result<(), IndexingError> {
|
) -> Result<(), IndexingError> {
|
||||||
let client = config.make_client();
|
let client = config.make_client();
|
||||||
|
|
||||||
create_and_add_to_index(&client, &projects, "relevance_projects", "desc(downloads)").await?;
|
create_and_add_to_index(
|
||||||
create_and_add_to_index(&client, &projects, "downloads_projects", "desc(downloads)").await?;
|
&client,
|
||||||
create_and_add_to_index(&client, &projects, "follows_projects", "desc(follows)").await?;
|
&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(
|
create_and_add_to_index(
|
||||||
&client,
|
&client,
|
||||||
&projects,
|
&projects,
|
||||||
|
|||||||
@@ -21,9 +21,15 @@ impl CreationQueue {
|
|||||||
self.queue.lock().unwrap().push(search_project);
|
self.queue.lock().unwrap().push(search_project);
|
||||||
}
|
}
|
||||||
pub fn take(&self) -> Vec<UploadSearchProject> {
|
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();
|
let queue = self.take();
|
||||||
add_projects(queue, config).await
|
add_projects(queue, config).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,12 +149,13 @@ pub async fn search_for_project(
|
|||||||
) -> Result<SearchResults, SearchError> {
|
) -> Result<SearchResults, SearchError> {
|
||||||
let client = Client::new(&*config.address, &*config.key);
|
let client = Client::new(&*config.address, &*config.key);
|
||||||
|
|
||||||
let filters: Cow<_> = match (info.filters.as_deref(), info.version.as_deref()) {
|
let filters: Cow<_> =
|
||||||
(Some(f), Some(v)) => format!("({}) AND ({})", f, v).into(),
|
match (info.filters.as_deref(), info.version.as_deref()) {
|
||||||
(Some(f), None) => f.into(),
|
(Some(f), Some(v)) => format!("({}) AND ({})", f, v).into(),
|
||||||
(None, Some(v)) => v.into(),
|
(Some(f), None) => f.into(),
|
||||||
(None, None) => "".into(),
|
(None, Some(v)) => v.into(),
|
||||||
};
|
(None, None) => "".into(),
|
||||||
|
};
|
||||||
|
|
||||||
let offset = info.offset.as_deref().unwrap_or("0").parse()?;
|
let offset = info.offset.as_deref().unwrap_or("0").parse()?;
|
||||||
let index = info.index.as_deref().unwrap_or("relevance");
|
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 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 {
|
match res {
|
||||||
Some(result) => Ok(User {
|
Some(result) => Ok(User {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ pub async fn read_from_payload(
|
|||||||
return Err(ApiError::InvalidInputError(String::from(err_msg)));
|
return Err(ApiError::InvalidInputError(String::from(err_msg)));
|
||||||
} else {
|
} else {
|
||||||
bytes.extend_from_slice(&item.map_err(|_| {
|
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 {
|
if bytes.len() >= cap {
|
||||||
return Err(CreateError::InvalidInput(String::from(err_msg)));
|
return Err(CreateError::InvalidInput(String::from(err_msg)));
|
||||||
} else {
|
} else {
|
||||||
bytes.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
bytes.extend_from_slice(
|
||||||
|
&chunk.map_err(CreateError::MultipartError)?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(bytes)
|
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
|
where
|
||||||
U: From<T> + Serialize,
|
U: From<T> + Serialize,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ use regex::Regex;
|
|||||||
use validator::{ValidationErrors, ValidationErrorsKind};
|
use validator::{ValidationErrors, ValidationErrorsKind};
|
||||||
|
|
||||||
lazy_static! {
|
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!
|
//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 mut output = String::new();
|
||||||
|
|
||||||
let map = errors.into_errors();
|
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) {
|
if let Some(error) = map.get(field) {
|
||||||
return match error {
|
return match error {
|
||||||
ValidationErrorsKind::Struct(errors) => {
|
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) => {
|
ValidationErrorsKind::List(list) => {
|
||||||
if let Some((index, errors)) = list.iter().next() {
|
if let Some((index, errors)) = list.iter().next() {
|
||||||
output.push_str(&*validation_errors_to_string(
|
output.push_str(&*validation_errors_to_string(
|
||||||
*errors.clone(),
|
*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 chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
@@ -31,13 +33,14 @@ impl super::Validator for FabricValidator {
|
|||||||
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
||||||
) -> Result<ValidationResult, ValidationError> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
archive.by_name("fabric.mod.json").map_err(|_| {
|
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
|
if !archive.file_names().any(|name| {
|
||||||
.file_names()
|
name.ends_with("refmap.json") || name.ends_with(".class")
|
||||||
.any(|name| name.ends_with("refmap.json") || name.ends_with(".class"))
|
}) {
|
||||||
{
|
|
||||||
return Ok(ValidationResult::Warning(
|
return Ok(ValidationResult::Warning(
|
||||||
"Fabric mod file is a source file!",
|
"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 chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
@@ -31,7 +33,9 @@ impl super::Validator for ForgeValidator {
|
|||||||
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
||||||
) -> Result<ValidationResult, ValidationError> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
archive.by_name("META-INF/mods.toml").map_err(|_| {
|
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")) {
|
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 {
|
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
|
// Times between versions 1.5.2 to 1.12.2, which all use the legacy way of defining mods
|
||||||
SupportedGameVersions::Range(
|
SupportedGameVersions::Range(
|
||||||
DateTime::from_utc(NaiveDateTime::from_timestamp(1366818300, 0), Utc),
|
DateTime::from_utc(
|
||||||
DateTime::from_utc(NaiveDateTime::from_timestamp(1505810340, 0), 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>>,
|
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
||||||
) -> Result<ValidationResult, ValidationError> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
archive.by_name("mcmod.info").map_err(|_| {
|
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")) {
|
if !archive.file_names().any(|name| name.ends_with(".class")) {
|
||||||
|
|||||||
@@ -114,20 +114,24 @@ fn game_version_supported(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
match supported_game_versions {
|
match supported_game_versions {
|
||||||
SupportedGameVersions::All => true,
|
SupportedGameVersions::All => true,
|
||||||
SupportedGameVersions::PastDate(date) => game_versions.iter().any(|x| {
|
SupportedGameVersions::PastDate(date) => {
|
||||||
all_game_versions
|
game_versions.iter().any(|x| {
|
||||||
.iter()
|
all_game_versions
|
||||||
.find(|y| y.version == x.0)
|
.iter()
|
||||||
.map(|x| x.date > date)
|
.find(|y| y.version == x.0)
|
||||||
.unwrap_or(false)
|
.map(|x| x.date > date)
|
||||||
}),
|
.unwrap_or(false)
|
||||||
SupportedGameVersions::Range(before, after) => game_versions.iter().any(|x| {
|
})
|
||||||
all_game_versions
|
}
|
||||||
.iter()
|
SupportedGameVersions::Range(before, after) => {
|
||||||
.find(|y| y.version == x.0)
|
game_versions.iter().any(|x| {
|
||||||
.map(|x| x.date > before && x.date < after)
|
all_game_versions
|
||||||
.unwrap_or(false)
|
.iter()
|
||||||
}),
|
.find(|y| y.version == x.0)
|
||||||
|
.map(|x| x.date > before && x.date < after)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
SupportedGameVersions::Custom(versions) => {
|
SupportedGameVersions::Custom(versions) => {
|
||||||
versions.iter().any(|x| game_versions.contains(x))
|
versions.iter().any(|x| game_versions.contains(x))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::models::projects::SideType;
|
use crate::models::projects::SideType;
|
||||||
use crate::util::env::parse_strings_from_var;
|
use crate::util::env::parse_strings_from_var;
|
||||||
use crate::util::validate::validation_errors_to_string;
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use std::path::Component;
|
use std::path::Component;
|
||||||
@@ -33,7 +35,9 @@ pub struct PackFile<'a> {
|
|||||||
pub downloads: Vec<&'a str>,
|
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 {
|
for value in values {
|
||||||
let url = url::Url::parse(value)
|
let url = url::Url::parse(value)
|
||||||
.ok()
|
.ok()
|
||||||
@@ -43,7 +47,8 @@ fn validate_download_url(values: &[&str]) -> Result<(), validator::ValidationErr
|
|||||||
return Err(validator::ValidationError::new("invalid URL"));
|
return Err(validator::ValidationError::new("invalid URL"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").unwrap_or_default();
|
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS")
|
||||||
|
.unwrap_or_default();
|
||||||
if !domains.contains(
|
if !domains.contains(
|
||||||
&url.domain()
|
&url.domain()
|
||||||
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
||||||
@@ -131,9 +136,12 @@ impl super::Validator for PackValidator {
|
|||||||
&self,
|
&self,
|
||||||
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
|
||||||
) -> Result<ValidationResult, ValidationError> {
|
) -> Result<ValidationResult, ValidationError> {
|
||||||
let mut file = archive
|
let mut file =
|
||||||
.by_name("modrinth.index.json")
|
archive.by_name("modrinth.index.json").map_err(|_| {
|
||||||
.map_err(|_| ValidationError::InvalidInputError("Pack manifest is missing.".into()))?;
|
ValidationError::InvalidInputError(
|
||||||
|
"Pack manifest is missing.".into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
@@ -141,7 +149,9 @@ impl super::Validator for PackValidator {
|
|||||||
let pack: PackFormat = serde_json::from_str(&contents)?;
|
let pack: PackFormat = serde_json::from_str(&contents)?;
|
||||||
|
|
||||||
pack.validate().map_err(|err| {
|
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" {
|
if pack.game != "minecraft" {
|
||||||
@@ -161,7 +171,9 @@ impl super::Validator for PackValidator {
|
|||||||
.components()
|
.components()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ValidationError::InvalidInputError("Invalid pack file path!".into())
|
ValidationError::InvalidInputError(
|
||||||
|
"Invalid pack file path!".into(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match path {
|
match path {
|
||||||
|
|||||||
Reference in New Issue
Block a user