aboutsummaryrefslogtreecommitdiff
path: root/planetwars-server/src
diff options
context:
space:
mode:
Diffstat (limited to 'planetwars-server/src')
-rw-r--r--planetwars-server/src/db/matches.rs97
-rw-r--r--planetwars-server/src/db/mod.rs1
-rw-r--r--planetwars-server/src/lib.rs5
-rw-r--r--planetwars-server/src/routes/matches.rs63
-rw-r--r--planetwars-server/src/schema.rs20
5 files changed, 177 insertions, 9 deletions
diff --git a/planetwars-server/src/db/matches.rs b/planetwars-server/src/db/matches.rs
new file mode 100644
index 0000000..85f0631
--- /dev/null
+++ b/planetwars-server/src/db/matches.rs
@@ -0,0 +1,97 @@
+use chrono::NaiveDateTime;
+use diesel::{BelongingToDsl, RunQueryDsl};
+use diesel::{Connection, GroupedBy, PgConnection, QueryResult};
+
+use crate::schema::{match_players, matches};
+
+#[derive(Insertable)]
+#[table_name = "matches"]
+pub struct NewMatch<'a> {
+ pub log_path: &'a str,
+}
+
+#[derive(Insertable)]
+#[table_name = "match_players"]
+pub struct NewMatchPlayer {
+ /// id of the match this player is in
+ pub match_id: i32,
+ /// id of the bot behind this player
+ pub bot_id: i32,
+ /// player id within the match
+ pub player_id: i32,
+}
+
+#[derive(Queryable, Identifiable)]
+#[table_name = "matches"]
+pub struct MatchBase {
+ pub id: i32,
+ pub log_path: String,
+ pub created_at: NaiveDateTime,
+}
+
+#[derive(Queryable, Identifiable, Associations)]
+#[primary_key(match_id, player_id)]
+#[belongs_to(MatchBase, foreign_key = "match_id")]
+pub struct MatchPlayer {
+ pub match_id: i32,
+ pub bot_id: i32,
+ pub player_id: i32,
+}
+
+pub struct MatchPlayerData {
+ pub bot_id: i32,
+}
+
+pub fn create_match(
+ match_data: &NewMatch,
+ match_players: &[MatchPlayerData],
+ conn: &PgConnection,
+) -> QueryResult<i32> {
+ conn.transaction(|| {
+ let match_base = diesel::insert_into(matches::table)
+ .values(match_data)
+ .get_result::<MatchBase>(conn)?;
+
+ let match_players = match_players
+ .iter()
+ .enumerate()
+ .map(|(num, player_data)| NewMatchPlayer {
+ match_id: match_base.id,
+ bot_id: player_data.bot_id,
+ player_id: num as i32,
+ })
+ .collect::<Vec<_>>();
+
+ diesel::insert_into(match_players::table)
+ .values(&match_players)
+ .execute(conn)?;
+
+ Ok(match_base.id)
+ })
+}
+
+pub struct MatchData {
+ pub base: MatchBase,
+ pub match_players: Vec<MatchPlayer>,
+}
+
+pub fn list_matches(conn: &PgConnection) -> QueryResult<Vec<MatchData>> {
+ conn.transaction(|| {
+ let matches = matches::table.get_results::<MatchBase>(conn)?;
+
+ let match_players = MatchPlayer::belonging_to(&matches)
+ .load::<MatchPlayer>(conn)?
+ .grouped_by(&matches);
+
+ let res = matches
+ .into_iter()
+ .zip(match_players.into_iter())
+ .map(|(base, players)| MatchData {
+ base,
+ match_players: players.into_iter().collect(),
+ })
+ .collect();
+
+ Ok(res)
+ })
+}
diff --git a/planetwars-server/src/db/mod.rs b/planetwars-server/src/db/mod.rs
index 947b789..7a950c6 100644
--- a/planetwars-server/src/db/mod.rs
+++ b/planetwars-server/src/db/mod.rs
@@ -1,3 +1,4 @@
pub mod bots;
+pub mod matches;
pub mod sessions;
pub mod users;
diff --git a/planetwars-server/src/lib.rs b/planetwars-server/src/lib.rs
index 1d08580..7eaa5a0 100644
--- a/planetwars-server/src/lib.rs
+++ b/planetwars-server/src/lib.rs
@@ -48,7 +48,10 @@ pub async fn api() -> Router {
"/bots/:bot_id/upload",
post(routes::bots::upload_code_multipart),
)
- .route("/matches", post(routes::matches::play_match))
+ .route(
+ "/matches",
+ get(routes::matches::list_matches).post(routes::matches::play_match),
+ )
.layer(AddExtensionLayer::new(pool));
api
}
diff --git a/planetwars-server/src/routes/matches.rs b/planetwars-server/src/routes/matches.rs
index 4a556af..f2c5186 100644
--- a/planetwars-server/src/routes/matches.rs
+++ b/planetwars-server/src/routes/matches.rs
@@ -1,14 +1,14 @@
use std::path::PathBuf;
-use axum::Json;
+use axum::{extract::Extension, Json};
use hyper::StatusCode;
use planetwars_matchrunner::{run_match, MatchConfig, MatchPlayer};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize};
use crate::{
- db::{bots, users::User},
- DatabaseConnection, BOTS_DIR, MAPS_DIR, MATCHES_DIR,
+ db::{bots, matches, users::User},
+ ConnectionPool, DatabaseConnection, BOTS_DIR, MAPS_DIR, MATCHES_DIR,
};
#[derive(Serialize, Deserialize, Debug)]
@@ -18,10 +18,11 @@ pub struct MatchParams {
}
pub async fn play_match(
- user: User,
- conn: DatabaseConnection,
+ _user: User,
+ Extension(pool): Extension<ConnectionPool>,
Json(params): Json<MatchParams>,
) -> Result<(), StatusCode> {
+ let conn = pool.get().await.expect("could not get database connection");
let map_path = PathBuf::from(MAPS_DIR).join("hex.json");
let slug: String = rand::thread_rng()
@@ -32,6 +33,7 @@ pub async fn play_match(
let log_path = PathBuf::from(MATCHES_DIR).join(&format!("{}.log", slug));
let mut players = Vec::new();
+ let mut bot_ids = Vec::new();
for bot_name in params.players {
let bot = bots::find_bot(bot_name, &conn).map_err(|_| StatusCode::BAD_REQUEST)?;
let code_bundle =
@@ -49,19 +51,66 @@ pub async fn play_match(
// TODO: this is an user error, should ideally be handled before we get here
.ok_or_else(|| StatusCode::INTERNAL_SERVER_ERROR)?,
});
+
+ bot_ids.push(matches::MatchPlayerData { bot_id: bot.id });
}
let match_config = MatchConfig {
map_name: "hex".to_string(),
map_path,
log_path: log_path.clone(),
- players,
+ players: players,
};
- tokio::spawn(run_match(match_config));
+ tokio::spawn(run_match_task(match_config, bot_ids, pool.clone()));
Ok(())
}
+async fn run_match_task(
+ config: MatchConfig,
+ match_players: Vec<matches::MatchPlayerData>,
+ pool: ConnectionPool,
+) {
+ let log_path = config.log_path.as_os_str().to_str().unwrap().to_string();
+ let match_data = matches::NewMatch {
+ log_path: &log_path,
+ };
+
+ run_match(config).await;
+ let conn = pool.get().await.expect("could not get database connection");
+ matches::create_match(&match_data, &match_players, &conn).expect("could not create match");
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ApiMatch {
+ id: i32,
+ timestamp: chrono::NaiveDateTime,
+ players: Vec<ApiMatchPlayer>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ApiMatchPlayer {
+ bot_id: i32,
+}
+
+pub async fn list_matches(conn: DatabaseConnection) -> Result<Json<Vec<ApiMatch>>, StatusCode> {
+ matches::list_matches(&conn)
+ .map_err(|_| StatusCode::BAD_REQUEST)
+ .map(|matches| Json(matches.into_iter().map(match_data_to_api).collect()))
+}
+
+fn match_data_to_api(data: matches::MatchData) -> ApiMatch {
+ ApiMatch {
+ id: data.base.id,
+ timestamp: data.base.created_at,
+ players: data
+ .match_players
+ .iter()
+ .map(|p| ApiMatchPlayer { bot_id: p.bot_id })
+ .collect(),
+ }
+}
+
// TODO: this is duplicated from planetwars-cli
// clean this up and move to matchrunner crate
#[derive(Serialize, Deserialize)]
diff --git a/planetwars-server/src/schema.rs b/planetwars-server/src/schema.rs
index bf58434..413c4d1 100644
--- a/planetwars-server/src/schema.rs
+++ b/planetwars-server/src/schema.rs
@@ -16,6 +16,22 @@ table! {
}
table! {
+ match_players (match_id, player_id) {
+ match_id -> Int4,
+ bot_id -> Int4,
+ player_id -> Int4,
+ }
+}
+
+table! {
+ matches (id) {
+ id -> Int4,
+ log_path -> Text,
+ created_at -> Timestamp,
+ }
+}
+
+table! {
sessions (id) {
id -> Int4,
user_id -> Int4,
@@ -34,6 +50,8 @@ table! {
joinable!(bots -> users (owner_id));
joinable!(code_bundles -> bots (bot_id));
+joinable!(match_players -> bots (bot_id));
+joinable!(match_players -> matches (match_id));
joinable!(sessions -> users (user_id));
-allow_tables_to_appear_in_same_query!(bots, code_bundles, sessions, users,);
+allow_tables_to_appear_in_same_query!(bots, code_bundles, match_players, matches, sessions, users,);