diff --git a/sqlx-data.json b/sqlx-data.json index 55a71148..0ff8d55f 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -519,26 +519,6 @@ "nullable": [] } }, - "40597b84607e77809c13ffa9c6b0b1674bd6378a4737a8f6118e91ae2ede7e4a": { - "query": "\n SELECT id\n FROM release_channels\n WHERE channel = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - } - }, "4411f2aefd43881450da34db81e826110ac86c3a6cef9fd6a3e9e341508d1f09": { "query": "\n SELECT id FROM versions\n WHERE mod_id = $1\n ", "describe": { @@ -1498,6 +1478,26 @@ ] } }, + "d88b160963bffbf3297de4418a38f4c914819fff94b912fab451b892c7494370": { + "query": "\n SELECT id\n FROM release_channels\n WHERE channel = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + } + }, "d8b4e7e382c77a05395124d5a6a27cccb687d0e2c31b76d49b03aa364d099d42": { "query": "\n DELETE FROM files\n WHERE files.version_id = $1\n ", "describe": { diff --git a/src/routes/mod_creation.rs b/src/routes/mod_creation.rs index 1a0409b9..397a888f 100644 --- a/src/routes/mod_creation.rs +++ b/src/routes/mod_creation.rs @@ -181,7 +181,7 @@ async fn mod_create_inner( ) -> Result { let cdn_url = dotenv::var("CDN_URL")?; - let mod_id = models::generate_mod_id(transaction).await?.into(); + let mod_id: ModId = models::generate_mod_id(transaction).await?.into(); let user = get_user_from_headers(req.headers(), &mut *transaction).await?; let mut created_versions: Vec = vec![]; @@ -208,6 +208,80 @@ async fn mod_create_inner( check_length("mod_name", 3, 255, &*create_data.mod_name)?; check_length("mod_description", 3, 2048, &*create_data.mod_description)?; + for version_data in &create_data.initial_versions { + if version_data.mod_id.is_some() { + return Err(CreateError::InvalidInput(String::from( + "Found mod id in initial version for new mod", + ))); + } + let version_id: VersionId = models::generate_version_id(transaction).await?.into(); + + let body_url = format!("data/{}/changelogs/{}/body.md", mod_id, version_id); + + let uploaded_text = file_host + .upload_file( + "text/plain", + &body_url, + version_data.version_body.clone().into_bytes(), + ) + .await?; + + uploaded_files.push(UploadedFile { + file_id: uploaded_text.file_id.clone(), + file_name: uploaded_text.file_name.clone(), + }); + + let release_channel = models::ChannelId( + sqlx::query!( + " + SELECT id + FROM release_channels + WHERE channel = $1 + ", + version_data.release_channel.to_string() + ) + .fetch_one(&mut *transaction) + .await? + .id, + ); + + let mut game_versions = Vec::with_capacity(version_data.game_versions.len()); + for v in &version_data.game_versions { + let id = models::categories::GameVersion::get_id(&v.0, &mut *transaction) + .await? + .ok_or_else(|| CreateError::InvalidGameVersion(v.0.clone()))?; + game_versions.push(id); + } + + let mut loaders = Vec::with_capacity(version_data.loaders.len()); + for l in &version_data.loaders { + let id = models::categories::Loader::get_id(&l.0, &mut *transaction) + .await? + .ok_or_else(|| CreateError::InvalidLoader(l.0.clone()))?; + loaders.push(id); + } + + let version = models::version_item::VersionBuilder { + version_id: version_id.into(), + mod_id: mod_id.into(), + author_id: user.id.into(), + name: version_data.version_title.clone(), + version_number: version_data.version_number.clone(), + changelog_url: Some(format!("{}/{}", cdn_url, body_url)), + files: Vec::with_capacity(1), + dependencies: version_data + .dependencies + .iter() + .map(|x| (*x).into()) + .collect::>(), + game_versions, + loaders, + release_channel, + }; + + created_versions.push(version); + } + mod_create_data = Some(create_data); continue; } @@ -233,10 +307,11 @@ async fn mod_create_inner( continue; } - let version_data = create_data + let (version_index, version_data) = create_data .initial_versions .iter() - .find(|x| x.file_parts.iter().any(|n| n == name)) + .enumerate() + .find(|(_, x)| x.file_parts.iter().any(|n| n == name)) .ok_or_else(|| { CreateError::InvalidInput(format!( "File `{}` (field {}) isn't specified in the versions data", @@ -244,83 +319,15 @@ async fn mod_create_inner( )) })?; - // If a version has already been created for this version, add the - // file to it instead of creating a new version. - // Versions must have at least one jar file to be uploaded - - let created_version = if let Some(created_version) = created_versions - .iter_mut() - .find(|x| x.version_number == version_data.version_number) + let created_version = if let Some(created_version) = created_versions.get_mut(version_index) { created_version } else { - let version_id: VersionId = models::generate_version_id(transaction).await?.into(); - - let body_url = format!("data/{}/changelogs/{}/body.md", mod_id, version_id); - - let uploaded_text = file_host - .upload_file( - "text/plain", - &body_url, - version_data.version_body.clone().into_bytes(), - ) - .await?; - - uploaded_files.push(UploadedFile { - file_id: uploaded_text.file_id.clone(), - file_name: uploaded_text.file_name.clone(), - }); - - let release_channel = models::ChannelId( - sqlx::query!( - " - SELECT id - FROM release_channels - WHERE channel = $1 - ", - version_data.release_channel.to_string() - ) - .fetch_one(&mut *transaction) - .await? - .id, - ); - - let mut game_versions = Vec::with_capacity(version_data.game_versions.len()); - for v in &version_data.game_versions { - let id = models::categories::GameVersion::get_id(&v.0, &mut *transaction) - .await? - .ok_or_else(|| CreateError::InvalidGameVersion(v.0.clone()))?; - game_versions.push(id); - } - - let mut loaders = Vec::with_capacity(version_data.loaders.len()); - for l in &version_data.loaders { - let id = models::categories::Loader::get_id(&l.0, &mut *transaction) - .await? - .ok_or_else(|| CreateError::InvalidLoader(l.0.clone()))?; - loaders.push(id); - } - - let version = models::version_item::VersionBuilder { - version_id: version_id.into(), - mod_id: mod_id.into(), - author_id: user.id.into(), - name: version_data.version_title.clone(), - version_number: version_data.version_number.clone(), - changelog_url: Some(format!("{}/{}", cdn_url, body_url)), - files: Vec::with_capacity(1), - dependencies: version_data - .dependencies - .iter() - .map(|x| (*x).into()) - .collect::>(), - game_versions, - loaders, - release_channel, - }; - - created_versions.push(version); - created_versions.last_mut().unwrap() + // This shouldn't be reachable, but better safe than sorry + return Err(CreateError::InvalidInput(format!( + "File `{}` (field {}) isn't specified in the versions data", + file_name, name + ))); }; // Upload the new jar file @@ -342,11 +349,23 @@ async fn mod_create_inner( let create_data = if let Some(create_data) = mod_create_data { create_data } else { - return Err(CreateError::InvalidInput(String::from( + return Err(CreateError::MissingValueError(String::from( "Multipart upload missing `data` field", ))); }; + for (version_data, builder) in create_data + .initial_versions + .iter() + .zip(created_versions.iter()) + { + if version_data.file_parts.len() != builder.files.len() { + return Err(CreateError::InvalidInput(String::from( + "Some files were specified in initial_versions but not uploaded", + ))); + } + } + let ids: Vec = (&create_data.team_members) .iter() .map(|m| m.user_id) @@ -561,10 +580,11 @@ fn check_length( max_length: usize, string: &str, ) -> Result<(), CreateError> { - if string.len() > max_length || string.len() < min_length { + let length = string.len(); + if length > max_length || length < min_length { Err(CreateError::InvalidInput(format!( "The {} must be between {} and {} characters; got {}.", - var_name, string, min_length, max_length + var_name, min_length, max_length, length ))) } else { Ok(()) diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index e0ea55f4..fa8c36a7 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -15,7 +15,7 @@ use sqlx::postgres::PgPool; #[derive(Serialize, Deserialize, Clone)] pub struct InitialVersionData { - pub mod_id: ModId, + pub mod_id: Option, pub file_parts: Vec, pub version_number: String, pub version_title: String, @@ -101,7 +101,11 @@ async fn version_create_inner( let version_create_data: InitialVersionData = serde_json::from_slice(&data)?; initial_version_data = Some(version_create_data); let version_create_data = initial_version_data.as_ref().unwrap(); - let mod_id: models::ModId = version_create_data.mod_id.into(); + if version_create_data.mod_id.is_none() { + return Err(CreateError::MissingValueError("Missing mod id".to_string())); + } + + let mod_id: models::ModId = version_create_data.mod_id.unwrap().into(); let results = sqlx::query!( "SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)", @@ -152,7 +156,8 @@ async fn version_create_inner( let version_id: VersionId = models::generate_version_id(transaction).await?.into(); let body_url = format!( "data/{}/changelogs/{}/body.md", - version_create_data.mod_id, version_id + version_create_data.mod_id.unwrap(), + version_id ); let uploaded_text = file_host @@ -171,10 +176,10 @@ async fn version_create_inner( let release_channel = models::ChannelId( sqlx::query!( " - SELECT id - FROM release_channels - WHERE channel = $1 - ", + SELECT id + FROM release_channels + WHERE channel = $1 + ", version_create_data.release_channel.to_string() ) .fetch_one(&mut *transaction) @@ -200,7 +205,7 @@ async fn version_create_inner( version_builder = Some(VersionBuilder { version_id: version_id.into(), - mod_id: version_create_data.mod_id.into(), + mod_id: version_create_data.mod_id.unwrap().into(), author_id: user.id.into(), name: version_create_data.version_title.clone(), version_number: version_create_data.version_number.clone(),