use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; use crate::models::ids::ImageId; use crate::models::projects::{ Dependency, FileType, Loader, ProjectId, Version, VersionId, VersionStatus, VersionType, }; use crate::models::v2::projects::LegacyVersion; use crate::queue::session::AuthQueue; use crate::routes::v3::project_creation::CreateError; use crate::routes::{v2_reroute, v3}; use actix_multipart::Multipart; use actix_web::web::Data; use actix_web::{post, web, HttpRequest, HttpResponse}; use serde::{Deserialize, Serialize}; use serde_json::json; use sqlx::postgres::PgPool; use std::collections::HashMap; use std::sync::Arc; use validator::Validate; pub fn default_requested_status() -> VersionStatus { VersionStatus::Listed } #[derive(Serialize, Deserialize, Validate, Clone)] pub struct InitialVersionData { #[serde(alias = "mod_id")] pub project_id: Option, #[validate(length(min = 1, max = 256))] pub file_parts: Vec, #[validate( length(min = 1, max = 32), regex = "crate::util::validate::RE_URL_SAFE" )] pub version_number: String, #[validate( length(min = 1, max = 64), custom(function = "crate::util::validate::validate_name") )] #[serde(alias = "name")] pub version_title: String, #[validate(length(max = 65536))] #[serde(alias = "changelog")] pub version_body: Option, #[validate( length(min = 0, max = 4096), custom(function = "crate::util::validate::validate_deps") )] pub dependencies: Vec, #[validate(length(min = 1))] pub game_versions: Vec, #[serde(alias = "version_type")] pub release_channel: VersionType, #[validate(length(min = 1))] pub loaders: Vec, pub featured: bool, pub primary_file: Option, #[serde(default = "default_requested_status")] pub status: VersionStatus, #[serde(default = "HashMap::new")] pub file_types: HashMap>, // Associations to uploaded images in changelog #[validate(length(max = 10))] #[serde(default)] pub uploaded_images: Vec, // The ordering relative to other versions pub ordering: Option, } #[derive(Serialize, Deserialize, Clone)] struct InitialFileData { #[serde(default = "HashMap::new")] pub file_types: HashMap>, } // under `/api/v1/version` #[post("version")] pub async fn version_create( req: HttpRequest, payload: Multipart, client: Data, redis: Data, file_host: Data>, session_queue: Data, ) -> Result { let payload = v2_reroute::alter_actix_multipart( 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), ); // 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; } // 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, }) }, ) .await?; // Call V3 project creation let response = v3::version_creation::version_create( req, payload, client.clone(), redis.clone(), file_host, session_queue, ) .await?; // Convert response to V2 format match v2_reroute::extract_ok_json::(response).await { Ok(version) => { let v2_version = LegacyVersion::from(version); Ok(HttpResponse::Ok().json(v2_version)) } Err(response) => Ok(response), } } // under /api/v1/version/{version_id} #[post("{version_id}/file")] pub async fn upload_file_to_version( req: HttpRequest, url_data: web::Path<(VersionId,)>, payload: Multipart, client: Data, redis: Data, file_host: Data>, session_queue: web::Data, ) -> Result { let response = v3::version_creation::upload_file_to_version( req, url_data, payload, client.clone(), redis.clone(), file_host, session_queue, ) .await?; Ok(response) }