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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 322 additions and 246 deletions

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, organization_permissions, is_owner, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8\n )\n ",
"query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, organization_permissions, is_owner, accepted, payouts_split\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9\n )\n ",
"describe": {
"columns": [],
"parameters": {
@ -12,10 +12,11 @@
"Int8",
"Int8",
"Bool",
"Bool"
"Bool",
"Numeric"
]
},
"nullable": []
},
"hash": "8d6166509c910b6400efc1d1398aae92ebe04b0d4d1fe1762208167cee23b645"
"hash": "02a585c845168c1bd8a82c30af351db75597da5456e29efc033ebb098e81e905"
}

View File

@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Varchar",
"Varchar",
"Bool",
"Int4",
"Varchar"
]
},
"nullable": []
},
"hash": "0c2addb0d7a87fa558821ff8e943bbb751fb2bdc22d1a5368f61cc7827586840"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO mods (\n id, team_id, name, summary, description,\n published, downloads, icon_url, status, requested_status,\n license_url, license,\n slug, color, monetization_status\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, \n $7, $8, $9, $10, \n $11, $12, \n LOWER($13), $14, $15\n )\n ",
"query": "\n INSERT INTO mods (\n id, team_id, name, summary, description,\n published, downloads, icon_url, status, requested_status,\n license_url, license,\n slug, color, monetization_status, organization_id\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, \n $7, $8, $9, $10, \n $11, $12, \n LOWER($13), $14, $15, $16\n )\n ",
"describe": {
"columns": [],
"parameters": {
@ -19,10 +19,11 @@
"Varchar",
"Text",
"Int4",
"Varchar"
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "9b0c04bc7d44a60c175259cfe86b8e7ba0340ea9c89be30a89cc224d0f7e9727"
"hash": "177b15719778b7788b88877af6affb8dba11da318b14dab7fcc7165c46bbecf5"
}

View File

@ -1,20 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)\n SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::varchar[], $4::varchar[], $5::bool[], $6::integer[], $7::varchar[])\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array",
"Int8Array",
"VarcharArray",
"VarcharArray",
"BoolArray",
"Int4Array",
"VarcharArray"
]
},
"nullable": []
},
"hash": "24ae57ca296554a29b414caca866cfe7ab956ea28450d40a564498c3d27b937f"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(SELECT 1 FROM organizations WHERE name = LOWER($1))\n ",
"query": "\n SELECT EXISTS(SELECT 1 FROM organizations WHERE LOWER(slug) = LOWER($1))\n ",
"describe": {
"columns": [
{
@ -18,5 +18,5 @@
null
]
},
"hash": "da30019590b9d0f7e21668997e780044c67a7c5d225e556c7ec2a4d7709db5ea"
"hash": "38f651362c0778254c28ccd4745af611f4deb6e72f52b8cf65d0515f0fe14779"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT o.id, o.name, o.team_id, o.description, o.icon_url, o.color\n FROM organizations o\n WHERE o.id = ANY($1) OR LOWER(o.name) = ANY($2)\n GROUP BY o.id;\n ",
"query": "\n SELECT o.id, o.slug, o.name, o.team_id, o.description, o.icon_url, o.color\n FROM organizations o\n WHERE o.id = ANY($1) OR LOWER(o.slug) = ANY($2)\n GROUP BY o.id;\n ",
"describe": {
"columns": [
{
@ -10,26 +10,31 @@
},
{
"ordinal": 1,
"name": "name",
"name": "slug",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "team_id",
"type_info": "Int8"
},
{
"ordinal": 3,
"ordinal": 4,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "icon_url",
"type_info": "Varchar"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "color",
"type_info": "Int4"
}
@ -45,9 +50,10 @@
false,
false,
false,
false,
true,
true
]
},
"hash": "ca9f3298ff92051412f5096690b3314fe91fe0b7c79ab2f7d09396af47b85ee6"
"hash": "4deaf065c12dbfd5f585286001fdf66f60524ec13eab7d922db9290237297849"
}

View File

