You've already forked AstralRinth
forked from didirus/AstralRinth
Force files to be unique, require all new versions to have at least one file (#236)
This commit is contained in:
@@ -1057,6 +1057,27 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"4298552497a48adb9ace61c8dcf989c4d35866866b61c0cc4d45909b1d31c660": {
|
||||||
|
"query": "\n SELECT EXISTS(SELECT 1 FROM hashes h\n WHERE h.algorithm = $2 AND h.hash = $1)\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "exists",
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Bytea",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"436dbf448697436ec90c30f44b27c92ec626601e7a7a9edb4d11bd916741b60f": {
|
"436dbf448697436ec90c30f44b27c92ec626601e7a7a9edb4d11bd916741b60f": {
|
||||||
"query": "\n UPDATE mods\n SET icon_url = NULL\n WHERE (id = $1)\n ",
|
"query": "\n UPDATE mods\n SET icon_url = NULL\n WHERE (id = $1)\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -5082,6 +5103,26 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"e29da865af4a0a110275b9756394546a3bb88bff40e18c66029651f515caed98": {
|
||||||
|
"query": "\n SELECT f.id id FROM files f\n WHERE f.version_id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"e3235e872f98eb85d3eb4a2518fb9dc88049ce62362bfd02623e9b49ac2e9fed": {
|
"e3235e872f98eb85d3eb4a2518fb9dc88049ce62362bfd02623e9b49ac2e9fed": {
|
||||||
"query": "\n SELECT name FROM report_types\n ",
|
"query": "\n SELECT name FROM report_types\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ Get logged in user
|
|||||||
pub async fn project_create_inner(
|
pub async fn project_create_inner(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
@@ -512,6 +512,7 @@ pub async fn project_create_inner(
|
|||||||
version_data.game_versions.clone(),
|
version_data.game_versions.clone(),
|
||||||
&all_game_versions,
|
&all_game_versions,
|
||||||
false,
|
false,
|
||||||
|
&mut transaction,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ pub async fn version_create(
|
|||||||
async fn version_create_inner(
|
async fn version_create_inner(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
) -> Result<HttpResponse, CreateError> {
|
) -> Result<HttpResponse, CreateError> {
|
||||||
@@ -289,6 +289,7 @@ async fn version_create_inner(
|
|||||||
version_data.game_versions,
|
version_data.game_versions,
|
||||||
&all_game_versions,
|
&all_game_versions,
|
||||||
false,
|
false,
|
||||||
|
&mut transaction,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -298,6 +299,12 @@ async fn version_create_inner(
|
|||||||
let builder = version_builder
|
let builder = version_builder
|
||||||
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
.ok_or_else(|| CreateError::InvalidInput("`data` field is required".to_string()))?;
|
||||||
|
|
||||||
|
if builder.files.is_empty() {
|
||||||
|
return Err(CreateError::InvalidInput(
|
||||||
|
"Versions must have at least one file uploaded to them".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT m.title FROM mods m
|
SELECT m.title FROM mods m
|
||||||
@@ -434,7 +441,7 @@ pub async fn upload_file_to_version(
|
|||||||
async fn upload_file_to_version_inner(
|
async fn upload_file_to_version_inner(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
file_host: &dyn FileHost,
|
file_host: &dyn FileHost,
|
||||||
uploaded_files: &mut Vec<UploadedFile>,
|
uploaded_files: &mut Vec<UploadedFile>,
|
||||||
version_id: models::VersionId,
|
version_id: models::VersionId,
|
||||||
@@ -536,6 +543,7 @@ async fn upload_file_to_version_inner(
|
|||||||
.collect(),
|
.collect(),
|
||||||
&all_game_versions,
|
&all_game_versions,
|
||||||
true,
|
true,
|
||||||
|
&mut transaction,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -570,6 +578,7 @@ pub async fn upload_file(
|
|||||||
game_versions: Vec<GameVersion>,
|
game_versions: Vec<GameVersion>,
|
||||||
all_game_versions: &[models::categories::GameVersion],
|
all_game_versions: &[models::categories::GameVersion],
|
||||||
ignore_primary: bool,
|
ignore_primary: bool,
|
||||||
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), CreateError> {
|
) -> Result<(), CreateError> {
|
||||||
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
||||||
|
|
||||||
@@ -577,6 +586,7 @@ pub async fn upload_file(
|
|||||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
let mut hash = sha1::Sha1::new();
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
// Project file size limit of 100MiB
|
// Project file size limit of 100MiB
|
||||||
const FILE_SIZE_CAP: usize = 100 * (1 << 20);
|
const FILE_SIZE_CAP: usize = 100 * (1 << 20);
|
||||||
@@ -586,10 +596,32 @@ pub async fn upload_file(
|
|||||||
String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.")
|
String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.")
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
let bytes = chunk.map_err(CreateError::MultipartError)?;
|
||||||
|
hash.update(&data);
|
||||||
|
data.append(&mut bytes.to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hash = hash.digest().to_string();
|
||||||
|
let exists = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT EXISTS(SELECT 1 FROM hashes h
|
||||||
|
WHERE h.algorithm = $2 AND h.hash = $1)
|
||||||
|
",
|
||||||
|
hash.as_bytes(),
|
||||||
|
"sha1"
|
||||||
|
)
|
||||||
|
.fetch_one(&mut *transaction)
|
||||||
|
.await?
|
||||||
|
.exists
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return Err(CreateError::InvalidInput(
|
||||||
|
"Duplicate files are not allowed to be uploaded to Modrinth!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let validation_result = validate_file(
|
let validation_result = validate_file(
|
||||||
data.as_slice(),
|
data.as_slice(),
|
||||||
file_extension,
|
file_extension,
|
||||||
|
|||||||
@@ -240,6 +240,26 @@ pub async fn delete_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use futures::stream::TryStreamExt;
|
||||||
|
|
||||||
|
let files = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT f.id id FROM files f
|
||||||
|
WHERE f.version_id = $1
|
||||||
|
",
|
||||||
|
row.version_id
|
||||||
|
)
|
||||||
|
.fetch_many(&**pool)
|
||||||
|
.try_filter_map(|e| async { Ok(e.right().map(|_| ())) })
|
||||||
|
.try_collect::<Vec<()>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if files.len() < 2 {
|
||||||
|
return Err(ApiError::InvalidInputError(
|
||||||
|
"Versions must have at least one file uploaded to them".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
|
|||||||
Reference in New Issue
Block a user