save all uploaded code bundles in database

This commit is contained in:
Ilion Beyst 2022-02-27 20:35:22 +01:00
parent 6ef6a872fe
commit 22a8f3d619
9 changed files with 60 additions and 26 deletions

View file

@ -8,7 +8,7 @@ CREATE UNIQUE INDEX bots_index ON bots(owner_id, name);
CREATE TABLE code_bundles ( CREATE TABLE code_bundles (
id serial PRIMARY KEY, id serial PRIMARY KEY,
bot_id integer REFERENCES bots(id) NOT NULL, bot_id integer REFERENCES bots(id),
path text NOT NULL, path text NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
); );

View file

@ -42,14 +42,14 @@ pub fn find_all_bots(conn: &PgConnection) -> QueryResult<Vec<Bot>> {
#[derive(Insertable)] #[derive(Insertable)]
#[table_name = "code_bundles"] #[table_name = "code_bundles"]
pub struct NewCodeBundle<'a> { pub struct NewCodeBundle<'a> {
pub bot_id: i32, pub bot_id: Option<i32>,
pub path: &'a str, pub path: &'a str,
} }
#[derive(Queryable, Serialize, Deserialize, Debug)] #[derive(Queryable, Serialize, Deserialize, Debug)]
pub struct CodeBundle { pub struct CodeBundle {
pub id: i32, pub id: i32,
pub bot_id: i32, pub bot_id: Option<i32>,
pub path: String, pub path: String,
pub created_at: chrono::NaiveDateTime, pub created_at: chrono::NaiveDateTime,
} }

View file

