Staging bug fixes (#819)

* Staging bug fixes

* Finish fixes

* fix tests

* Update migration

* Update migrations

* fix side types being added for ineligible loaders

* fix tests

* Fix tests

* Finish fixes

* Add slug display names
This commit is contained in:
Geometrically
2024-01-04 16:24:33 -05:00
committed by GitHub
parent cf9c8cbb4f
commit f5802fee31
36 changed files with 322 additions and 246 deletions

View File

@@ -497,7 +497,7 @@ pub async fn project_edit(
let version = Version::from(version);
let mut fields = version.fields;
let (current_client_side, current_server_side) =
v2_reroute::convert_side_types_v2(&fields);
v2_reroute::convert_side_types_v2(&fields, None);
let client_side = client_side.unwrap_or(current_client_side);
let server_side = server_side.unwrap_or(current_server_side);
fields.extend(v2_reroute::convert_side_types_v3(client_side, server_side));

View File

@@ -79,16 +79,22 @@ pub async fn loader_list(
Ok(loaders) => {
let loaders = loaders
.into_iter()
.map(|l| LoaderData {
icon: l.icon,
name: l.name,
.filter(|l| &*l.name != "mrpack")
.map(|l| {
let mut supported_project_types = l.supported_project_types;
// Add generic 'project' type to all loaders, which is the v2 representation of
// a project type before any versions are set.
supported_project_types: l
.supported_project_types
.into_iter()
.chain(std::iter::once("project".to_string()))
.collect(),
supported_project_types.push("project".to_string());
if ["forge", "fabric", "quilt", "neoforge"].contains(&&*l.name) {
supported_project_types.push("modpack".to_string());
}
LoaderData {
icon: l.icon,
name: l.name,
supported_project_types,
}
})
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(loaders))

View File

@@ -242,6 +242,7 @@ pub fn convert_side_type_facets_v3(facets: Vec<Vec<Vec<String>>>) -> Vec<Vec<Vec
// this is not lossless. (See tests)
pub fn convert_side_types_v2(
side_types: &HashMap<String, Value>,
project_type: Option<&str>,
) -> (LegacySideType, LegacySideType) {
let client_and_server = side_types
.get("client_and_server")
@@ -265,6 +266,7 @@ pub fn convert_side_types_v2(
client_only,
server_only,
Some(client_and_server),
project_type,
)
}
@@ -274,29 +276,38 @@ pub fn convert_side_types_v2_bools(
client_only: bool,
server_only: bool,
client_and_server: Option<bool>,
project_type: Option<&str>,
) -> (LegacySideType, LegacySideType) {
use LegacySideType::{Optional, Required, Unsupported};
use LegacySideType::{Optional, Required, Unknown, Unsupported};
let singleplayer = singleplayer.or(client_and_server).unwrap_or(false);
match project_type {
Some("plugin") => (Unsupported, Required),
Some("datapack") => (Optional, Required),
Some("shader") => (Required, Unsupported),
Some("resourcepack") => (Required, Unsupported),
_ => {
let singleplayer = singleplayer.or(client_and_server).unwrap_or(false);
match (singleplayer, client_only, server_only) {
// Only singleplayer
(true, false, false) => (Required, Required),
match (singleplayer, client_only, server_only) {
// Only singleplayer
(true, false, false) => (Required, Required),
// Client only and not server only
(false, true, false) => (Required, Unsupported),
(true, true, false) => (Required, Unsupported),
// Client only and not server only
(false, true, false) => (Required, Unsupported),
(true, true, false) => (Required, Unsupported),
// Server only and not client only
(false, false, true) => (Unsupported, Required),
(true, false, true) => (Unsupported, Required),
// Server only and not client only
(false, false, true) => (Unsupported, Required),
(true, false, true) => (Unsupported, Required),
// Both server only and client only
(true, true, true) => (Optional, Optional),
(false, true, true) => (Optional, Optional),
// Both server only and client only
(true, true, true) => (Optional, Optional),
(false, true, true) => (Optional, Optional),
// Bad type
(false, false, false) => (Unsupported, Unsupported),
// Bad type
(false, false, false) => (Unknown, Unknown),
}
}
}
}
@@ -321,6 +332,7 @@ mod tests {
(Unsupported, Optional),
(Required, Optional),
(Optional, Required),
(Unsupported, Unsupported),
];
for client_side in [Required, Optional, Unsupported] {
@@ -329,7 +341,7 @@ mod tests {
continue;
}
let side_types = convert_side_types_v3(client_side, server_side);
let (client_side2, server_side2) = convert_side_types_v2(&side_types);
let (client_side2, server_side2) = convert_side_types_v2(&side_types, None);
assert_eq!(client_side, client_side2);
assert_eq!(server_side, server_side2);
}

View File

@@ -72,7 +72,7 @@ pub async fn organization_projects_get(
"
SELECT m.id FROM organizations o
INNER JOIN mods m ON m.organization_id = o.id
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.name = $2 AND $2 IS NOT NULL)
WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.slug = $2 AND $2 IS NOT NULL)
",
possible_organization_id.map(|x| x as i64),
info
@@ -95,7 +95,9 @@ pub struct NewOrganization {
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub slug: String,
// Title of the organization
#[validate(length(min = 3, max = 64))]
pub name: String,
#[validate(length(min = 3, max = 256))]
pub description: String,
@@ -126,12 +128,12 @@ pub async fn organization_create(
// Try title
let name_organization_id_option: Option<OrganizationId> =
serde_json::from_str(&format!("\"{}\"", new_organization.name)).ok();
serde_json::from_str(&format!("\"{}\"", new_organization.slug)).ok();
let mut organization_strings = vec![];
if let Some(name_organization_id) = name_organization_id_option {
organization_strings.push(name_organization_id.to_string());
}
organization_strings.push(new_organization.name.clone());
organization_strings.push(new_organization.slug.clone());
let results = Organization::get_many(&organization_strings, &mut *transaction, &redis).await?;
if !results.is_empty() {
return Err(CreateError::SlugCollision);
@@ -157,6 +159,7 @@ pub async fn organization_create(
// Create organization
let organization = Organization {
id: organization_id,
slug: new_organization.slug.clone(),
name: new_organization.name.clone(),
description: new_organization.description.clone(),
team_id,
@@ -336,7 +339,8 @@ pub struct OrganizationEdit {
length(min = 3, max = 64),
regex = "crate::util::validate::RE_URL_SAFE"
)]
// Title of the organization, also used as slug
pub slug: Option<String>,
#[validate(length(min = 3, max = 64))]
pub name: Option<String>,
}
@@ -406,8 +410,28 @@ pub async fn organizations_edit(
.to_string(),
));
}
sqlx::query!(
"
UPDATE organizations
SET name = $1
WHERE (id = $2)
",
name,
id as database::models::ids::OrganizationId,
)
.execute(&mut *transaction)
.await?;
}
let name_organization_id_option: Option<u64> = parse_base62(name).ok();
if let Some(slug) = &new_organization.slug {
if !perms.contains(OrganizationPermissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the slug of this organization!"
.to_string(),
));
}
let name_organization_id_option: Option<u64> = parse_base62(slug).ok();
if let Some(name_organization_id) = name_organization_id_option {
let results = sqlx::query!(
"
@@ -420,26 +444,26 @@ pub async fn organizations_edit(
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput(
"name collides with other organization's id!".to_string(),
"slug collides with other organization's id!".to_string(),
));
}
}
// Make sure the new name is different from the old one
// We are able to unwrap here because the name is always set
if !name.eq(&organization_item.name.clone()) {
if !slug.eq(&organization_item.slug.clone()) {
let results = sqlx::query!(
"
SELECT EXISTS(SELECT 1 FROM organizations WHERE name = LOWER($1))
",
name
SELECT EXISTS(SELECT 1 FROM organizations WHERE LOWER(slug) = LOWER($1))
",
slug
)
.fetch_one(&mut *transaction)
.await?;
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInput(
"Name collides with other organization's id!".to_string(),
"slug collides with other organization's id!".to_string(),
));
}
}
@@ -447,10 +471,10 @@ pub async fn organizations_edit(
sqlx::query!(
"
UPDATE organizations
SET name = LOWER($1)
SET slug = $1
WHERE (id = $2)
",
Some(name),
Some(slug),
id as database::models::ids::OrganizationId,
)
.execute(&mut *transaction)
@@ -460,7 +484,7 @@ pub async fn organizations_edit(
transaction.commit().await?;
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.name),
Some(organization_item.slug),
&redis,
)
.await?;
@@ -578,7 +602,7 @@ pub async fn organization_delete(
transaction.commit().await?;
database::models::Organization::clear_cache(organization.id, Some(organization.name), &redis)
database::models::Organization::clear_cache(organization.id, Some(organization.slug), &redis)
.await?;
for team_id in organization_project_teams {
@@ -994,7 +1018,7 @@ pub async fn organization_icon_edit(
transaction.commit().await?;
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.name),
Some(organization_item.slug),
&redis,
)
.await?;
@@ -1079,7 +1103,7 @@ pub async fn delete_organization_icon(
database::models::Organization::clear_cache(
organization_item.id,
Some(organization_item.name),
Some(organization_item.slug),
&redis,
)
.await?;

View File

@@ -73,7 +73,7 @@ pub enum CreateError {
InvalidCategory(String),
#[error("Invalid file type for version file: {0}")]
InvalidFileType(String),
#[error("Slug collides with other project's id!")]
#[error("Slug is already taken!")]
SlugCollision,
#[error("Authentication Error: {0}")]
Unauthorized(#[from] AuthenticationError),
@@ -612,22 +612,22 @@ async fn project_create_inner(
additional_categories.extend(ids.values());
}
// Should only be owner if not attached to an organization
let is_owner = project_create_data.organization_id.is_none();
let mut members = vec![];
let team = models::team_item::TeamBuilder {
members: vec![models::team_item::TeamMemberBuilder {
if project_create_data.organization_id.is_none() {
members.push(models::team_item::TeamMemberBuilder {
user_id: current_user.id.into(),
role: crate::models::teams::OWNER_ROLE.to_owned(),
is_owner,
// Allow all permissions for project creator, even if attached to a project
is_owner: true,
permissions: ProjectPermissions::all(),
organization_permissions: None,
accepted: true,
payouts_split: Decimal::ONE_HUNDRED,
ordering: 0,
}],
};
})
}
let team = models::team_item::TeamBuilder { members };
let team_id = team.insert(&mut *transaction).await?;

View File

@@ -65,7 +65,7 @@ pub async fn team_members_get_project(
}
let members_data =
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
let users = crate::database::models::User::get_many_ids(
let users = User::get_many_ids(
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
&**pool,
&redis,
@@ -73,14 +73,14 @@ pub async fn team_members_get_project(
.await?;
let user_id = current_user.as_ref().map(|x| x.id.into());
let logged_in = if let Some(user_id) = user_id {
let (team_member, organization_team_member) =
TeamMember::get_for_project_permissions(&project.inner, user_id, &**pool).await?;
let logged_in = current_user
.and_then(|user| {
members_data
.iter()
.find(|x| x.user_id == user.id.into() && x.accepted)
})
.is_some();
team_member.is_some() || organization_team_member.is_some()
} else {
false
};
let team_members: Vec<_> = members_data
.into_iter()

View File

@@ -731,7 +731,9 @@ async fn upload_file_to_version_inner(
"At least one file must be specified".to_string(),
));
} else {
VersionFileBuilder::insert_many(file_builders, version_id, &mut *transaction).await?;
for file in file_builders {
file.insert(version_id, &mut *transaction).await?;
}
}
// Clear version cache