implement create new map endpoint

This commit is contained in:
Ilion Beyst 2022-11-23 17:33:39 +01:00
parent 1eb81092d7
commit ee57f06544
4 changed files with 122 additions and 7 deletions

1
Cargo.lock generated
View file

@ -1620,6 +1620,7 @@ dependencies = [
"hyper", "hyper",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"planetwars-matchrunner", "planetwars-matchrunner",
"planetwars-rules",
"prost", "prost",
"rand 0.8.4", "rand 0.8.4",
"rust-argon2", "rust-argon2",

View file

@ -34,6 +34,7 @@ serde_json = "1.0"
base64 = "0.13.0" base64 = "0.13.0"
zip = "0.5" zip = "0.5"
toml = "0.5" toml = "0.5"
planetwars-rules = { path = "../planetwars-rules" }
planetwars-matchrunner = { path = "../planetwars-matchrunner" } planetwars-matchrunner = { path = "../planetwars-matchrunner" }
config = { version = "0.12", features = ["toml"] } config = { version = "0.12", features = ["toml"] }
thiserror = "1.0.31" thiserror = "1.0.31"

View file

@ -137,7 +137,10 @@ fn api() -> Router {
"/matches/:match_id/log", "/matches/:match_id/log",
get(routes::matches::get_match_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("/leaderboard", get(routes::bots::get_ranking))
.route("/submit_bot", post(routes::demo::submit_bot)) .route("/submit_bot", post(routes::demo::submit_bot))
.route("/save_bot", post(routes::bots::save_bot)) .route("/save_bot", post(routes::bots::save_bot))

View file

@ -1,5 +1,8 @@
use crate::{db, DatabaseConnection}; use std::{collections::HashSet, fs::File, path::PathBuf, sync::Arc};
use axum::Json;
use crate::{db, DatabaseConnection, GlobalConfig};
use axum::{Extension, Json};
use diesel::OptionalExtension;
use hyper::StatusCode; use hyper::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,12 +11,119 @@ pub struct ApiMap {
pub name: String, 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> { 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 maps = db::maps::list_maps(&mut conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let api_maps = maps let api_maps = maps.into_iter().map(map_into_api_map).collect();
.into_iter()
.map(|map| ApiMap { name: map.name })
.collect();
Ok(Json(api_maps)) 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(())
}