@ -3,15 +3,17 @@ extern crate diesel;
pub mod db; pub mod db;
pub mod db_types; pub mod db_types;
pub mod modules;
pub mod routes; pub mod routes;
pub mod schema; pub mod schema;
pub mod util;
use std::ops::Deref; use std::ops::Deref;
use axum; use axum;
use bb8::PooledConnection; use bb8::{Pool, PooledConnection};
use bb8_diesel::{self, DieselConnectionManager}; use bb8_diesel::{self, DieselConnectionManager};
use diesel::PgConnection; use diesel::{Connection, PgConnection};
use serde::Deserialize; use serde::Deserialize;
use axum::{ use axum::{
@ -29,9 +31,14 @@ const MAPS_DIR: &str = "./data/maps";
type ConnectionPool = bb8::Pool<DieselConnectionManager<PgConnection>>; type ConnectionPool = bb8::Pool<DieselConnectionManager<PgConnection>>;
pub async fn api(configuration: Configuration) -> Router { pub async fn prepare_db(database_url: &str) -> Pool<DieselConnectionManager<PgConnection>> {
let manager = DieselConnectionManager::<PgConnection>::new(configuration.database_url); let manager = DieselConnectionManager::<PgConnection>::new(database_url);
let pool = bb8::Pool::builder().build(manager).await.unwrap(); let pool = bb8::Pool::builder().build(manager).await.unwrap();
return pool;
}
pub async fn api(configuration: Configuration) -> Router {
let db_pool = prepare_db(&configuration.database_url).await;
let api = Router::new() let api = Router::new()
.route("/register", post(routes::users::register)) .route("/register", post(routes::users::register))
@ -57,7 +64,7 @@ pub async fn api(configuration: Configuration) -> Router {
get(routes::matches::get_match_log), get(routes::matches::get_match_log),
) )
.route("/submit_bot", post(routes::demo::submit_bot)) .route("/submit_bot", post(routes::demo::submit_bot))
.layer(AddExtensionLayer::new(pool)); .layer(AddExtensionLayer::new(db_pool));
api api
} }

View file

@ -0,0 +1,23 @@
use std::path::PathBuf;
use diesel::{PgConnection, QueryResult};
use crate::{db, util::gen_alphanumeric, BOTS_DIR};
pub fn save_code_bundle(
bot_code: &str,
bot_id: Option<i32>,
conn: &PgConnection,
) -> QueryResult<db::bots::CodeBundle> {
let bundle_name = gen_alphanumeric(16);
let code_bundle_dir = PathBuf::from(BOTS_DIR).join(&bundle_name);
std::fs::create_dir(&code_bundle_dir).unwrap();
std::fs::write(code_bundle_dir.join("bot.py"), bot_code).unwrap();
let new_code_bundle = db::bots::NewCodeBundle {
bot_id,
path: &bundle_name,
};
db::bots::create_code_bundle(&new_code_bundle, conn)
}

View file

@ -0,0 +1,3 @@
// This module implements general domain logic, not directly
// tied to the database or API layers.
pub mod bots;

View file

@ -97,7 +97,7 @@ pub async fn upload_code_multipart(
.map_err(|_| StatusCode::BAD_REQUEST)?; .map_err(|_| StatusCode::BAD_REQUEST)?;
let bundle = bots::NewCodeBundle { let bundle = bots::NewCodeBundle {
bot_id: bot.id, bot_id: Some(bot.id),
path: &folder_name, path: &folder_name,
}; };
let code_bundle = let code_bundle =

View file

@ -1,10 +1,11 @@
use crate::db::matches::{self, MatchState}; use crate::db::matches::{self, MatchState};
use crate::modules::bots::save_code_bundle;
use crate::util::gen_alphanumeric;
use crate::{ConnectionPool, BOTS_DIR, MAPS_DIR, MATCHES_DIR}; use crate::{ConnectionPool, BOTS_DIR, MAPS_DIR, MATCHES_DIR};
use axum::extract::Extension; use axum::extract::Extension;
use axum::Json; use axum::Json;
use hyper::StatusCode; use hyper::StatusCode;
use planetwars_matchrunner::{docker_runner::DockerBotSpec, run_match, MatchConfig, MatchPlayer}; use planetwars_matchrunner::{docker_runner::DockerBotSpec, run_match, MatchConfig, MatchPlayer};
use rand::{distributions::Alphanumeric, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
@ -15,6 +16,7 @@ const SIMPLEBOT_PATH: &'static str = "../simplebot";
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SubmitBotParams { pub struct SubmitBotParams {
pub bot_name: Option<String>,
pub code: String, pub code: String,
} }
@ -32,14 +34,12 @@ pub async fn submit_bot(
) -> Result<Json<SubmitBotResponse>, StatusCode> { ) -> Result<Json<SubmitBotResponse>, StatusCode> {
let conn = pool.get().await.expect("could not get database connection"); let conn = pool.get().await.expect("could not get database connection");
let uploaded_bot_uuid: String = gen_alphanumeric(16); let code_bundle = save_code_bundle(&params.code, None, &conn)
// TODO: can we recover from this?
.expect("could not save bot code");
let log_file_name = format!("{}.log", gen_alphanumeric(16)); let log_file_name = format!("{}.log", gen_alphanumeric(16));
let uploaded_bot_dir = PathBuf::from(BOTS_DIR).join(&code_bundle.path);
// store uploaded bot
let uploaded_bot_dir = PathBuf::from(BOTS_DIR).join(&uploaded_bot_uuid);
std::fs::create_dir(&uploaded_bot_dir).unwrap();
std::fs::write(uploaded_bot_dir.join("bot.py"), params.code.as_bytes()).unwrap();
// play the match // play the match
let match_config = MatchConfig { let match_config = MatchConfig {
map_path: PathBuf::from(MAPS_DIR).join("hex.json"), map_path: PathBuf::from(MAPS_DIR).join("hex.json"),
@ -95,11 +95,3 @@ async fn run_match_task(match_id: i32, match_config: MatchConfig, connection_poo
matches::set_match_state(match_id, MatchState::Finished, &conn) matches::set_match_state(match_id, MatchState::Finished, &conn)
.expect("failed to update match state"); .expect("failed to update match state");
} }
pub fn gen_alphanumeric(length: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}

View file

@ -18,7 +18,7 @@ table! {
code_bundles (id) { code_bundles (id) {
id -> Int4, id -> Int4,
bot_id -> Int4, bot_id -> Nullable<Int4>,
path -> Text, path -> Text,
created_at -> Timestamp, created_at -> Timestamp,
} }

View file

@ -0,0 +1,9 @@
use rand::{distributions::Alphanumeric, Rng};
pub fn gen_alphanumeric(length: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}