@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO organizations (id, slug, name, team_id, description, icon_url, color)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Varchar",
"Text",
"Int8",
"Text",
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "a004ad357abfc01b4ab8a2e0a78e2b38f8a0edb7e3b2174d040ac4bb6e5bde39"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT m.id FROM organizations o\n INNER JOIN mods m ON m.organization_id = o.id\n WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.name = $2 AND $2 IS NOT NULL)\n ",
"query": "\n SELECT m.id FROM organizations o\n INNER JOIN mods m ON m.organization_id = o.id\n WHERE (o.id = $1 AND $1 IS NOT NULL) OR (o.slug = $2 AND $2 IS NOT NULL)\n ",
"describe": {
"columns": [
{
@ -19,5 +19,5 @@
false
]
},
"hash": "da962cbb02919ea79e1106e6e5de39224d240d9b8afb5cead28578ca65e281ae"
"hash": "a3448f22ec82f75ab2f3769b7d0a653a7d7315fb5e4696c26c6a96e6fc11e907"
}

View File

@ -1,19 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO organizations (id, name, team_id, description, icon_url, color)\n VALUES ($1, $2, $3, $4, $5, $6)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Varchar",
"Int8",
"Text",
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "bd48b18b9bef07185d2d050c7c978904cfbdf4ec765b7d3568f930939e236cbe"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT o.id, o.name, o.team_id, o.description, o.icon_url, o.color\n FROM organizations o\n LEFT JOIN mods m ON m.organization_id = o.id\n WHERE m.id = $1\n GROUP BY o.id;\n ",
"query": "\n SELECT o.id, o.slug, o.name, o.team_id, o.description, o.icon_url, o.color\n FROM organizations o\n LEFT JOIN mods m ON m.organization_id = o.id\n WHERE m.id = $1\n GROUP BY o.id;\n ",
"describe": {
"columns": [
{
@ -10,26 +10,31 @@
},
{
"ordinal": 1,
"name": "name",
"name": "slug",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "team_id",
"type_info": "Int8"
},
{
"ordinal": 3,
"ordinal": 4,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "icon_url",
"type_info": "Varchar"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "color",
"type_info": "Int4"
}
@ -44,9 +49,10 @@
false,
false,
false,
false,
true,
true
]
},
"hash": "26a6271a6d365e64c68ea5855109f1597a121b2e0075b20e2bc34659a269294b"
"hash": "c0f23758b879b8a1304ad895b9bcf52b90913f23f96d16c24137b0d529a7475a"
}

View File

@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Varchar",
"Bytea"
]
},
"nullable": []
},
"hash": "cb57ae673f1a7e50cc319efddb9bdc82e2251596bcf85aea52e8def343e423b8"
}

View File

@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n SELECT * FROM UNNEST($1::bigint[], $2::varchar[], $3::bytea[])\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8Array",
"VarcharArray",
"ByteaArray"
]
},
"nullable": []
},
"hash": "d2e826d4fa4e3e730cc84c97964c0c5fdd25cd49ddff8c593bd9b8a3b4d5ff1e"
}

View File

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE organizations\n SET name = LOWER($1)\n WHERE (id = $2)\n ",
"query": "\n UPDATE organizations\n SET name = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
@ -11,5 +11,5 @@
},
"nullable": []
},
"hash": "eefe0f3e40273da9adea96cdef5fd5cff917a864a701408455cc6b02cd005cf7"
"hash": "dbbfd789feb09459ef25b90eba9458e0d4bceb6389eae13a166556f828a6c3a6"
}

View File

@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE organizations\n SET slug = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int8"
]
},
"nullable": []
},
"hash": "ed7cc47dc2acfcaf27c4e763390371dccddbeea902928f1382c9505742f0a9a9"
}

View File

