Make mod creation always create initial versions & don't require mod id for mod creation versions (#79)

* Make mod creation always create initial versions, other fixes

* Fix sqlx prepare

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Aeledfyr
2020-10-16 15:28:53 -05:00
committed by GitHub
parent 5c8ffe961e
commit 520b12e56b
3 changed files with 133 additions and 108 deletions

View File

@@ -519,26 +519,6 @@
"nullable": [] "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": { "4411f2aefd43881450da34db81e826110ac86c3a6cef9fd6a3e9e341508d1f09": {
"query": "\n SELECT id FROM versions\n WHERE mod_id = $1\n ", "query": "\n SELECT id FROM versions\n WHERE mod_id = $1\n ",
"describe": { "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": { "d8b4e7e382c77a05395124d5a6a27cccb687d0e2c31b76d49b03aa364d099d42": {
"query": "\n DELETE FROM files\n WHERE files.version_id = $1\n ", "query": "\n DELETE FROM files\n WHERE files.version_id = $1\n ",
"describe": { "describe": {

View File

@@ -181,7 +181,7 @@ async fn mod_create_inner(
) -> Result<HttpResponse, CreateError> { ) -> Result<HttpResponse, CreateError> {
let cdn_url = dotenv::var("CDN_URL")?; 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 user = get_user_from_headers(req.headers(), &mut *transaction).await?;
let mut created_versions: Vec<models::version_item::VersionBuilder> = vec![]; let mut created_versions: Vec<models::version_item::VersionBuilder> = vec![];
@@ -208,6 +208,80 @@ async fn mod_create_inner(
check_length("mod_name", 3, 255, &*create_data.mod_name)?; check_length("mod_name", 3, 255, &*create_data.mod_name)?;
check_length("mod_description", 3, 2048, &*create_data.mod_description)?; 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::<Vec<_>>(),
game_versions,
loaders,
release_channel,
};
created_versions.push(version);
}
mod_create_data = Some(create_data); mod_create_data = Some(create_data);
continue; continue;
} }
@@ -233,10 +307,11 @@ async fn mod_create_inner(
continue; continue;
} }
let version_data = create_data let (version_index, version_data) = create_data
.initial_versions .initial_versions
.iter() .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(|| { .ok_or_else(|| {
CreateError::InvalidInput(format!( CreateError::InvalidInput(format!(
"File `{}` (field {}) isn't specified in the versions data", "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 let created_version = if let Some(created_version) = created_versions.get_mut(version_index)
// 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)
{ {
created_version created_version
} else { } else {
let version_id: VersionId = models::generate_version_id(transaction).await?.into(); // This shouldn't be reachable, but better safe than sorry
return Err(CreateError::InvalidInput(format!(
let body_url = format!("data/{}/changelogs/{}/body.md", mod_id, version_id); "File `{}` (field {}) isn't specified in the versions data",
file_name, name
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::<Vec<_>>(),
game_versions,
loaders,
release_channel,
};
created_versions.push(version);
created_versions.last_mut().unwrap()
}; };
// Upload the new jar file // 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 { let create_data = if let Some(create_data) = mod_create_data {
create_data create_data
} else { } else {
return Err(CreateError::InvalidInput(String::from( return Err(CreateError::MissingValueError(String::from(
"Multipart upload missing `data` field", "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<UserId> = (&create_data.team_members) let ids: Vec<UserId> = (&create_data.team_members)
.iter() .iter()
.map(|m| m.user_id) .map(|m| m.user_id)
@@ -561,10 +580,11 @@ fn check_length(
max_length: usize, max_length: usize,
string: &str, string: &str,
) -> Result<(), CreateError> { ) -> 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!( Err(CreateError::InvalidInput(format!(
"The {} must be between {} and {} characters; got {}.", "The {} must be between {} and {} characters; got {}.",
var_name, string, min_length, max_length var_name, min_length, max_length, length
))) )))
} else { } else {
Ok(()) Ok(())

View File

@@ -15,7 +15,7 @@ use sqlx::postgres::PgPool;
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct InitialVersionData { pub struct InitialVersionData {
pub mod_id: ModId, pub mod_id: Option<ModId>,
pub file_parts: Vec<String>, pub file_parts: Vec<String>,
pub version_number: String, pub version_number: String,
pub version_title: String, pub version_title: String,
@@ -101,7 +101,11 @@ async fn version_create_inner(
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();
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!( let results = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)", "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 version_id: VersionId = models::generate_version_id(transaction).await?.into();
let body_url = format!( let body_url = format!(
"data/{}/changelogs/{}/body.md", "data/{}/changelogs/{}/body.md",
version_create_data.mod_id, version_id version_create_data.mod_id.unwrap(),
version_id
); );
let uploaded_text = file_host let uploaded_text = file_host
@@ -171,10 +176,10 @@ async fn version_create_inner(
let release_channel = models::ChannelId( let release_channel = models::ChannelId(
sqlx::query!( sqlx::query!(
" "
SELECT id SELECT id
FROM release_channels FROM release_channels
WHERE channel = $1 WHERE channel = $1
", ",
version_create_data.release_channel.to_string() version_create_data.release_channel.to_string()
) )
.fetch_one(&mut *transaction) .fetch_one(&mut *transaction)
@@ -200,7 +205,7 @@ async fn version_create_inner(
version_builder = Some(VersionBuilder { version_builder = Some(VersionBuilder {
version_id: version_id.into(), 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(), author_id: user.id.into(),
name: version_create_data.version_title.clone(), name: version_create_data.version_title.clone(),
version_number: version_create_data.version_number.clone(), version_number: version_create_data.version_number.clone(),