From ee57f06544a7281138ee909995fde4a1b3993c4b Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Wed, 23 Nov 2022 17:33:39 +0100 Subject: implement create new map endpoint --- planetwars-server/src/routes/maps.rs | 122 +++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 6 deletions(-) (limited to 'planetwars-server/src/routes') diff --git a/planetwars-server/src/routes/maps.rs b/planetwars-server/src/routes/maps.rs index 188089f..cef0b4a 100644 --- a/planetwars-server/src/routes/maps.rs +++ b/planetwars-server/src/routes/maps.rs @@ -1,5 +1,8 @@ -use crate::{db, DatabaseConnection}; -use axum::Json; +use std::{collections::HashSet, fs::File, path::PathBuf, sync::Arc}; + +use crate::{db, DatabaseConnection, GlobalConfig}; +use axum::{Extension, Json}; +use diesel::OptionalExtension; use hyper::StatusCode; use serde::{Deserialize, Serialize}; @@ -8,12 +11,119 @@ pub struct ApiMap { pub name: String, } +fn map_into_api_map(map: db::maps::Map) -> ApiMap { + ApiMap { name: map.name } +} + pub async fn list_maps(mut conn: DatabaseConnection) -> Result>, StatusCode> { let maps = db::maps::list_maps(&mut conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - let api_maps = maps - .into_iter() - .map(|map| ApiMap { name: map.name }) - .collect(); + let api_maps = maps.into_iter().map(map_into_api_map).collect(); Ok(Json(api_maps)) } + +use planetwars_rules::config::Map as PlanetwarsMap; +use serde_json::json; + +#[derive(Serialize, Deserialize)] +pub struct CreateMapRequest { + name: String, + #[serde(flatten)] + map: PlanetwarsMap, +} + +pub async fn create_map( + mut conn: DatabaseConnection, + Extension(config): Extension>, + params: Json, +) -> Result, (StatusCode, String)> { + match db::maps::find_map_by_name(¶ms.name, &mut conn).optional() { + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + json!({ + "error": "internal error" + }) + .to_string(), + )) + } + Ok(Some(_)) => { + return Err(( + StatusCode::BAD_REQUEST, + json!({ + "error": "name taken", + }) + .to_string(), + )) + } + Ok(None) => {} + }; + + if let Err(error) = check_map_name(¶ms.name) { + return Err(( + StatusCode::BAD_REQUEST, + json!({ + "error": error, + }) + .to_string(), + )); + } + + if let Err(error) = check_map(¶ms.map) { + return Err(( + StatusCode::BAD_REQUEST, + json!({ + "error": error, + }) + .to_string(), + )); + } + + let rel_map_path = format!("{}.json", ¶ms.name); + + { + let full_map_path = PathBuf::from(&config.maps_directory).join(&rel_map_path); + let file = File::create(full_map_path).expect("failed to open file"); + serde_json::to_writer_pretty(file, ¶ms.map).expect("failed to write map"); + } + let map = db::maps::create_map( + db::maps::NewMap { + name: ¶ms.name, + file_path: &rel_map_path, + }, + &mut conn, + ) + .expect("failed to save map"); + + Ok(Json(map_into_api_map(map))) +} + +fn check_map(map: &PlanetwarsMap) -> Result<(), &str> { + let unique_names: HashSet = map.planets.iter().map(|p| p.name.clone()).collect(); + if unique_names.len() != map.planets.len() { + return Err("planet names not unique"); + } + let players: HashSet = map.planets.iter().filter_map(|p| p.owner).collect(); + + if players != HashSet::from([1, 2]) { + return Err("maps should have player 1 and 2"); + } + + Ok(()) +} + +// TODO: remove duplication (bot name, user name) +fn check_map_name(name: &str) -> Result<(), &str> { + if !name + .chars() + .all(|c| !c.is_uppercase() && (c.is_ascii_alphanumeric() || c == '_' || c == '-')) + { + return Err("Only [a-z-_] are allowed in map names"); + } + + if name.len() < 3 || name.len() > 32 { + return Err("map name should be between 3 and 32 characters"); + } + + Ok(()) +} -- cgit v1.2.3