@ -77,7 +77,7 @@ FROM versions v
INNER JOIN mods m ON v.mod_id = m.id
INNER JOIN loader_field_enum_values lfev ON m.client_side = lfev.original_id
CROSS JOIN loader_fields lf
WHERE client_side IS NOT NULL AND lfev.enum_id = 1 AND lf.field = 'client_side';
WHERE client_side IS NOT NULL AND lfev.enum_id = 1 AND lf.field = 'client_side' AND NOT (ARRAY['vanilla', 'minecraft', 'optifine', 'iris', 'canvas', 'bukkit', 'folia', 'paper', 'purpur', 'spigot', 'sponge', 'datapack', 'bungeecord', 'velocity', 'waterfall'] @> m.loaders::text[]);;
INSERT INTO version_fields (version_id, field_id, enum_value)
SELECT v.id, lf.id, lfev.id -- Note: bug fix/edited 2023-11-27
@ -85,7 +85,7 @@ FROM versions v
INNER JOIN mods m ON v.mod_id = m.id
INNER JOIN loader_field_enum_values lfev ON m.server_side = lfev.original_id
CROSS JOIN loader_fields lf
WHERE server_side IS NOT NULL AND lfev.enum_id = 1 AND lf.field = 'server_side';
WHERE server_side IS NOT NULL AND lfev.enum_id = 1 AND lf.field = 'server_side' AND NOT (ARRAY['vanilla', 'minecraft', 'optifine', 'iris', 'canvas', 'bukkit', 'folia', 'paper', 'purpur', 'spigot', 'sponge', 'datapack', 'bungeecord', 'velocity', 'waterfall'] @> m.loaders::text[]);
ALTER TABLE mods DROP COLUMN client_side;
ALTER TABLE mods DROP COLUMN server_side;
@ -99,8 +99,12 @@ SELECT id, 2, version, created, json_build_object('type', type, 'major', major)
INSERT INTO loader_fields (field, field_type, enum_type, optional, min_val) VALUES('game_versions', 'array_enum', 2, false, 0);
INSERT INTO loader_fields_loaders (loader_id, loader_field_id) SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf WHERE lf.field = 'game_versions' AND l.loader = ANY( ARRAY['forge', 'fabric', 'quilt', 'modloader','rift','liteloader', 'neoforge']);
INSERT INTO version_fields(version_id, field_id, enum_value)
SELECT gvv.joining_version_id, lf.id, lfev.id
-- remove dangling game versions
DELETE FROM game_versions_versions
WHERE joining_version_id NOT IN (SELECT id FROM versions);
INSERT INTO version_fields(version_id, field_id, enum_value)
SELECT gvv.joining_version_id, lf.id, lfev.id
FROM game_versions_versions gvv INNER JOIN loader_field_enum_values lfev ON gvv.game_version_id = lfev.original_id
CROSS JOIN loader_fields lf
WHERE lf.field = 'game_versions' AND lfev.enum_id = 2;

View File

@ -1,3 +1,5 @@
ALTER TABLE users DROP COLUMN IF EXISTS paypal_email;
ALTER TABLE users
ADD COLUMN paypal_country text NULL,
ADD COLUMN paypal_email text NULL,

View File

@ -4,13 +4,18 @@
-- This also allows v2 routes (which have things such as client_side to remain to work with these loaders)
INSERT INTO loader_fields_loaders
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf
WHERE lf.field=ANY(ARRAY['game_versions','client_and_server','server_only','client_only','singleplayer'])
WHERE lf.field=ANY(ARRAY['client_and_server','server_only','client_only','singleplayer'])
AND
l.loader NOT IN ('vanilla', 'minecraft', 'optifine', 'iris', 'canvas')
l.loader NOT IN ('vanilla', 'minecraft', 'optifine', 'iris', 'canvas', 'bukkit', 'folia', 'paper', 'purpur', 'spigot', 'sponge', 'datapack', 'bungeecord', 'velocity', 'waterfall')
ON CONFLICT DO NOTHING;
INSERT INTO loader_fields_loaders
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf
WHERE lf.field=ANY(ARRAY['game_versions'])
ON CONFLICT DO NOTHING;
-- All existing loader_project_types so far should have a games entry as minecraft
INSERT INTO loaders_project_types_games
SELECT lpt.joining_loader_id, lpt.joining_project_type_id, g.id FROM loaders_project_types lpt CROSS JOIN games g
WHERE g.name='minecraft-java'
ON CONFLICT DO NOTHING;
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,7 @@
-- Add migration script here
ALTER TABLE organizations RENAME COLUMN name TO slug;
ALTER TABLE organizations ADD COLUMN name text NULL;
UPDATE organizations SET name = slug;
ALTER TABLE organizations ALTER COLUMN name SET NOT NULL;

View File

