1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
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};
#[derive(Serialize, Deserialize)]
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_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(())
}
|