implement create new map endpoint
This commit is contained in:
parent
1eb81092d7
commit
ee57f06544
4 changed files with 122 additions and 7 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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(¶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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue