diff options
Diffstat (limited to 'planetwars-server/src/routes')
-rw-r--r-- | planetwars-server/src/routes/maps.rs | 122 |
1 files changed, 116 insertions, 6 deletions
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<Json<Vec<ApiMap>>, 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<Arc<GlobalConfig>>, + params: Json<CreateMapRequest>, +) -> Result<Json<ApiMap>, (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<String> = 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<usize> = 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(()) +} |