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",
|
||||
"parking_lot 0.12.1",
|
||||
"planetwars-matchrunner",
|
||||
"planetwars-rules",
|
||||
"prost",
|
||||
"rand 0.8.4",
|
||||
"rust-argon2",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue