You've already forked AstralRinth
forked from didirus/AstralRinth
Side types overhaul (#762)
* side types overhaul * fixes, fmt clippy * migration fix for v3 bug * fixed migration issues * more tested migration changes * fmt, clippy * bump cicd --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
@@ -72,18 +72,20 @@ INSERT INTO loader_fields_loaders (loader_id, loader_field_id) SELECT l.id, lf.i
|
||||
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 = 'server_side' AND l.loader = ANY( ARRAY['forge', 'fabric', 'quilt', 'modloader','rift','liteloader', 'neoforge']);
|
||||
|
||||
INSERT INTO version_fields (version_id, field_id, enum_value)
|
||||
SELECT v.id, 1, m.client_side
|
||||
SELECT v.id, lf.id, lfev.id -- Note: bug fix/edited 2023-11-27
|
||||
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
|
||||
WHERE client_side IS NOT NULL AND lfev.enum_id = 1;
|
||||
CROSS JOIN loader_fields lf
|
||||
WHERE client_side IS NOT NULL AND lfev.enum_id = 1 AND lf.field = 'client_side';
|
||||
|
||||
INSERT INTO version_fields (version_id, field_id, enum_value)
|
||||
SELECT v.id, 1, m.server_side
|
||||
SELECT v.id, lf.id, lfev.id -- Note: bug fix/edited 2023-11-27
|
||||
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
|
||||
WHERE server_side IS NOT NULL AND lfev.enum_id = 1;
|
||||
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';
|
||||
|
||||
ALTER TABLE mods DROP COLUMN client_side;
|
||||
ALTER TABLE mods DROP COLUMN server_side;
|
||||
@@ -95,11 +97,13 @@ INSERT INTO loader_field_enum_values (original_id, enum_id, value, created, meta
|
||||
SELECT id, 2, version, created, json_build_object('type', type, 'major', major) FROM game_versions;
|
||||
|
||||
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, 2, lfev.id
|
||||
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
|
||||
WHERE lfev.enum_id = 2;
|
||||
CROSS JOIN loader_fields lf
|
||||
WHERE lf.field = 'game_versions' AND lfev.enum_id = 2;
|
||||
|
||||
ALTER TABLE mods DROP COLUMN loaders;
|
||||
ALTER TABLE mods DROP COLUMN game_versions;
|
||||
@@ -108,12 +112,13 @@ DROP TABLE game_versions;
|
||||
|
||||
-- Convert project types
|
||||
-- we are creating a new loader type- 'mrpack'- for minecraft modpacks
|
||||
SELECT setval('loaders_id_seq', (SELECT MAX(id) FROM loaders) + 1, false);
|
||||
INSERT INTO loaders (loader) VALUES ('mrpack');
|
||||
|
||||
-- For the loader 'mrpack', we create loader fields for every loader
|
||||
-- That way we keep information like "this modpack is a fabric modpack"
|
||||
INSERT INTO loader_field_enums (id, enum_name, hidable) VALUES (3, 'mrpack_loaders', true);
|
||||
INSERT INTO loader_field_enum_values (original_id, enum_id, value) SELECT id, 2, loader FROM loaders WHERE loader != 'mrpack';
|
||||
INSERT INTO loader_field_enum_values (original_id, enum_id, value) SELECT id, 3, loader FROM loaders WHERE loader != 'mrpack';
|
||||
INSERT INTO loader_fields (field, field_type, enum_type, optional, min_val) VALUES('mrpack_loaders', 'array_enum', 3, 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 = 'mrpack_loaders' AND l.loader = 'mrpack';
|
||||
@@ -125,11 +130,31 @@ INNER JOIN mods m ON v.mod_id = m.id
|
||||
INNER JOIN loaders_versions lv ON v.id = lv.version_id
|
||||
INNER JOIN loaders l ON lv.loader_id = l.id
|
||||
CROSS JOIN loader_fields lf
|
||||
LEFT JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.enum_id
|
||||
LEFT JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.enum_id AND lfev.original_id = l.id
|
||||
WHERE m.project_type = (SELECT id FROM project_types WHERE name = 'modpack') AND lf.field = 'mrpack_loaders';
|
||||
|
||||
INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id) SELECT DISTINCT l.id, pt.id FROM loaders l CROSS JOIN project_types pt WHERE pt.name = 'modpack' AND l.loader = 'mrpack';
|
||||
|
||||
-- Set those versions to mrpack as their version
|
||||
INSERT INTO loaders_versions (version_id, loader_id)
|
||||
SELECT DISTINCT vf.version_id, l.id
|
||||
FROM version_fields vf
|
||||
LEFT JOIN loader_fields lf ON lf.id = vf.field_id
|
||||
CROSS JOIN loaders l
|
||||
WHERE lf.field = 'mrpack_loaders'
|
||||
AND l.loader = 'mrpack'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Delete the old versions that had mrpack added to them
|
||||
DELETE FROM loaders_versions lv
|
||||
WHERE lv.loader_id != (SELECT id FROM loaders WHERE loader = 'mrpack')
|
||||
AND lv.version_id IN (
|
||||
SELECT version_id
|
||||
FROM loaders_versions
|
||||
WHERE loader_id = (SELECT id FROM loaders WHERE loader = 'mrpack')
|
||||
);
|
||||
|
||||
|
||||
--- Non-mrpack loaders no longer support modpacks
|
||||
DELETE FROM loaders_project_types WHERE joining_loader_id != (SELECT id FROM loaders WHERE loader = 'mrpack') AND joining_project_type_id = (SELECT id FROM project_types WHERE name = 'modpack');
|
||||
|
||||
|
||||
96
migrations/20231116112800_side_types_overhaul.sql
Normal file
96
migrations/20231116112800_side_types_overhaul.sql
Normal file
@@ -0,0 +1,96 @@
|
||||
|
||||
INSERT INTO loader_fields (field, field_type, optional) SELECT 'singleplayer', 'boolean', false;
|
||||
INSERT INTO loader_fields (field, field_type, optional) SELECT 'client_and_server', 'boolean', false;
|
||||
INSERT INTO loader_fields (field, field_type, optional) SELECT 'client_only', 'boolean', false;
|
||||
INSERT INTO loader_fields (field, field_type, optional) SELECT 'server_only', 'boolean', false;
|
||||
|
||||
-- Create 4 temporary columns for the four booleans (makes queries easier)
|
||||
ALTER TABLE versions ADD COLUMN singleplayer boolean;
|
||||
ALTER TABLE versions ADD COLUMN client_and_server boolean;
|
||||
ALTER TABLE versions ADD COLUMN client_only boolean;
|
||||
ALTER TABLE versions ADD COLUMN server_only boolean;
|
||||
|
||||
-- Set singleplayer to be true if either client_side or server_side is 'required' OR 'optional'
|
||||
UPDATE versions v SET singleplayer = true
|
||||
FROM version_fields vf
|
||||
INNER JOIN loader_fields lf ON vf.field_id = lf.id
|
||||
INNER JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.id AND vf.enum_value = lfev.id
|
||||
WHERE v.id = vf.version_id
|
||||
AND (lf.field = 'client_side' OR lf.field = 'server_side') AND (lfev.value = 'required' OR lfev.value = 'optional');
|
||||
|
||||
-- Set client and server to be true if either client_side or server_side is 'required' OR 'optional'
|
||||
UPDATE versions v SET client_and_server = true
|
||||
FROM version_fields vf
|
||||
INNER JOIN loader_fields lf ON vf.field_id = lf.id
|
||||
INNER JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.id AND vf.enum_value = lfev.id
|
||||
WHERE v.id = vf.version_id
|
||||
AND (lf.field = 'client_side' OR lf.field = 'server_side') AND (lfev.value = 'required' OR lfev.value = 'optional');
|
||||
|
||||
-- Set client_only to be true if client_side is 'required' or 'optional', and server_side is 'optional', 'unsupported', or 'unknown'
|
||||
UPDATE versions v SET client_only = true
|
||||
FROM version_fields vf
|
||||
INNER JOIN loader_fields lf ON vf.field_id = lf.id
|
||||
INNER JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.enum_id AND vf.enum_value = lfev.id
|
||||
CROSS JOIN version_fields vf2
|
||||
INNER JOIN loader_fields lf2 ON vf2.field_id = lf2.id
|
||||
INNER JOIN loader_field_enum_values lfev2 ON lf2.enum_type = lfev2.enum_id AND vf2.enum_value = lfev2.id
|
||||
WHERE v.id = vf.version_id AND v.id = vf2.version_id
|
||||
AND lf.field = 'client_side' AND (lfev.value = 'required' OR lfev.value = 'optional')
|
||||
AND lf2.field = 'server_side' AND (lfev2.value = 'optional' OR lfev2.value = 'unsupported' OR lfev2.value = 'unknown');
|
||||
|
||||
-- Set server_only to be true if server_side is 'required' or 'optional', and client_side is 'optional', 'unsupported', or 'unknown'
|
||||
UPDATE versions v SET server_only = true
|
||||
FROM version_fields vf
|
||||
INNER JOIN loader_fields lf ON vf.field_id = lf.id
|
||||
INNER JOIN loader_field_enum_values lfev ON lf.enum_type = lfev.enum_id AND vf.enum_value = lfev.id
|
||||
CROSS JOIN version_fields vf2
|
||||
INNER JOIN loader_fields lf2 ON vf2.field_id = lf2.id
|
||||
INNER JOIN loader_field_enum_values lfev2 ON lf2.enum_type = lfev2.enum_id AND vf2.enum_value = lfev2.id
|
||||
WHERE v.id = vf.version_id AND v.id = vf2.version_id
|
||||
AND lf.field = 'server_side' AND (lfev.value = 'required' OR lfev.value = 'optional')
|
||||
AND lf2.field = 'client_side' AND (lfev2.value = 'optional' OR lfev2.value = 'unsupported' OR lfev2.value = 'unknown');
|
||||
|
||||
-- Insert the values into the version_fields table
|
||||
INSERT INTO version_fields (version_id, field_id, int_value)
|
||||
SELECT v.id, lf.id, CASE WHEN v.singleplayer THEN 1 ELSE 0 END
|
||||
FROM versions v
|
||||
INNER JOIN loader_fields lf ON lf.field = 'singleplayer';
|
||||
|
||||
INSERT INTO version_fields (version_id, field_id, int_value)
|
||||
SELECT v.id, lf.id, CASE WHEN v.client_and_server THEN 1 ELSE 0 END
|
||||
FROM versions v
|
||||
INNER JOIN loader_fields lf ON lf.field = 'client_and_server';
|
||||
|
||||
INSERT INTO version_fields (version_id, field_id, int_value)
|
||||
SELECT v.id, lf.id, CASE WHEN v.client_only THEN 1 ELSE 0 END
|
||||
FROM versions v
|
||||
INNER JOIN loader_fields lf ON lf.field = 'client_only';
|
||||
|
||||
INSERT INTO version_fields (version_id, field_id, int_value)
|
||||
SELECT v.id, lf.id, CASE WHEN v.server_only THEN 1 ELSE 0 END
|
||||
FROM versions v
|
||||
INNER JOIN loader_fields lf ON lf.field = 'server_only';
|
||||
|
||||
-- Drop the temporary columns
|
||||
ALTER TABLE versions DROP COLUMN singleplayer;
|
||||
ALTER TABLE versions DROP COLUMN client_and_server;
|
||||
ALTER TABLE versions DROP COLUMN client_only;
|
||||
ALTER TABLE versions DROP COLUMN server_only;
|
||||
|
||||
-- For each loader where loader_fields_loaders is 'client_side' or 'server_side', add the new fields
|
||||
INSERT INTO loader_fields_loaders (loader_id, loader_field_id)
|
||||
SELECT lfl.loader_id, lf.id
|
||||
FROM loader_fields_loaders lfl
|
||||
CROSS JOIN loader_fields lf
|
||||
WHERE lfl.loader_field_id IN (SELECT id FROM loader_fields WHERE field = 'client_side' OR field = 'server_side')
|
||||
AND lf.field IN ('singleplayer', 'client_and_server', 'client_only', 'server_only')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Drop the old loader_fields_loaders entries
|
||||
DELETE FROM loader_fields_loaders WHERE loader_field_id IN (SELECT id FROM loader_fields WHERE field = 'client_side' OR field = 'server_side');
|
||||
|
||||
-- Drop client_side and server_side loader fields
|
||||
DELETE FROM version_fields WHERE field_id IN (SELECT id FROM loader_fields WHERE field = 'client_side' OR field = 'server_side');
|
||||
DELETE FROM loader_field_enum_values WHERE id IN (SELECT enum_type FROM loader_fields WHERE field = 'client_side' OR field = 'server_side');
|
||||
DELETE FROM loader_fields WHERE field = 'client_side' OR field = 'server_side';
|
||||
DELETE FROM loader_field_enums WHERE id IN (SELECT enum_type FROM loader_fields WHERE field = 'side_types');
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
-- Adds missing fields to loader_fields_loaders
|
||||
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'])
|
||||
ON CONFLICT (loader_id, loader_field_id) DO NOTHING;
|
||||
|
||||
-- Fixes mrpack variants being added to the wrong enum
|
||||
-- Luckily, mrpack variants are the only ones set to 2 without metadata
|
||||
UPDATE loader_field_enum_values SET enum_id = 3 WHERE enum_id = 2 AND metadata IS NULL;
|
||||
|
||||
-- Because it was mislabeled, version_fields for mrpack_loaders were set to null.
|
||||
-- 1) Update version_fields corresponding to mrpack_loaders to the correct enum_value
|
||||
UPDATE version_fields vf
|
||||
SET enum_value = subquery.lfev_id
|
||||
FROM (
|
||||
SELECT vf.version_id, vf.field_id, lfev.id AS lfev_id
|
||||
FROM version_fields vf
|
||||
LEFT JOIN versions v ON v.id = vf.version_id
|
||||
LEFT JOIN loaders_versions lv ON v.id = lv.version_id
|
||||
LEFT JOIN loaders l ON l.id = lv.loader_id
|
||||
LEFT JOIN loader_fields lf ON lf.id = vf.field_id
|
||||
LEFT JOIN loader_field_enum_values lfev ON lfev.value = l.loader AND lf.enum_type = lfev.enum_id
|
||||
WHERE lf.field = 'mrpack_loaders' AND vf.enum_value IS NULL
|
||||
) AS subquery
|
||||
WHERE vf.version_id = subquery.version_id AND vf.field_id = subquery.field_id;
|
||||
|
||||
-- 2) Set those versions to mrpack as their version
|
||||
INSERT INTO loaders_versions (version_id, loader_id)
|
||||
SELECT DISTINCT vf.version_id, l.id
|
||||
FROM version_fields vf
|
||||
LEFT JOIN loader_fields lf ON lf.id = vf.field_id
|
||||
CROSS JOIN loaders l
|
||||
WHERE lf.field = 'mrpack_loaders'
|
||||
AND l.loader = 'mrpack'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 3) Delete the old versions that had mrpack added to them
|
||||
DELETE FROM loaders_versions lv
|
||||
WHERE lv.loader_id != (SELECT id FROM loaders WHERE loader = 'mrpack')
|
||||
AND lv.version_id IN (
|
||||
SELECT version_id
|
||||
FROM loaders_versions
|
||||
WHERE loader_id = (SELECT id FROM loaders WHERE loader = 'mrpack')
|
||||
);
|
||||
@@ -320,6 +320,23 @@ impl LoaderField {
|
||||
exec: E,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Vec<LoaderField>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let found_loader_fields = Self::get_fields_per_loader(loader_ids, exec, redis).await?;
|
||||
let result = found_loader_fields
|
||||
.into_values()
|
||||
.flatten()
|
||||
.unique_by(|x| x.id)
|
||||
.collect();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_fields_per_loader<'a, E>(
|
||||
loader_ids: &[LoaderId],
|
||||
exec: E,
|
||||
redis: &RedisPool,
|
||||
) -> Result<HashMap<LoaderId, Vec<LoaderField>>, DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
@@ -336,11 +353,11 @@ impl LoaderField {
|
||||
.filter_map(|x: String| serde_json::from_str::<RedisLoaderFieldTuple>(&x).ok())
|
||||
.collect();
|
||||
|
||||
let mut found_loader_fields = vec![];
|
||||
let mut found_loader_fields = HashMap::new();
|
||||
if !cached_fields.is_empty() {
|
||||
for (loader_id, fields) in cached_fields {
|
||||
if loader_ids.contains(&loader_id) {
|
||||
found_loader_fields.extend(fields);
|
||||
found_loader_fields.insert(loader_id, fields);
|
||||
loader_ids.retain(|x| x != &loader_id);
|
||||
}
|
||||
}
|
||||
@@ -388,14 +405,10 @@ impl LoaderField {
|
||||
redis
|
||||
.set_serialized_to_json(LOADER_FIELDS_NAMESPACE, k.0, (k, &v), None)
|
||||
.await?;
|
||||
found_loader_fields.extend(v);
|
||||
found_loader_fields.insert(k, v);
|
||||
}
|
||||
}
|
||||
let result = found_loader_fields
|
||||
.into_iter()
|
||||
.unique_by(|x| x.id)
|
||||
.collect();
|
||||
Ok(result)
|
||||
Ok(found_loader_fields)
|
||||
}
|
||||
|
||||
// Gets all fields for a given loader(s)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::super::ids::OrganizationId;
|
||||
use super::super::teams::TeamId;
|
||||
use super::super::users::UserId;
|
||||
@@ -10,6 +12,7 @@ use crate::models::projects::{
|
||||
Project, ProjectStatus, Version, VersionFile, VersionStatus, VersionType,
|
||||
};
|
||||
use crate::models::threads::ThreadId;
|
||||
use crate::routes::v2_reroute;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -85,26 +88,6 @@ impl LegacyProject {
|
||||
let mut loaders = data.loaders;
|
||||
|
||||
if let Some(versions_item) = versions_item {
|
||||
client_side = versions_item
|
||||
.version_fields
|
||||
.iter()
|
||||
.find(|f| f.field_name == "client_side")
|
||||
.and_then(|f| {
|
||||
Some(LegacySideType::from_string(
|
||||
f.value.serialize_internal().as_str()?,
|
||||
))
|
||||
})
|
||||
.unwrap_or(LegacySideType::Unknown);
|
||||
server_side = versions_item
|
||||
.version_fields
|
||||
.iter()
|
||||
.find(|f| f.field_name == "server_side")
|
||||
.and_then(|f| {
|
||||
Some(LegacySideType::from_string(
|
||||
f.value.serialize_internal().as_str()?,
|
||||
))
|
||||
})
|
||||
.unwrap_or(LegacySideType::Unknown);
|
||||
game_versions = versions_item
|
||||
.version_fields
|
||||
.iter()
|
||||
@@ -113,6 +96,14 @@ impl LegacyProject {
|
||||
.map(|v| v.into_iter().map(|v| v.version).collect())
|
||||
.unwrap_or(Vec::new());
|
||||
|
||||
// Extract side types from remaining fields (singleplayer, client_only, etc)
|
||||
let fields = versions_item
|
||||
.version_fields
|
||||
.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);
|
||||
|
||||
// - if loader is mrpack, this is a modpack
|
||||
// the loaders are whatever the corresponding loader fields are
|
||||
if versions_item.loaders == vec!["mrpack".to_string()] {
|
||||
@@ -194,7 +185,7 @@ impl LegacyProject {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Copy)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LegacySideType {
|
||||
Required,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{models::projects::SideType, util::env::parse_strings_from_var};
|
||||
use crate::{models::v2::projects::LegacySideType, util::env::parse_strings_from_var};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct PackFormat {
|
||||
pub struct PackFile {
|
||||
pub path: String,
|
||||
pub hashes: std::collections::HashMap<PackFileHash, String>,
|
||||
pub env: Option<std::collections::HashMap<EnvType, SideType>>,
|
||||
pub env: Option<std::collections::HashMap<EnvType, LegacySideType>>, // TODO: Should this use LegacySideType? Will probably require a overhaul of mrpack format to change this
|
||||
#[validate(custom(function = "validate_download_url"))]
|
||||
pub downloads: Vec<String>,
|
||||
pub file_size: u32,
|
||||
|
||||
@@ -216,42 +216,6 @@ pub struct ModeratorMessage {
|
||||
pub body: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum SideType {
|
||||
Required,
|
||||
Optional,
|
||||
Unsupported,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SideType {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl SideType {
|
||||
// These are constant, so this can remove unneccessary allocations (`to_string`)
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
SideType::Required => "required",
|
||||
SideType::Optional => "optional",
|
||||
SideType::Unsupported => "unsupported",
|
||||
SideType::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_string(string: &str) -> SideType {
|
||||
match string {
|
||||
"required" => SideType::Required,
|
||||
"optional" => SideType::Optional,
|
||||
"unsupported" => SideType::Unsupported,
|
||||
_ => SideType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_LICENSE_ID: &str = "LicenseRef-All-Rights-Reserved";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::projects::{DonationLink, Loader, Project, ProjectStatus, SideType};
|
||||
use crate::models::v2::projects::LegacyProject;
|
||||
use crate::models::projects::{DonationLink, Loader, Project, ProjectStatus};
|
||||
use crate::models::v2::projects::{LegacyProject, LegacySideType};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::v3::project_creation::default_project_type;
|
||||
use crate::routes::v3::project_creation::{CreateError, NewGalleryItem};
|
||||
@@ -60,9 +60,9 @@ struct ProjectCreateData {
|
||||
pub body: String,
|
||||
|
||||
/// The support range for the client project
|
||||
pub client_side: SideType,
|
||||
pub client_side: LegacySideType,
|
||||
/// The support range for the server project
|
||||
pub server_side: SideType,
|
||||
pub server_side: LegacySideType,
|
||||
|
||||
#[validate(length(max = 32))]
|
||||
#[validate]
|
||||
@@ -146,7 +146,7 @@ pub async fn project_create(
|
||||
let payload = v2_reroute::alter_actix_multipart(
|
||||
payload,
|
||||
req.headers().clone(),
|
||||
|legacy_create: ProjectCreateData| {
|
||||
|legacy_create: ProjectCreateData| async move {
|
||||
// Side types will be applied to each version
|
||||
let client_side = legacy_create.client_side;
|
||||
let server_side = legacy_create.server_side;
|
||||
@@ -158,8 +158,7 @@ pub async fn project_create(
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert("client_side".to_string(), json!(client_side));
|
||||
fields.insert("server_side".to_string(), json!(server_side));
|
||||
fields.extend(v2_reroute::convert_side_types_v3(client_side, server_side));
|
||||
fields.insert("game_versions".to_string(), json!(v.game_versions));
|
||||
|
||||
// Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{
|
||||
DonationLink, MonetizationStatus, Project, ProjectStatus, SearchRequest, SideType,
|
||||
DonationLink, MonetizationStatus, Project, ProjectStatus, SearchRequest, Version,
|
||||
};
|
||||
use crate::models::v2::projects::LegacyProject;
|
||||
use crate::models::v2::projects::{LegacyProject, LegacySideType};
|
||||
use crate::models::v2::search::LegacySearchResults;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::v3::projects::ProjectIds;
|
||||
@@ -13,10 +13,9 @@ use crate::routes::{v2_reroute, v3, ApiError};
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
@@ -59,27 +58,55 @@ pub async fn project_search(
|
||||
// Search now uses loader_fields instead of explicit 'client_side' and 'server_side' fields
|
||||
// While the backend for this has changed, it doesnt affect much
|
||||
// in the API calls except that 'versions:x' is now 'game_versions:x'
|
||||
let facets: Option<Vec<Vec<String>>> = if let Some(facets) = info.facets {
|
||||
let facets = serde_json::from_str::<Vec<Vec<&str>>>(&facets)?;
|
||||
let facets: Option<Vec<Vec<Vec<String>>>> = if let Some(facets) = info.facets {
|
||||
let facets = serde_json::from_str::<Vec<Vec<serde_json::Value>>>(&facets)?;
|
||||
// Search can now *optionally* have a third inner array: So Vec(AND)<Vec(OR)<Vec(AND)< _ >>>
|
||||
// For every inner facet, we will check if it can be deserialized into a Vec<&str>, and do so.
|
||||
// If not, we will assume it is a single facet and wrap it in a Vec.
|
||||
let facets: Vec<Vec<Vec<String>>> = facets
|
||||
.into_iter()
|
||||
.map(|facets| {
|
||||
facets
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
if facet.is_array() {
|
||||
serde_json::from_value::<Vec<String>>(facet).unwrap_or_default()
|
||||
} else {
|
||||
vec![serde_json::from_value::<String>(facet.clone())
|
||||
.unwrap_or_default()]
|
||||
}
|
||||
})
|
||||
.collect_vec()
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// We will now convert side_types to their new boolean format
|
||||
let facets = v2_reroute::convert_side_type_facets_v3(facets);
|
||||
|
||||
Some(
|
||||
facets
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
facet
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
let val = match facet.split(':').nth(1) {
|
||||
Some(val) => val,
|
||||
None => return facet.to_string(),
|
||||
};
|
||||
.map(|facets| {
|
||||
facets
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
let val = match facet.split(':').nth(1) {
|
||||
Some(val) => val,
|
||||
None => return facet.to_string(),
|
||||
};
|
||||
|
||||
if facet.starts_with("versions:") {
|
||||
format!("game_versions:{}", val)
|
||||
} else if facet.starts_with("project_type:") {
|
||||
format!("project_types:{}", val)
|
||||
} else {
|
||||
facet.to_string()
|
||||
}
|
||||
if facet.starts_with("versions:") {
|
||||
format!("game_versions:{}", val)
|
||||
} else if facet.starts_with("project_type:") {
|
||||
format!("project_types:{}", val)
|
||||
} else {
|
||||
facet.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
@@ -279,8 +306,8 @@ pub struct EditProject {
|
||||
#[validate]
|
||||
pub donation_urls: Option<Vec<DonationLink>>,
|
||||
pub license_id: Option<String>,
|
||||
pub client_side: Option<SideType>,
|
||||
pub server_side: Option<SideType>,
|
||||
pub client_side: Option<LegacySideType>,
|
||||
pub server_side: Option<LegacySideType>,
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
regex = "crate::util::validate::RE_URL_SAFE"
|
||||
@@ -321,8 +348,8 @@ pub async fn project_edit(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let v2_new_project = new_project.into_inner();
|
||||
let client_side = v2_new_project.client_side.clone();
|
||||
let server_side = v2_new_project.server_side.clone();
|
||||
let client_side = v2_new_project.client_side;
|
||||
let server_side = v2_new_project.server_side;
|
||||
let new_slug = v2_new_project.slug.clone();
|
||||
|
||||
// TODO: Some kind of handling here to ensure project type is fine.
|
||||
@@ -376,12 +403,17 @@ pub async fn project_edit(
|
||||
let version_ids = project_item.map(|x| x.versions).unwrap_or_default();
|
||||
let versions = version_item::Version::get_many(&version_ids, &**pool, &redis).await?;
|
||||
for version in versions {
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert("client_side".to_string(), json!(client_side));
|
||||
fields.insert("server_side".to_string(), json!(server_side));
|
||||
let version = Version::from(version);
|
||||
let mut fields = version.fields;
|
||||
let (current_client_side, current_server_side) =
|
||||
v2_reroute::convert_side_types_v2(&fields);
|
||||
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));
|
||||
|
||||
response = v3::versions::version_edit_helper(
|
||||
req.clone(),
|
||||
(version.inner.id.into(),),
|
||||
(version.id,),
|
||||
pool.clone(),
|
||||
redis.clone(),
|
||||
v3::versions::EditVersion {
|
||||
|
||||
@@ -3,10 +3,12 @@ use std::collections::HashMap;
|
||||
use super::ApiError;
|
||||
use crate::database::models::loader_fields::LoaderFieldEnumValue;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::v2::projects::LegacySideType;
|
||||
use crate::routes::v3::tags::{LoaderData as LoaderDataV3, LoaderFieldsEnumQuery};
|
||||
use crate::routes::{v2_reroute, v3};
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
@@ -191,28 +193,15 @@ pub async fn project_type_list(
|
||||
}
|
||||
|
||||
#[get("side_type")]
|
||||
pub async fn side_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let response = v3::tags::loader_fields_list(
|
||||
pool,
|
||||
web::Query(LoaderFieldsEnumQuery {
|
||||
loader_field: "client_side".to_string(), // same as server_side
|
||||
filters: None,
|
||||
}),
|
||||
redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Convert to V2 format
|
||||
Ok(
|
||||
match v2_reroute::extract_ok_json::<Vec<LoaderFieldEnumValue>>(response).await {
|
||||
Ok(fields) => {
|
||||
let fields = fields.into_iter().map(|f| f.value).collect::<Vec<_>>();
|
||||
HttpResponse::Ok().json(fields)
|
||||
}
|
||||
Err(response) => response,
|
||||
},
|
||||
)
|
||||
pub async fn side_type_list() -> Result<HttpResponse, ApiError> {
|
||||
// Original side types are no longer reflected in the database.
|
||||
// Therefore, we hardcode and return all the fields that are supported by our v2 conversion logic.
|
||||
let side_types = [
|
||||
LegacySideType::Required,
|
||||
LegacySideType::Optional,
|
||||
LegacySideType::Unsupported,
|
||||
LegacySideType::Unknown,
|
||||
];
|
||||
let side_types = side_types.iter().map(|s| s.to_string()).collect_vec();
|
||||
Ok(HttpResponse::Ok().json(side_types))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::database::models::loader_fields::VersionField;
|
||||
use crate::database::models::{project_item, version_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::ImageId;
|
||||
@@ -88,63 +90,90 @@ pub async fn version_create(
|
||||
payload,
|
||||
req.headers().clone(),
|
||||
|legacy_create: InitialVersionData| {
|
||||
// Convert input data to V3 format
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert(
|
||||
"game_versions".to_string(),
|
||||
json!(legacy_create.game_versions),
|
||||
);
|
||||
let client = client.clone();
|
||||
let redis = redis.clone();
|
||||
async move {
|
||||
// Convert input data to V3 format
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert(
|
||||
"game_versions".to_string(),
|
||||
json!(legacy_create.game_versions),
|
||||
);
|
||||
|
||||
// TODO: will be overhauled with side-types overhaul
|
||||
// TODO: if not, should default to previous version
|
||||
fields.insert("client_side".to_string(), json!("required"));
|
||||
fields.insert("server_side".to_string(), json!("optional"));
|
||||
|
||||
// Handle project type via file extension prediction
|
||||
let mut project_type = None;
|
||||
for file_part in &legacy_create.file_parts {
|
||||
if let Some(ext) = file_part.split('.').last() {
|
||||
match ext {
|
||||
"mrpack" | "mrpack-primary" => {
|
||||
project_type = Some("modpack");
|
||||
break;
|
||||
// Copies side types of another version of the project.
|
||||
// If no version exists, defaults to all false.
|
||||
// TODO: write test for this to ensure predictible unchanging behaviour
|
||||
// This is inherently lossy, but not much can be done about it, as side types are no longer associated with projects,
|
||||
// so the 'missing' ones can't be easily accessed.
|
||||
let side_type_loader_field_names = [
|
||||
"singleplayer",
|
||||
"client_and_server",
|
||||
"client_only",
|
||||
"server_only",
|
||||
];
|
||||
fields.extend(
|
||||
side_type_loader_field_names
|
||||
.iter()
|
||||
.map(|f| (f.to_string(), json!(false))),
|
||||
);
|
||||
if let Some(example_version_fields) =
|
||||
get_example_version_fields(legacy_create.project_id, client, &redis).await?
|
||||
{
|
||||
fields.extend(example_version_fields.into_iter().filter_map(|f| {
|
||||
if side_type_loader_field_names.contains(&f.field_name.as_str()) {
|
||||
Some((f.field_name, f.value.serialize_internal()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
// No other type matters
|
||||
_ => {}
|
||||
}
|
||||
break;
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle project type via file extension prediction
|
||||
let mut project_type = None;
|
||||
for file_part in &legacy_create.file_parts {
|
||||
if let Some(ext) = file_part.split('.').last() {
|
||||
match ext {
|
||||
"mrpack" | "mrpack-primary" => {
|
||||
project_type = Some("modpack");
|
||||
break;
|
||||
}
|
||||
// No other type matters
|
||||
_ => {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
|
||||
// Setting of 'project_type' directly is removed, it's loader-based now.
|
||||
if project_type == Some("modpack") {
|
||||
fields.insert("mrpack_loaders".to_string(), json!(legacy_create.loaders));
|
||||
}
|
||||
|
||||
let loaders = if project_type == Some("modpack") {
|
||||
vec![Loader("mrpack".to_string())]
|
||||
} else {
|
||||
legacy_create.loaders
|
||||
};
|
||||
|
||||
Ok(v3::version_creation::InitialVersionData {
|
||||
project_id: legacy_create.project_id,
|
||||
file_parts: legacy_create.file_parts,
|
||||
version_number: legacy_create.version_number,
|
||||
version_title: legacy_create.version_title,
|
||||
version_body: legacy_create.version_body,
|
||||
dependencies: legacy_create.dependencies,
|
||||
release_channel: legacy_create.release_channel,
|
||||
loaders,
|
||||
featured: legacy_create.featured,
|
||||
primary_file: legacy_create.primary_file,
|
||||
status: legacy_create.status,
|
||||
file_types: legacy_create.file_types,
|
||||
uploaded_images: legacy_create.uploaded_images,
|
||||
ordering: legacy_create.ordering,
|
||||
fields,
|
||||
})
|
||||
}
|
||||
|
||||
// Modpacks now use the "mrpack" loader, and loaders are converted to loader fields.
|
||||
// Setting of 'project_type' directly is removed, it's loader-based now.
|
||||
if project_type == Some("modpack") {
|
||||
fields.insert("mrpack_loaders".to_string(), json!(legacy_create.loaders));
|
||||
}
|
||||
|
||||
let loaders = if project_type == Some("modpack") {
|
||||
vec![Loader("mrpack".to_string())]
|
||||
} else {
|
||||
legacy_create.loaders
|
||||
};
|
||||
|
||||
Ok(v3::version_creation::InitialVersionData {
|
||||
project_id: legacy_create.project_id,
|
||||
file_parts: legacy_create.file_parts,
|
||||
version_number: legacy_create.version_number,
|
||||
version_title: legacy_create.version_title,
|
||||
version_body: legacy_create.version_body,
|
||||
dependencies: legacy_create.dependencies,
|
||||
release_channel: legacy_create.release_channel,
|
||||
loaders,
|
||||
featured: legacy_create.featured,
|
||||
primary_file: legacy_create.primary_file,
|
||||
status: legacy_create.status,
|
||||
file_types: legacy_create.file_types,
|
||||
uploaded_images: legacy_create.uploaded_images,
|
||||
ordering: legacy_create.ordering,
|
||||
fields,
|
||||
})
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
@@ -170,6 +199,32 @@ pub async fn version_create(
|
||||
}
|
||||
}
|
||||
|
||||
// Gets version fields of an example version of a project, if one exists.
|
||||
async fn get_example_version_fields(
|
||||
project_id: Option<ProjectId>,
|
||||
pool: Data<PgPool>,
|
||||
redis: &RedisPool,
|
||||
) -> Result<Option<Vec<VersionField>>, CreateError> {
|
||||
let project_id = match project_id {
|
||||
Some(project_id) => project_id,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let vid = match project_item::Project::get_id(project_id.into(), &**pool, redis)
|
||||
.await?
|
||||
.and_then(|p| p.versions.first().cloned())
|
||||
{
|
||||
Some(vid) => vid,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let example_version = match version_item::Version::get(vid, &**pool, redis).await? {
|
||||
Some(version) => version,
|
||||
None => return Ok(None),
|
||||
};
|
||||
Ok(Some(example_version.version_fields))
|
||||
}
|
||||
|
||||
// under /api/v1/version/{version_id}
|
||||
#[post("{version_id}/file")]
|
||||
pub async fn upload_file_to_version(
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::v3::project_creation::CreateError;
|
||||
use crate::models::v2::projects::LegacySideType;
|
||||
use crate::util::actix::{generate_multipart, MultipartSegment, MultipartSegmentData};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::http::header::{HeaderMap, TryIntoHeaderPair};
|
||||
use actix_web::HttpResponse;
|
||||
use futures::{stream, StreamExt};
|
||||
use serde_json::json;
|
||||
use futures::{stream, Future, StreamExt};
|
||||
use itertools::Itertools;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub async fn extract_ok_json<T>(response: HttpResponse) -> Result<T, HttpResponse>
|
||||
where
|
||||
@@ -29,14 +33,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn alter_actix_multipart<T, U>(
|
||||
pub async fn alter_actix_multipart<T, U, Fut>(
|
||||
mut multipart: Multipart,
|
||||
mut headers: HeaderMap,
|
||||
mut closure: impl FnMut(T) -> Result<U, CreateError>,
|
||||
mut closure: impl FnMut(T) -> Fut,
|
||||
) -> Result<Multipart, CreateError>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
U: serde::Serialize,
|
||||
Fut: Future<Output = Result<U, CreateError>>,
|
||||
{
|
||||
let mut segments: Vec<MultipartSegment> = Vec::new();
|
||||
|
||||
@@ -56,7 +61,7 @@ where
|
||||
|
||||
{
|
||||
let json_value: T = serde_json::from_slice(&buffer)?;
|
||||
let json_value: U = closure(json_value)?;
|
||||
let json_value: U = closure(json_value).await?;
|
||||
buffer = serde_json::to_vec(&json_value)?;
|
||||
}
|
||||
|
||||
@@ -110,3 +115,353 @@ where
|
||||
|
||||
Ok(new_multipart)
|
||||
}
|
||||
|
||||
// Converts a "client_side" and "server_side" pair into the new v3 corresponding fields
|
||||
pub fn convert_side_types_v3(
|
||||
client_side: LegacySideType,
|
||||
server_side: LegacySideType,
|
||||
) -> HashMap<String, Value> {
|
||||
use LegacySideType::{Optional, Required};
|
||||
|
||||
let singleplayer = client_side == Required
|
||||
|| client_side == Optional
|
||||
|| server_side == Required
|
||||
|| server_side == Optional;
|
||||
let client_and_server = singleplayer;
|
||||
let client_only =
|
||||
(client_side == Required || client_side == Optional) && server_side != Required;
|
||||
let server_only =
|
||||
(server_side == Required || server_side == Optional) && client_side != Required;
|
||||
|
||||
let mut fields = HashMap::new();
|
||||
fields.insert("singleplayer".to_string(), json!(singleplayer));
|
||||
fields.insert("client_and_server".to_string(), json!(client_and_server));
|
||||
fields.insert("client_only".to_string(), json!(client_only));
|
||||
fields.insert("server_only".to_string(), json!(server_only));
|
||||
fields
|
||||
}
|
||||
|
||||
// Convert search facets from V2 to V3
|
||||
// Less trivial as we need to handle the case where one side is set and the other is not, which does not convert cleanly
|
||||
pub fn convert_side_type_facets_v3(facets: Vec<Vec<Vec<String>>>) -> Vec<Vec<Vec<String>>> {
|
||||
use LegacySideType::{Optional, Required, Unsupported};
|
||||
let possible_side_types = [Required, Optional, Unsupported]; // Should not include Unknown
|
||||
|
||||
let mut v3_facets = vec![];
|
||||
|
||||
// Outer facets are joined by AND
|
||||
for inner_facets in facets {
|
||||
// Inner facets are joined by OR
|
||||
// These may change as the inner facets are converted
|
||||
// ie:
|
||||
// for A v B v C, if A is converted to X^Y v Y^Z, then the new facets are X^Y v Y^Z v B v C
|
||||
let mut new_inner_facets = vec![];
|
||||
|
||||
for inner_inner_facets in inner_facets {
|
||||
// Inner inner facets are joined by AND
|
||||
let mut client_side = None;
|
||||
let mut server_side = None;
|
||||
|
||||
// Extract client_side and server_side facets, and remove them from the list
|
||||
let inner_inner_facets = inner_inner_facets
|
||||
.into_iter()
|
||||
.filter_map(|facet| {
|
||||
let val = match facet.split(':').nth(1) {
|
||||
Some(val) => val,
|
||||
None => return Some(facet.to_string()),
|
||||
};
|
||||
|
||||
if facet.starts_with("client_side:") {
|
||||
client_side = Some(LegacySideType::from_string(val));
|
||||
None
|
||||
} else if facet.starts_with("server_side:") {
|
||||
server_side = Some(LegacySideType::from_string(val));
|
||||
None
|
||||
} else {
|
||||
Some(facet.to_string())
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// Depending on whether client_side and server_side are set, we can convert the facets to the new loader fields differently
|
||||
let mut new_possibilities = match (client_side, server_side) {
|
||||
// Both set or unset is a trivial case
|
||||
(Some(client_side), Some(server_side)) => {
|
||||
vec![convert_side_types_v3(client_side, server_side)
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}:{}", k, v))
|
||||
.collect()]
|
||||
}
|
||||
(None, None) => vec![vec![]],
|
||||
|
||||
(Some(client_side), None) => possible_side_types
|
||||
.iter()
|
||||
.map(|server_side| {
|
||||
convert_side_types_v3(client_side, *server_side)
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}:{}", k, v))
|
||||
.unique()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
(None, Some(server_side)) => possible_side_types
|
||||
.iter()
|
||||
.map(|client_side| {
|
||||
convert_side_types_v3(*client_side, server_side)
|
||||
.into_iter()
|
||||
.map(|(k, v)| format!("{}:{}", k, v))
|
||||
.unique()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
// Add the new possibilities to the list
|
||||
for new_possibility in &mut new_possibilities {
|
||||
new_possibility.extend(inner_inner_facets.clone());
|
||||
}
|
||||
new_inner_facets.extend(new_possibilities);
|
||||
}
|
||||
v3_facets.push(new_inner_facets);
|
||||
}
|
||||
v3_facets
|
||||
}
|
||||
|
||||
// Convert search facets from V3 back to v2
|
||||
// this is not lossless. (See tests)
|
||||
pub fn convert_side_types_v2(
|
||||
side_types: &HashMap<String, Value>,
|
||||
) -> (LegacySideType, LegacySideType) {
|
||||
use LegacySideType::{Optional, Required, Unsupported};
|
||||
|
||||
let client_and_server = side_types
|
||||
.get("client_and_server")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(false);
|
||||
let singleplayer = side_types
|
||||
.get("singleplayer")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(client_and_server);
|
||||
let client_only = side_types
|
||||
.get("client_only")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(false);
|
||||
let server_only = side_types
|
||||
.get("server_only")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
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),
|
||||
|
||||
// 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),
|
||||
|
||||
// Bad type
|
||||
(false, false, false) => (Unsupported, Unsupported),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::models::v2::projects::LegacySideType::{Optional, Required, Unsupported};
|
||||
|
||||
#[test]
|
||||
fn convert_types() {
|
||||
// Converting types from V2 to V3 and back should be idempotent- for certain pairs
|
||||
let lossy_pairs = [
|
||||
(Optional, Unsupported),
|
||||
(Unsupported, Optional),
|
||||
(Required, Optional),
|
||||
(Optional, Required),
|
||||
];
|
||||
|
||||
for client_side in [Required, Optional, Unsupported] {
|
||||
for server_side in [Required, Optional, Unsupported] {
|
||||
if lossy_pairs.contains(&(client_side, server_side)) {
|
||||
continue;
|
||||
}
|
||||
let side_types = convert_side_types_v3(client_side, server_side);
|
||||
let (client_side2, server_side2) = convert_side_types_v2(&side_types);
|
||||
assert_eq!(client_side, client_side2);
|
||||
assert_eq!(server_side, server_side2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_facets() {
|
||||
let pre_facets = vec![
|
||||
// Test combinations of both sides being set
|
||||
vec![vec![
|
||||
"client_side:required".to_string(),
|
||||
"server_side:required".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"client_side:required".to_string(),
|
||||
"server_side:optional".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"client_side:required".to_string(),
|
||||
"server_side:unsupported".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"client_side:optional".to_string(),
|
||||
"server_side:required".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"client_side:optional".to_string(),
|
||||
"server_side:optional".to_string(),
|
||||
]],
|
||||
// Test multiple inner facets
|
||||
vec![
|
||||
vec![
|
||||
"client_side:required".to_string(),
|
||||
"server_side:required".to_string(),
|
||||
],
|
||||
vec![
|
||||
"client_side:required".to_string(),
|
||||
"server_side:optional".to_string(),
|
||||
],
|
||||
],
|
||||
// Test additional fields
|
||||
vec![
|
||||
vec![
|
||||
"random_field_test_1".to_string(),
|
||||
"client_side:required".to_string(),
|
||||
"server_side:required".to_string(),
|
||||
],
|
||||
vec![
|
||||
"random_field_test_2".to_string(),
|
||||
"client_side:required".to_string(),
|
||||
"server_side:optional".to_string(),
|
||||
],
|
||||
],
|
||||
// Test only one facet being set
|
||||
vec![vec!["client_side:required".to_string()]],
|
||||
];
|
||||
|
||||
let converted_facets = convert_side_type_facets_v3(pre_facets)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
x.into_iter()
|
||||
.map(|mut y| {
|
||||
y.sort();
|
||||
y
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let post_facets = vec![
|
||||
vec![vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:false".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:false".to_string(),
|
||||
"server_only:true".to_string(),
|
||||
]],
|
||||
vec![vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:true".to_string(),
|
||||
]],
|
||||
vec![
|
||||
vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:false".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
],
|
||||
vec![
|
||||
vec![
|
||||
"random_field_test_1".to_string(),
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:false".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
vec![
|
||||
"random_field_test_2".to_string(),
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
],
|
||||
// Test only one facet being set
|
||||
// Iterates over all possible side types
|
||||
vec![
|
||||
// C: Required, S: Required
|
||||
vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:false".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
// C: Required, S: Optional
|
||||
vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
// C: Required, S: Unsupported
|
||||
vec![
|
||||
"singleplayer:true".to_string(),
|
||||
"client_and_server:true".to_string(),
|
||||
"client_only:true".to_string(),
|
||||
"server_only:false".to_string(),
|
||||
],
|
||||
],
|
||||
]
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
x.into_iter()
|
||||
.map(|mut y| {
|
||||
y.sort();
|
||||
y
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(converted_facets, post_facets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,7 +511,6 @@ pub async fn revoke_oauth_authorization(
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
println!("Inside revoke_oauth_authorization");
|
||||
let current_user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::database::models::loader_fields::{
|
||||
use crate::database::redis::RedisPool;
|
||||
use actix_web::{web, HttpResponse};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde_json::Value;
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -84,6 +85,7 @@ pub struct LoaderData {
|
||||
pub name: String,
|
||||
pub supported_project_types: Vec<String>,
|
||||
pub supported_games: Vec<String>,
|
||||
pub supported_fields: Vec<String>, // Available loader fields for this loader
|
||||
pub metadata: Value,
|
||||
}
|
||||
|
||||
@@ -91,14 +93,26 @@ pub async fn loader_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let mut results = Loader::list(&**pool, &redis)
|
||||
.await?
|
||||
let loaders = Loader::list(&**pool, &redis).await?;
|
||||
|
||||
let loader_fields = LoaderField::get_fields_per_loader(
|
||||
&loaders.iter().map(|x| x.id).collect_vec(),
|
||||
&**pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut results = loaders
|
||||
.into_iter()
|
||||
.map(|x| LoaderData {
|
||||
icon: x.icon,
|
||||
name: x.loader,
|
||||
supported_project_types: x.supported_project_types,
|
||||
supported_games: x.supported_games,
|
||||
supported_fields: loader_fields
|
||||
.get(&x.id)
|
||||
.map(|x| x.iter().map(|x| x.field.clone()).collect_vec())
|
||||
.unwrap_or_default(),
|
||||
metadata: x.metadata,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -3,8 +3,10 @@ use crate::models::projects::SearchRequest;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::HttpResponse;
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
use meilisearch_sdk::client::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
@@ -177,7 +179,7 @@ pub async fn search_for_project(
|
||||
query.with_filter(new_filters);
|
||||
} else {
|
||||
let facets = if let Some(facets) = &info.facets {
|
||||
Some(serde_json::from_str::<Vec<Vec<&str>>>(facets)?)
|
||||
Some(serde_json::from_str::<Vec<Vec<Value>>>(facets)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -190,14 +192,42 @@ pub async fn search_for_project(
|
||||
};
|
||||
|
||||
if let Some(facets) = facets {
|
||||
// Search can now *optionally* have a third inner array: So Vec(AND)<Vec(OR)<Vec(AND)< _ >>>
|
||||
// For every inner facet, we will check if it can be deserialized into a Vec<&str>, and do so.
|
||||
// If not, we will assume it is a single facet and wrap it in a Vec.
|
||||
let facets: Vec<Vec<Vec<String>>> = facets
|
||||
.into_iter()
|
||||
.map(|facets| {
|
||||
facets
|
||||
.into_iter()
|
||||
.map(|facet| {
|
||||
if facet.is_array() {
|
||||
serde_json::from_value::<Vec<String>>(facet).unwrap_or_default()
|
||||
} else {
|
||||
vec![serde_json::from_value::<String>(facet.clone())
|
||||
.unwrap_or_default()]
|
||||
}
|
||||
})
|
||||
.collect_vec()
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
filter_string.push('(');
|
||||
for (index, facet_list) in facets.iter().enumerate() {
|
||||
for (index, facet_outer_list) in facets.iter().enumerate() {
|
||||
filter_string.push('(');
|
||||
|
||||
for (facet_index, facet) in facet_list.iter().enumerate() {
|
||||
filter_string.push_str(&facet.replace(':', " = "));
|
||||
for (facet_outer_index, facet_inner_list) in facet_outer_list.iter().enumerate()
|
||||
{
|
||||
filter_string.push('(');
|
||||
for (facet_inner_index, facet) in facet_inner_list.iter().enumerate() {
|
||||
filter_string.push_str(&facet.replace(':', " = "));
|
||||
if facet_inner_index != (facet_inner_list.len() - 1) {
|
||||
filter_string.push_str(" AND ")
|
||||
}
|
||||
}
|
||||
filter_string.push(')');
|
||||
|
||||
if facet_index != (facet_list.len() - 1) {
|
||||
if facet_outer_index != (facet_outer_list.len() - 1) {
|
||||
filter_string.push_str(" OR ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,10 @@ pub fn get_public_version_creation_data_json(
|
||||
|
||||
// Loader fields
|
||||
"game_versions": ["1.20.1"],
|
||||
"client_side": "required",
|
||||
"server_side": "optional"
|
||||
"singleplayer": true,
|
||||
"client_and_server": true,
|
||||
"client_only": true,
|
||||
"server_only": false,
|
||||
});
|
||||
if is_modpack {
|
||||
j["mrpack_loaders"] = json!(["fabric"]);
|
||||
|
||||
@@ -3,10 +3,8 @@ use actix_web::{
|
||||
test::{self, TestRequest},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use labrinth::routes::v3::tags::GameData;
|
||||
use labrinth::{
|
||||
database::models::loader_fields::LoaderFieldEnumValue, routes::v3::tags::LoaderData,
|
||||
};
|
||||
use labrinth::database::models::loader_fields::LoaderFieldEnumValue;
|
||||
use labrinth::routes::v3::tags::{GameData, LoaderData};
|
||||
|
||||
use crate::common::{
|
||||
api_common::{
|
||||
|
||||
@@ -24,7 +24,7 @@ use super::{
|
||||
|
||||
use super::{asserts::assert_status, database::USER_USER_ID, get_json_val_str};
|
||||
|
||||
pub const DUMMY_DATA_UPDATE: i64 = 5;
|
||||
pub const DUMMY_DATA_UPDATE: i64 = 6;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const DUMMY_CATEGORIES: &[&str] = &[
|
||||
@@ -340,8 +340,10 @@ pub async fn add_project_beta(api: &ApiV3) -> (CommonProject, CommonVersion) {
|
||||
"version_title": "start",
|
||||
"status": "unlisted",
|
||||
"dependencies": [],
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"singleplayer": true,
|
||||
"client_and_server": true,
|
||||
"client_only": true,
|
||||
"server_only": false,
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
|
||||
@@ -68,7 +68,7 @@ INSERT INTO loader_field_enum_values(enum_id, value, metadata, ordering)
|
||||
VALUES (2, 'Ordering_Positive100', '{"type":"release","major":false}', 100);
|
||||
|
||||
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' OR lf.field = 'client_side' OR lf.field = 'server_side';
|
||||
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf WHERE lf.field IN ('game_versions','singleplayer', 'client_and_server', 'client_only', 'server_only');
|
||||
|
||||
INSERT INTO categories (id, category, project_type) VALUES
|
||||
(51, 'combat', 1),
|
||||
|
||||
@@ -112,7 +112,7 @@ async fn creating_loader_fields() {
|
||||
Some(
|
||||
serde_json::from_value(json!([{
|
||||
"op": "remove",
|
||||
"path": "/client_side"
|
||||
"path": "/singleplayer"
|
||||
}]))
|
||||
.unwrap(),
|
||||
),
|
||||
@@ -183,7 +183,7 @@ async fn creating_loader_fields() {
|
||||
json!(1),
|
||||
json!([1]),
|
||||
json!("1.20.1"),
|
||||
json!(["client_side"]),
|
||||
json!(["singleplayer"]),
|
||||
] {
|
||||
// TODO: - Create project
|
||||
// - Create version
|
||||
@@ -271,12 +271,12 @@ async fn creating_loader_fields() {
|
||||
"value": ["1.20.1", "1.20.2"]
|
||||
}, {
|
||||
"op": "add",
|
||||
"path": "/client_side",
|
||||
"value": "optional"
|
||||
"path": "/singleplayer",
|
||||
"value": false
|
||||
}, {
|
||||
"op": "add",
|
||||
"path": "/server_side",
|
||||
"value": "required"
|
||||
"path": "/server_only",
|
||||
"value": true
|
||||
}]))
|
||||
.unwrap(),
|
||||
),
|
||||
@@ -287,16 +287,16 @@ async fn creating_loader_fields() {
|
||||
v.fields.get("game_versions").unwrap(),
|
||||
&json!(["1.20.1", "1.20.2"])
|
||||
);
|
||||
assert_eq!(v.fields.get("client_side").unwrap(), &json!("optional"));
|
||||
assert_eq!(v.fields.get("server_side").unwrap(), &json!("required"));
|
||||
assert_eq!(v.fields.get("singleplayer").unwrap(), &json!(false));
|
||||
assert_eq!(v.fields.get("server_only").unwrap(), &json!(true));
|
||||
// - Patch
|
||||
let resp = api
|
||||
.edit_version(
|
||||
alpha_version_id,
|
||||
json!({
|
||||
"game_versions": ["1.20.1", "1.20.2"],
|
||||
"client_side": "optional",
|
||||
"server_side": "required"
|
||||
"singleplayer": false,
|
||||
"server_only": true
|
||||
}),
|
||||
USER_USER_PAT,
|
||||
)
|
||||
@@ -314,16 +314,13 @@ async fn creating_loader_fields() {
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn get_loader_fields() {
|
||||
async fn get_loader_fields_variants() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let api = &test_env.api;
|
||||
|
||||
let game_versions = api
|
||||
.get_loader_field_variants_deserialized("game_versions")
|
||||
.await;
|
||||
let side_types = api
|
||||
.get_loader_field_variants_deserialized("client_side")
|
||||
.await;
|
||||
|
||||
// These tests match dummy data and will need to be updated if the dummy data changes
|
||||
// Versions should be ordered by:
|
||||
@@ -348,18 +345,64 @@ async fn get_loader_fields() {
|
||||
"1.20.1"
|
||||
]
|
||||
);
|
||||
|
||||
let side_type_names = side_types
|
||||
.into_iter()
|
||||
.map(|x| x.value)
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
side_type_names,
|
||||
["unknown", "required", "optional", "unsupported"]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn get_available_loader_fields() {
|
||||
// Get available loader fields for a given loader
|
||||
// (ie: which fields are relevant for 'fabric', etc)
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let api = &test_env.api;
|
||||
let loaders = api.get_loaders_deserialized().await;
|
||||
|
||||
let fabric_loader_fields = loaders
|
||||
.iter()
|
||||
.find(|x| x.name == "fabric")
|
||||
.unwrap()
|
||||
.supported_fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
fabric_loader_fields,
|
||||
[
|
||||
"game_versions",
|
||||
"singleplayer",
|
||||
"client_and_server",
|
||||
"client_only",
|
||||
"server_only",
|
||||
"test_fabric_optional" // exists for testing
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
);
|
||||
|
||||
let mrpack_loader_fields = loaders
|
||||
.iter()
|
||||
.find(|x| x.name == "mrpack")
|
||||
.unwrap()
|
||||
.supported_fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
mrpack_loader_fields,
|
||||
[
|
||||
"game_versions",
|
||||
"singleplayer",
|
||||
"client_and_server",
|
||||
"client_only",
|
||||
"server_only",
|
||||
// mrpack has all the general fields as well as this
|
||||
"mrpack_loaders"
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,24 @@ use actix_http::StatusCode;
|
||||
use actix_web::test;
|
||||
use bytes::Bytes;
|
||||
use chrono::{Duration, Utc};
|
||||
use common::api_v3::request_data::get_public_version_creation_data;
|
||||
use common::api_v3::ApiV3;
|
||||
use common::database::*;
|
||||
use common::dummy_data::DUMMY_CATEGORIES;
|
||||
|
||||
use common::environment::with_test_environment_all;
|
||||
use common::environment::{with_test_environment, with_test_environment_all, TestEnvironment};
|
||||
use common::permissions::{PermissionsTest, PermissionsTestContext};
|
||||
use futures::StreamExt;
|
||||
use labrinth::database::models::project_item::{PROJECTS_NAMESPACE, PROJECTS_SLUGS_NAMESPACE};
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::models::teams::ProjectPermissions;
|
||||
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::api_common::{ApiProject, ApiVersion};
|
||||
use crate::common::api_v3::request_data::get_public_project_creation_data_json;
|
||||
use crate::common::dummy_data::TestFile;
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -101,32 +106,11 @@ async fn test_get_project() {
|
||||
#[actix_rt::test]
|
||||
async fn test_add_remove_project() {
|
||||
// Test setup and dummy data
|
||||
with_test_environment_all(None, |test_env| async move {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let api = &test_env.api;
|
||||
|
||||
// Generate test project data.
|
||||
let mut json_data = json!(
|
||||
{
|
||||
"title": "Test_Add_Project project",
|
||||
"slug": "demo",
|
||||
"description": "Example description.",
|
||||
"body": "Example body.",
|
||||
"initial_versions": [{
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}],
|
||||
"categories": [],
|
||||
"license_id": "MIT"
|
||||
}
|
||||
);
|
||||
let mut json_data =
|
||||
get_public_project_creation_data_json("demo", Some(&TestFile::BasicMod));
|
||||
|
||||
// Basic json
|
||||
let json_segment = MultipartSegment {
|
||||
@@ -730,48 +714,27 @@ async fn permissions_edit_details() {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn permissions_upload_version() {
|
||||
with_test_environment_all(None, |test_env| async move {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
let alpha_project_id = &test_env.dummy.as_ref().unwrap().project_alpha.project_id;
|
||||
let alpha_version_id = &test_env.dummy.as_ref().unwrap().project_alpha.version_id;
|
||||
let alpha_team_id = &test_env.dummy.as_ref().unwrap().project_alpha.team_id;
|
||||
let alpha_file_hash = &test_env.dummy.as_ref().unwrap().project_alpha.file_hash;
|
||||
|
||||
let upload_version = ProjectPermissions::UPLOAD_VERSION;
|
||||
|
||||
// Upload version with basic-mod.jar
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::post().uri("/v3/version").set_multipart([
|
||||
MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(
|
||||
serde_json::to_string(&json!({
|
||||
"project_id": ctx.project_id.unwrap(),
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.0.0",
|
||||
"version_title": "1.0.0",
|
||||
"version_type": "release",
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"],
|
||||
"loaders": ["fabric"],
|
||||
"featured": false,
|
||||
|
||||
}))
|
||||
.unwrap(),
|
||||
),
|
||||
},
|
||||
MultipartSegment {
|
||||
name: "basic-mod.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../tests/files/basic-mod.jar").to_vec(),
|
||||
),
|
||||
},
|
||||
])
|
||||
let project_id = ctx.project_id.unwrap();
|
||||
let project_id = ProjectId(parse_base62(project_id).unwrap());
|
||||
let multipart = get_public_version_creation_data(
|
||||
project_id,
|
||||
"1.0.0",
|
||||
TestFile::BasicMod,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
test::TestRequest::post()
|
||||
.uri("/v3/version")
|
||||
.set_multipart(multipart.segment_data)
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.simple_project_permissions_test(upload_version, req_gen)
|
||||
|
||||
@@ -2,9 +2,16 @@ use actix_web::test::{self, TestRequest};
|
||||
use bytes::Bytes;
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
use common::environment::with_test_environment_all;
|
||||
use common::api_v3::request_data::{
|
||||
get_public_project_creation_data, get_public_version_creation_data,
|
||||
};
|
||||
use common::api_v3::ApiV3;
|
||||
use common::dummy_data::TestFile;
|
||||
use common::environment::{with_test_environment, with_test_environment_all, TestEnvironment};
|
||||
use common::{database::*, scopes::ScopeTest};
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::pats::Scopes;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -201,93 +208,37 @@ pub async fn notifications_scopes() {
|
||||
// Project version creation scopes
|
||||
#[actix_rt::test]
|
||||
pub async fn project_version_create_scopes() {
|
||||
with_test_environment_all(None, |test_env| async move {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV3>| async move {
|
||||
// Create project
|
||||
let create_project = Scopes::PROJECT_CREATE;
|
||||
let json_data = json!(
|
||||
{
|
||||
"title": "Test_Add_Project project",
|
||||
"slug": "demo",
|
||||
"description": "Example description.",
|
||||
"body": "Example body.",
|
||||
"initial_versions": [{
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}],
|
||||
"categories": [],
|
||||
"license_id": "MIT"
|
||||
}
|
||||
);
|
||||
let json_segment = MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
|
||||
};
|
||||
let file_segment = MultipartSegment {
|
||||
name: "basic-mod.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../tests/files/basic-mod.jar").to_vec(),
|
||||
),
|
||||
};
|
||||
|
||||
let req_gen = || {
|
||||
let creation_data =
|
||||
get_public_project_creation_data("demo", Some(TestFile::BasicMod), None);
|
||||
test::TestRequest::post()
|
||||
.uri("/v3/project")
|
||||
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
|
||||
.set_multipart(creation_data.segment_data)
|
||||
};
|
||||
let (_, success) = ScopeTest::new(&test_env)
|
||||
.test(req_gen, create_project)
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = success["id"].as_str().unwrap();
|
||||
let project_id = ProjectId(parse_base62(project_id).unwrap());
|
||||
|
||||
// Add version to project
|
||||
let create_version = Scopes::VERSION_CREATE;
|
||||
let json_data = json!(
|
||||
{
|
||||
"project_id": project_id,
|
||||
"file_parts": ["basic-mod-different.jar"],
|
||||
"version_number": "1.2.3.4",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}
|
||||
);
|
||||
let json_segment = MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
|
||||
};
|
||||
let file_segment = MultipartSegment {
|
||||
name: "basic-mod-different.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../tests/files/basic-mod-different.jar").to_vec(),
|
||||
),
|
||||
};
|
||||
|
||||
let req_gen = || {
|
||||
let creation_data = get_public_version_creation_data(
|
||||
project_id,
|
||||
"1.2.3.4",
|
||||
TestFile::BasicModDifferent,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
test::TestRequest::post()
|
||||
.uri("/v3/version")
|
||||
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
|
||||
.set_multipart(creation_data.segment_data)
|
||||
};
|
||||
ScopeTest::new(&test_env)
|
||||
.test(req_gen, create_version)
|
||||
|
||||
@@ -66,7 +66,7 @@ async fn search_projects() {
|
||||
let id = 0;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[4..6] },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_side", "value": "required" },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||
]))
|
||||
.unwrap();
|
||||
@@ -81,7 +81,7 @@ async fn search_projects() {
|
||||
let id = 1;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_side", "value": "optional" },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
||||
]))
|
||||
.unwrap();
|
||||
project_creation_futures.push(create_async_future(
|
||||
@@ -95,7 +95,7 @@ async fn search_projects() {
|
||||
let id = 2;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_side", "value": "required" },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
||||
{ "op": "add", "path": "/title", "value": "Mysterious Project" },
|
||||
]))
|
||||
.unwrap();
|
||||
@@ -110,7 +110,7 @@ async fn search_projects() {
|
||||
let id = 3;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_side", "value": "required" },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.4"] },
|
||||
{ "op": "add", "path": "/title", "value": "Mysterious Project" },
|
||||
{ "op": "add", "path": "/license_id", "value": "LicenseRef-All-Rights-Reserved" },
|
||||
@@ -127,7 +127,7 @@ async fn search_projects() {
|
||||
let id = 4;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_side", "value": "optional" },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
||||
]))
|
||||
.unwrap();
|
||||
@@ -142,7 +142,7 @@ async fn search_projects() {
|
||||
let id = 5;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_side", "value": "optional" },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||
]))
|
||||
@@ -158,8 +158,8 @@ async fn search_projects() {
|
||||
let id = 6;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_side", "value": "optional" },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_side", "value": "required" },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||
]))
|
||||
.unwrap();
|
||||
@@ -176,8 +176,8 @@ async fn search_projects() {
|
||||
let id = 7;
|
||||
let modify_json = serde_json::from_value(json!([
|
||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_side", "value": "optional" },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_side", "value": "required" },
|
||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||
{ "op": "add", "path": "/initial_versions/0/loaders", "value": ["forge"] },
|
||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.2"] },
|
||||
@@ -236,8 +236,8 @@ async fn search_projects() {
|
||||
vec![1, 2, 3, 4],
|
||||
),
|
||||
(json!([["project_types:modpack"]]), vec![4]),
|
||||
(json!([["client_side:required"]]), vec![0, 2, 3, 7]),
|
||||
(json!([["server_side:required"]]), vec![0, 2, 3, 6, 7]),
|
||||
(json!([["client_only:true"]]), vec![0, 2, 3, 7]),
|
||||
(json!([["server_only:true"]]), vec![0, 2, 3, 6, 7]),
|
||||
(json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7]),
|
||||
(json!([["license:MIT"]]), vec![1, 2, 4]),
|
||||
(json!([[r#"title:'Mysterious Project'"#]]), vec![2, 3]),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::common::{
|
||||
api_common::ApiProject,
|
||||
api_v2::ApiV2,
|
||||
api_v2::{
|
||||
request_data::{get_public_project_creation_data_json, get_public_version_creation_data},
|
||||
ApiV2,
|
||||
},
|
||||
database::{ENEMY_USER_PAT, FRIEND_USER_ID, FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT},
|
||||
dummy_data::{TestFile, DUMMY_CATEGORIES},
|
||||
environment::{with_test_environment, TestEnvironment},
|
||||
@@ -10,7 +13,7 @@ use actix_web::test;
|
||||
use itertools::Itertools;
|
||||
use labrinth::{
|
||||
database::models::project_item::PROJECTS_SLUGS_NAMESPACE,
|
||||
models::teams::ProjectPermissions,
|
||||
models::{ids::base62_impl::parse_base62, projects::ProjectId, teams::ProjectPermissions},
|
||||
util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData},
|
||||
};
|
||||
use serde_json::json;
|
||||
@@ -63,28 +66,8 @@ async fn test_add_remove_project() {
|
||||
let api = &test_env.api;
|
||||
|
||||
// Generate test project data.
|
||||
let mut json_data = json!(
|
||||
{
|
||||
"title": "Test_Add_Project project",
|
||||
"slug": "demo",
|
||||
"description": "Example description.",
|
||||
"body": "Example body.",
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"initial_versions": [{
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}],
|
||||
"categories": [],
|
||||
"license_id": "MIT"
|
||||
}
|
||||
);
|
||||
let mut json_data =
|
||||
get_public_project_creation_data_json("demo", Some(&TestFile::BasicMod));
|
||||
|
||||
// Basic json
|
||||
let json_segment = MultipartSegment {
|
||||
@@ -251,36 +234,18 @@ async fn permissions_upload_version() {
|
||||
|
||||
// Upload version with basic-mod.jar
|
||||
let req_gen = |ctx: &PermissionsTestContext| {
|
||||
test::TestRequest::post().uri("/v2/version").set_multipart([
|
||||
MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(
|
||||
serde_json::to_string(&json!({
|
||||
"project_id": ctx.project_id.unwrap(),
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.0.0",
|
||||
"version_title": "1.0.0",
|
||||
"version_type": "release",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"],
|
||||
"loaders": ["fabric"],
|
||||
"featured": false,
|
||||
|
||||
}))
|
||||
.unwrap(),
|
||||
),
|
||||
},
|
||||
MultipartSegment {
|
||||
name: "basic-mod.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../../tests/files/basic-mod.jar").to_vec(),
|
||||
),
|
||||
},
|
||||
])
|
||||
let project_id = ctx.project_id.unwrap();
|
||||
let project_id = ProjectId(parse_base62(project_id).unwrap());
|
||||
let multipart = get_public_version_creation_data(
|
||||
project_id,
|
||||
"1.0.0",
|
||||
TestFile::BasicMod,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
test::TestRequest::post()
|
||||
.uri("/v2/version")
|
||||
.set_multipart(multipart.segment_data)
|
||||
};
|
||||
PermissionsTest::new(&test_env)
|
||||
.simple_project_permissions_test(upload_version, req_gen)
|
||||
@@ -491,7 +456,7 @@ pub async fn test_patch_project() {
|
||||
"issues_url": "https://github.com",
|
||||
"discord_url": "https://discord.gg",
|
||||
"wiki_url": "https://wiki.com",
|
||||
"client_side": "optional",
|
||||
"client_side": "unsupported",
|
||||
"server_side": "required",
|
||||
"donation_urls": [{
|
||||
"id": "patreon",
|
||||
@@ -520,7 +485,11 @@ pub async fn test_patch_project() {
|
||||
assert_eq!(project.issues_url, Some("https://github.com".to_string()));
|
||||
assert_eq!(project.discord_url, Some("https://discord.gg".to_string()));
|
||||
assert_eq!(project.wiki_url, Some("https://wiki.com".to_string()));
|
||||
assert_eq!(project.client_side.as_str(), "optional");
|
||||
// Note: the original V2 value of this was "optional",
|
||||
// but Required/Optional is no longer a carried combination in v3, as the changes made were lossy.
|
||||
// Now, the test Required/Unsupported combination is tested instead.
|
||||
// Setting Required/Optional in v2 will not work, this is known and accepteed.
|
||||
assert_eq!(project.client_side.as_str(), "unsupported");
|
||||
assert_eq!(project.server_side.as_str(), "required");
|
||||
assert_eq!(project.donation_urls.unwrap()[0].url, "https://patreon.com");
|
||||
})
|
||||
|
||||
@@ -1,104 +1,50 @@
|
||||
use crate::common::api_v2::request_data::get_public_project_creation_data;
|
||||
use crate::common::api_v2::request_data::get_public_version_creation_data;
|
||||
use crate::common::api_v2::ApiV2;
|
||||
use crate::common::dummy_data::TestFile;
|
||||
use crate::common::environment::with_test_environment;
|
||||
use crate::common::environment::TestEnvironment;
|
||||
use crate::common::scopes::ScopeTest;
|
||||
use actix_web::test;
|
||||
use labrinth::models::ids::base62_impl::parse_base62;
|
||||
use labrinth::models::pats::Scopes;
|
||||
use labrinth::models::projects::ProjectId;
|
||||
use labrinth::util::actix::AppendsMultipart;
|
||||
use labrinth::util::actix::MultipartSegment;
|
||||
use labrinth::util::actix::MultipartSegmentData;
|
||||
|
||||
use serde_json::json;
|
||||
// Project version creation scopes
|
||||
#[actix_rt::test]
|
||||
pub async fn project_version_create_scopes() {
|
||||
with_test_environment(None, |test_env: TestEnvironment<ApiV2>| async move {
|
||||
// Create project
|
||||
let create_project = Scopes::PROJECT_CREATE;
|
||||
let json_data = json!(
|
||||
{
|
||||
"title": "Test_Add_Project project",
|
||||
"slug": "demo",
|
||||
"description": "Example description.",
|
||||
"body": "Example body.",
|
||||
"initial_versions": [{
|
||||
"file_parts": ["basic-mod.jar"],
|
||||
"version_number": "1.2.3",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}],
|
||||
"categories": [],
|
||||
"license_id": "MIT"
|
||||
}
|
||||
);
|
||||
let json_segment = MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
|
||||
};
|
||||
let file_segment = MultipartSegment {
|
||||
name: "basic-mod.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../../tests/files/basic-mod.jar").to_vec(),
|
||||
),
|
||||
};
|
||||
|
||||
let req_gen = || {
|
||||
let creation_data =
|
||||
get_public_project_creation_data("demo", Some(TestFile::BasicMod), None);
|
||||
test::TestRequest::post()
|
||||
.uri("/v3/project")
|
||||
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
|
||||
.uri("/v2/project")
|
||||
.set_multipart(creation_data.segment_data)
|
||||
};
|
||||
let (_, success) = ScopeTest::new(&test_env)
|
||||
.test(req_gen, create_project)
|
||||
.await
|
||||
.unwrap();
|
||||
let project_id = success["id"].as_str().unwrap();
|
||||
let project_id = ProjectId(parse_base62(project_id).unwrap());
|
||||
|
||||
// Add version to project
|
||||
let create_version = Scopes::VERSION_CREATE;
|
||||
let json_data = json!(
|
||||
{
|
||||
"project_id": project_id,
|
||||
"file_parts": ["basic-mod-different.jar"],
|
||||
"version_number": "1.2.3.4",
|
||||
"version_title": "start",
|
||||
"dependencies": [],
|
||||
"game_versions": ["1.20.1"] ,
|
||||
"client_side": "required",
|
||||
"server_side": "optional",
|
||||
"release_channel": "release",
|
||||
"loaders": ["fabric"],
|
||||
"featured": true
|
||||
}
|
||||
);
|
||||
let json_segment = MultipartSegment {
|
||||
name: "data".to_string(),
|
||||
filename: None,
|
||||
content_type: Some("application/json".to_string()),
|
||||
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
|
||||
};
|
||||
let file_segment = MultipartSegment {
|
||||
name: "basic-mod-different.jar".to_string(),
|
||||
filename: Some("basic-mod.jar".to_string()),
|
||||
content_type: Some("application/java-archive".to_string()),
|
||||
data: MultipartSegmentData::Binary(
|
||||
include_bytes!("../../tests/files/basic-mod-different.jar").to_vec(),
|
||||
),
|
||||
};
|
||||
|
||||
let req_gen = || {
|
||||
let creation_data = get_public_version_creation_data(
|
||||
project_id,
|
||||
"1.2.3.4",
|
||||
TestFile::BasicModDifferent,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
test::TestRequest::post()
|
||||
.uri("/v3/version")
|
||||
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
|
||||
.uri("/v2/version")
|
||||
.set_multipart(creation_data.segment_data)
|
||||
};
|
||||
ScopeTest::new(&test_env)
|
||||
.test(req_gen, create_version)
|
||||
|
||||
@@ -214,6 +214,15 @@ async fn search_projects() {
|
||||
// 1. vec of search facets
|
||||
// 2. expected project ids to be returned by this search
|
||||
let pairs = vec![
|
||||
// For testing: remove me
|
||||
(
|
||||
json!([
|
||||
["client_side:required"],
|
||||
["versions:1.20.5"],
|
||||
[&format!("categories:{}", DUMMY_CATEGORIES[5])]
|
||||
]),
|
||||
vec![],
|
||||
),
|
||||
(json!([["categories:fabric"]]), vec![0, 1, 2, 3, 4, 5, 6, 7]),
|
||||
(json!([["categories:forge"]]), vec![7]),
|
||||
(
|
||||
@@ -229,7 +238,9 @@ async fn search_projects() {
|
||||
vec![1, 2, 3, 4],
|
||||
),
|
||||
(json!([["project_types:modpack"]]), vec![4]),
|
||||
(json!([["client_side:required"]]), vec![0, 2, 3, 7]),
|
||||
// Formerly included 7, but with v2 changes, this is no longer the case.
|
||||
// This is because we assume client_side/server_side with subsequent versions.
|
||||
(json!([["client_side:required"]]), vec![0, 2, 3]),
|
||||
(json!([["server_side:required"]]), vec![0, 2, 3, 6, 7]),
|
||||
(json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7]),
|
||||
(json!([["license:MIT"]]), vec![1, 2, 4]),
|
||||
|
||||
@@ -428,9 +428,9 @@ async fn add_version_project_types_v2() {
|
||||
.get_project_deserialized(&test_project.slug.unwrap(), USER_USER_PAT)
|
||||
.await;
|
||||
assert_eq!(test_project.project_type, "unknown"); // No project_type set, as no versions are set
|
||||
// This is a known difference between older v2 ,but is acceptable.
|
||||
// This would be the appropriate test on older v2:
|
||||
// assert_eq!(test_project.project_type, "modpack");
|
||||
// This is a known difference between older v2 ,but is acceptable.
|
||||
// This would be the appropriate test on older v2:
|
||||
// assert_eq!(test_project.project_type, "modpack");
|
||||
|
||||
// Create a version with a modpack file attached
|
||||
let test_version = api
|
||||
|
||||
Reference in New Issue
Block a user