@ -15,7 +15,10 @@ pub struct Organization {
/// The id of the organization
pub id: OrganizationId,
/// The title (and slug) of the organization
/// The slug of the organization
pub slug: String,
/// The title of the organization
pub name: String,
/// The associated team of the organization
@ -36,10 +39,11 @@ impl Organization {
) -> Result<(), super::DatabaseError> {
sqlx::query!(
"
INSERT INTO organizations (id, name, team_id, description, icon_url, color)
VALUES ($1, $2, $3, $4, $5, $6)
INSERT INTO organizations (id, slug, name, team_id, description, icon_url, color)
VALUES ($1, $2, $3, $4, $5, $6, $7)
",
self.id.0,
self.slug,
self.name,
self.team_id as TeamId,
self.description,
@ -149,7 +153,7 @@ impl Organization {
{
remaining_strings.retain(|x| {
&to_base62(organization.id.0 as u64) != x
&& organization.name.to_lowercase() != x.to_lowercase()
&& organization.slug.to_lowercase() != x.to_lowercase()
});
found_organizations.push(organization);
continue;
@ -166,9 +170,9 @@ impl Organization {
let organizations: Vec<Organization> = sqlx::query!(
"
SELECT o.id, o.name, o.team_id, o.description, o.icon_url, o.color
SELECT o.id, o.slug, o.name, o.team_id, o.description, o.icon_url, o.color
FROM organizations o
WHERE o.id = ANY($1) OR LOWER(o.name) = ANY($2)
WHERE o.id = ANY($1) OR LOWER(o.slug) = ANY($2)
GROUP BY o.id;
",
&organization_ids_parsed,
@ -181,6 +185,7 @@ impl Organization {
.try_filter_map(|e| async {
Ok(e.right().map(|m| Organization {
id: OrganizationId(m.id),
slug: m.slug,
name: m.name,
team_id: TeamId(m.team_id),
description: m.description,
@ -203,7 +208,7 @@ impl Organization {
redis
.set(
ORGANIZATIONS_TITLES_NAMESPACE,
&organization.name.to_lowercase(),
&organization.slug.to_lowercase(),
&organization.id.0.to_string(),
None,
)
@ -226,7 +231,7 @@ impl Organization {
{
let result = sqlx::query!(
"
SELECT o.id, o.name, o.team_id, o.description, o.icon_url, o.color
SELECT o.id, o.slug, o.name, o.team_id, o.description, o.icon_url, o.color
FROM organizations o
LEFT JOIN mods m ON m.organization_id = o.id
WHERE m.id = $1
@ -240,6 +245,7 @@ impl Organization {
if let Some(result) = result {
Ok(Some(Organization {
id: OrganizationId(result.id),
slug: result.slug,
name: result.name,
team_id: TeamId(result.team_id),
description: result.description,
@ -299,7 +305,7 @@ impl Organization {
pub async fn clear_cache(
id: OrganizationId,
title: Option<String>,
slug: Option<String>,
redis: &RedisPool,
) -> Result<(), super::DatabaseError> {
let mut redis = redis.connect().await?;
@ -309,7 +315,7 @@ impl Organization {
(ORGANIZATIONS_NAMESPACE, Some(id.0.to_string())),
(
ORGANIZATIONS_TITLES_NAMESPACE,
title.map(|x| x.to_lowercase()),
slug.map(|x| x.to_lowercase()),
),
])
.await?;

View File

@ -273,13 +273,13 @@ impl Project {
id, team_id, name, summary, description,
published, downloads, icon_url, status, requested_status,
license_url, license,
slug, color, monetization_status
slug, color, monetization_status, organization_id
)
VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12,
LOWER($13), $14, $15
LOWER($13), $14, $15, $16
)
",
self.id as ProjectId,
@ -297,6 +297,7 @@ impl Project {
self.slug.as_ref(),
self.color.map(|x| x as i32),
self.monetization_status.as_str(),
self.organization_id.map(|x| x.0 as i64),
)
.execute(&mut **transaction)
.await?;

View File

@ -412,10 +412,10 @@ impl TeamMember {
sqlx::query!(
"
INSERT INTO team_members (
id, team_id, user_id, role, permissions, organization_permissions, is_owner, accepted
id, team_id, user_id, role, permissions, organization_permissions, is_owner, accepted, payouts_split
)
VALUES (
$1, $2, $3, $4, $5, $6, $7, $8
$1, $2, $3, $4, $5, $6, $7, $8, $9
)
",
self.id as TeamMemberId,
@ -426,6 +426,7 @@ impl TeamMember {
self.organization_permissions.map(|p| p.bits() as i64),
self.is_owner,
self.accepted,
self.payouts_split
)
.execute(&mut **transaction)
.await?;

View File

@ -126,70 +126,42 @@ pub struct VersionFileBuilder {
}
impl VersionFileBuilder {
pub async fn insert_many(
version_files: Vec<Self>,
pub async fn insert(
self,
version_id: VersionId,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<FileId, DatabaseError> {
let file_id = generate_file_id(transaction).await?;
let file_id = generate_file_id(&mut *transaction).await?;
let (file_ids, version_ids, urls, filenames, primary, sizes, file_types): (
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
Vec<_>,
) = version_files
.iter()
.map(|f| {
(
file_id.0,
version_id.0,
f.url.clone(),
f.filename.clone(),
f.primary,
f.size as i32,
f.file_type.map(|x| x.to_string()),
)
})
.multiunzip();
sqlx::query!(
"
INSERT INTO files (id, version_id, url, filename, is_primary, size, file_type)
SELECT * FROM UNNEST($1::bigint[], $2::bigint[], $3::varchar[], $4::varchar[], $5::bool[], $6::integer[], $7::varchar[])
VALUES ($1, $2, $3, $4, $5, $6, $7)
",
&file_ids[..],
&version_ids[..],
&urls[..],
&filenames[..],
&primary[..],
&sizes[..],
&file_types[..] as &[Option<String>],
file_id as FileId,
version_id as VersionId,
self.url,
self.filename,
self.primary,
self.size as i32,
self.file_type.map(|x| x.as_str()),
)
.execute(&mut **transaction)
.await?;
let (file_ids, algorithms, hashes): (Vec<_>, Vec<_>, Vec<_>) = version_files
.into_iter()
.flat_map(|f| {
f.hashes
.into_iter()
.map(|h| (file_id.0, h.algorithm, h.hash))
})
.multiunzip();
sqlx::query!(
"
INSERT INTO hashes (file_id, algorithm, hash)
SELECT * FROM UNNEST($1::bigint[], $2::varchar[], $3::bytea[])
",
&file_ids[..],
&algorithms[..],
&hashes[..],
)
.execute(&mut **transaction)
.await?;
for hash in self.hashes {
sqlx::query!(
"
INSERT INTO hashes (file_id, algorithm, hash)
VALUES ($1, $2, $3)
",
file_id as FileId,
hash.algorithm,
hash.hash,
)
.execute(&mut **transaction)
.await?;
}
Ok(file_id)
}
@ -242,7 +214,10 @@ impl VersionBuilder {
version_id,
..
} = self;
VersionFileBuilder::insert_many(files, self.version_id, transaction).await?;
for file in files {
file.insert(version_id, transaction).await?;
}
DependencyBuilder::insert_many(dependencies, self.version_id, transaction).await?;

View File

@ -83,24 +83,23 @@ impl LegacyProject {
let mut game_versions = Vec::new();
// V2 versions only have one project type- v3 versions can rarely have multiple.
// We'll prioritize 'modpack' first, then 'mod', and if neither are found, use the first one.
// We'll prioritize 'modpack' first, and if neither are found, use the first one.
// If there are no project types, default to 'project'
let mut project_types = data.project_types;
if project_types.contains(&"modpack".to_string()) {
project_types = vec!["modpack".to_string()];
} else if project_types.contains(&"mod".to_string()) {
project_types = vec!["mod".to_string()];
}
let project_type = project_types
let og_project_type = project_types
.first()
.cloned()
.unwrap_or("project".to_string()); // Default to 'project' if none are found
let mut project_type = if project_type == "datapack" || project_type == "plugin" {
let mut project_type = if og_project_type == "datapack" || og_project_type == "plugin" {
// These are not supported in V2, so we'll just use 'mod' instead
"mod".to_string()
} else {
project_type
og_project_type.clone()
};
let mut loaders = data.loaders;
@ -120,7 +119,8 @@ impl LegacyProject {
.iter()
.map(|f| (f.field_name.clone(), f.value.clone().serialize_internal()))
.collect::<HashMap<_, _>>();
(client_side, server_side) = v2_reroute::convert_side_types_v2(&fields);
(client_side, server_side) =
v2_reroute::convert_side_types_v2(&fields, Some(&*og_project_type));
// - if loader is mrpack, this is a modpack
// the loaders are whatever the corresponding loader fields are

View File

@ -75,24 +75,22 @@ impl LegacyResultSearchProject {
display_categories.dedup();
// V2 versions only have one project type- v3 versions can rarely have multiple.
// We'll prioritize 'modpack' first, then 'mod', and if neither are found, use the first one.
// We'll prioritize 'modpack' first, and if neither are found, use the first one.
// If there are no project types, default to 'project'
let mut project_types = result_search_project.project_types;
if project_types.contains(&"modpack".to_string()) {
project_types = vec!["modpack".to_string()];
} else if project_types.contains(&"mod".to_string()) {
project_types = vec!["mod".to_string()];
}
let project_type = project_types
let og_project_type = project_types
.first()
.cloned()
.unwrap_or("project".to_string()); // Default to 'project' if none are found
let project_type = if project_type == "datapack" || project_type == "plugin" {
let project_type = if og_project_type == "datapack" || og_project_type == "plugin" {
// These are not supported in V2, so we'll just use 'mod' instead
"mod".to_string()
} else {
project_type
og_project_type.clone()
};
let loader_fields = result_search_project.loader_fields.clone();
@ -115,6 +113,7 @@ impl LegacyResultSearchProject {
client_only,
server_only,
client_and_server,
Some(&*og_project_type),
);
let client_side = client_side.to_string();
let server_side = server_side.to_string();

View File

@ -15,6 +15,8 @@ pub struct OrganizationId(pub u64);
pub struct Organization {
/// The id of the organization
pub id: OrganizationId,
/// The slug of the organization
pub slug: String,
/// The title (and slug) of the organization
pub name: String,
/// The associated team of the organization
@ -38,6 +40,7 @@ impl Organization {
) -> Self {
Self {
id: data.id.into(),
slug: data.slug,
name: data.name,
team_id: data.team_id.into(),
description: data.description,

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

View File

@ -163,6 +163,9 @@ pub async fn index_local(
})
.unwrap_or_default();
categories.extend(mrpack_loaders);
if loader_fields.contains_key("mrpack_loaders") {
categories.retain(|x| *x != "mrpack");
}
let gallery = m
.gallery_items

View File

@ -11,7 +11,7 @@ use serde_json::json;
mod common;
// TODO: Revisit this with the new modify_json in the version maker
// TODO: Revisit this wit h the new modify_json in the version maker
// That change here should be able to simplify it vastly
#[actix_rt::test]
@ -61,17 +61,13 @@ async fn search_projects() {
),
// Project type change
// Modpack should still be able to search based on former loader, even though technically the loader is 'mrpack'
(json!([["categories:mrpack"]]), vec![4]),
// (json!([["categories:mrpack"]]), vec![4]),
// (
// json!([["categories:fabric"]]),
// vec![4],
// ),
(
json!([["categories:mrpack"], ["categories:fabric"]]),
vec![4],
),
(
json!([
["categories:mrpack"],
["categories:fabric"],
["project_types:modpack"]
]),
json!([["categories:fabric"], ["project_types:modpack"]]),
vec![4],
),
];

View File

@ -259,14 +259,14 @@ async fn search_projects() {
),
// Project type change
// Modpack should still be able to search based on former loader, even though technically the loader is 'mrpack'
(json!([["categories:mrpack"]]), vec![4]),
(
json!([["categories:mrpack"], ["categories:fabric"]]),
vec![4],
),
// (json!([["categories:mrpack"]]), vec![4]),
// (
// json!([["categories:mrpack"], ["categories:fabric"]]),
// vec![4],
// ),
(
json!([
["categories:mrpack"],
// ["categories:mrpack"],
["categories:fabric"],
["project_type:modpack"]
]),

View File

@ -46,7 +46,7 @@ async fn get_tags() {
let loader_names = loaders.into_iter().map(|x| x.name).collect::<HashSet<_>>();
assert_eq!(
loader_names,
["fabric", "forge", "mrpack", "bukkit", "waterfall"]
["fabric", "forge", "bukkit", "waterfall"]
.iter()
.map(|s| s.to_string())
.collect()