aboutsummaryrefslogtreecommitdiff
path: root/planetwars-server
diff options
context:
space:
mode:
Diffstat (limited to 'planetwars-server')
-rw-r--r--planetwars-server/Cargo.toml1
-rw-r--r--planetwars-server/src/lib.rs5
-rw-r--r--planetwars-server/src/routes/maps.rs122
3 files changed, 121 insertions, 7 deletions
diff --git a/planetwars-server/Cargo.toml b/planetwars-server/Cargo.toml
index 183bb90..735836e 100644
--- a/planetwars-server/Cargo.toml
+++ b/planetwars-server/Cargo.toml
@@ -34,6 +34,7 @@ serde_json = "1.0"
base64 = "0.13.0"
zip = "0.5"
toml = "0.5"
+planetwars-rules = { path = "../planetwars-rules" }
planetwars-matchrunner = { path = "../planetwars-matchrunner" }
config = { version = "0.12", features = ["toml"] }
thiserror = "1.0.31"
diff --git a/planetwars-server/src/lib.rs b/planetwars-server/src/lib.rs
index 316458c..2045eda 100644
--- a/planetwars-server/src/lib.rs
+++ b/planetwars-server/src/lib.rs
@@ -137,7 +137,10 @@ fn api() -> Router {
"/matches/:match_id/log",
get(routes::matches::get_match_log),
)
- .route("/maps", get(routes::maps::list_maps))
+ .route(
+ "/maps",
+ get(routes::maps::list_maps).post(routes::maps::create_map),
+ )
.route("/leaderboard", get(routes::bots::get_ranking))
.route("/submit_bot", post(routes::demo::submit_bot))
.route("/save_bot", post(routes::bots::save_bot))
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(&params.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(&params.name) {
+ return Err((
+ StatusCode::BAD_REQUEST,
+ json!({
+ "error": error,
+ })
+ .to_string(),
+ ));
+ }
+
+ if let Err(error) = check_map(&params.map) {
+ return Err((
+ StatusCode::BAD_REQUEST,
+ json!({
+ "error": error,
+ })
+ .to_string(),
+ ));
+ }
+
+ let rel_map_path = format!("{}.json", &params.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, &params.map).expect("failed to write map");
+ }
+ let map = db::maps::create_map(
+ db::maps::NewMap {
+ name: &params.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(())
+}