2021-12-14 20:23:07 +01:00
|
|
|
#[macro_use]
|
|
|
|
extern crate diesel;
|
|
|
|
|
2021-12-19 00:16:46 +01:00
|
|
|
pub mod db;
|
2022-01-03 23:33:00 +01:00
|
|
|
pub mod db_types;
|
2022-02-27 20:35:22 +01:00
|
|
|
pub mod modules;
|
2021-12-19 00:16:46 +01:00
|
|
|
pub mod routes;
|
|
|
|
pub mod schema;
|
2022-02-27 20:35:22 +01:00
|
|
|
pub mod util;
|
2021-12-14 20:23:07 +01:00
|
|
|
|
2022-10-12 22:52:15 +02:00
|
|
|
use std::ops::{Deref, DerefMut};
|
2022-07-17 18:23:24 +02:00
|
|
|
use std::path::PathBuf;
|
2022-07-14 20:53:08 +02:00
|
|
|
use std::sync::Arc;
|
2022-07-17 18:23:24 +02:00
|
|
|
use std::{fs, net::SocketAddr};
|
2021-12-29 16:11:27 +01:00
|
|
|
|
2022-02-27 20:35:22 +01:00
|
|
|
use bb8::{Pool, PooledConnection};
|
2021-12-29 16:11:27 +01:00
|
|
|
use bb8_diesel::{self, DieselConnectionManager};
|
2022-04-27 20:43:12 +02:00
|
|
|
use config::ConfigError;
|
2022-02-27 20:35:22 +01:00
|
|
|
use diesel::{Connection, PgConnection};
|
2022-07-25 22:16:50 +02:00
|
|
|
use modules::client_api::run_client_api;
|
2022-07-14 21:50:42 +02:00
|
|
|
use modules::ranking::run_ranker;
|
2022-06-12 21:03:41 +02:00
|
|
|
use modules::registry::registry_service;
|
2022-07-16 21:57:12 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2021-12-14 20:23:07 +01:00
|
|
|
|
2021-12-29 16:11:27 +01:00
|
|
|
use axum::{
|
|
|
|
async_trait,
|
|
|
|
extract::{Extension, FromRequest, RequestParts},
|
|
|
|
http::StatusCode,
|
|
|
|
routing::{get, post},
|
2022-06-20 22:01:26 +02:00
|
|
|
Router,
|
2021-12-29 16:11:27 +01:00
|
|
|
};
|
2022-10-10 13:23:18 +02:00
|
|
|
use tower_http::compression::CompressionLayer;
|
2021-12-29 16:11:27 +01:00
|
|
|
|
|
|
|
type ConnectionPool = bb8::Pool<DieselConnectionManager<PgConnection>>;
|
|
|
|
|
2022-07-17 15:10:17 +02:00
|
|
|
// this should probably be modularized a bit as the config grows
|
2022-07-16 21:57:12 +02:00
|
|
|
#[derive(Serialize, Deserialize)]
|
2022-07-14 21:50:42 +02:00
|
|
|
pub struct GlobalConfig {
|
2022-07-16 21:57:12 +02:00
|
|
|
/// url for the postgres database
|
|
|
|
pub database_url: String,
|
|
|
|
|
|
|
|
/// which image to use for running python bots
|
2022-07-14 21:50:42 +02:00
|
|
|
pub python_runner_image: String,
|
2022-07-16 21:57:12 +02:00
|
|
|
|
|
|
|
/// url for the internal container registry
|
|
|
|
/// this will be used when running bots
|
2022-07-14 21:50:42 +02:00
|
|
|
pub container_registry_url: String,
|
2022-07-16 21:22:03 +02:00
|
|
|
|
2022-07-21 21:42:47 +02:00
|
|
|
/// webserver root url, used to construct links
|
|
|
|
pub root_url: String,
|
|
|
|
|
2022-07-16 21:57:12 +02:00
|
|
|
/// directory where bot code will be stored
|
2022-07-16 21:22:03 +02:00
|
|
|
pub bots_directory: String,
|
2022-07-16 21:57:12 +02:00
|
|
|
/// directory where match logs will be stored
|
2022-07-16 21:22:03 +02:00
|
|
|
pub match_logs_directory: String,
|
2022-07-16 21:57:12 +02:00
|
|
|
/// directory where map files will be stored
|
2022-07-16 21:22:03 +02:00
|
|
|
pub maps_directory: String,
|
2022-07-16 21:47:22 +02:00
|
|
|
|
2022-07-16 21:57:12 +02:00
|
|
|
/// base directory for registry data
|
2022-07-16 21:47:22 +02:00
|
|
|
pub registry_directory: String,
|
2022-07-16 21:57:12 +02:00
|
|
|
/// secret admin password for internal docker login
|
|
|
|
/// used to pull bots when running matches
|
2022-07-16 21:47:22 +02:00
|
|
|
pub registry_admin_password: String,
|
2022-07-17 18:23:24 +02:00
|
|
|
|
|
|
|
/// Whether to run the ranker
|
|
|
|
pub ranker_enabled: bool,
|
2022-07-14 21:50:42 +02:00
|
|
|
}
|
|
|
|
|
2022-07-17 15:10:17 +02:00
|
|
|
// TODO: do we still need this? Is there a better way?
|
|
|
|
const SIMPLEBOT_PATH: &str = "../simplebot/simplebot.py";
|
|
|
|
|
2022-07-16 21:22:03 +02:00
|
|
|
pub async fn seed_simplebot(config: &GlobalConfig, pool: &ConnectionPool) {
|
2022-10-12 22:52:15 +02:00
|
|
|
let mut conn = pool.get().await.expect("could not get database connection");
|
2022-02-27 21:47:17 +01:00
|
|
|
// This transaction is expected to fail when simplebot already exists.
|
2022-10-12 22:52:15 +02:00
|
|
|
let _res = conn.transaction::<(), diesel::result::Error, _>(|conn| {
|
2022-02-27 21:47:17 +01:00
|
|
|
use db::bots::NewBot;
|
|
|
|
|
|
|
|
let new_bot = NewBot {
|
|
|
|
name: "simplebot",
|
|
|
|
owner_id: None,
|
|
|
|
};
|
|
|
|
|
2022-10-12 22:52:15 +02:00
|
|
|
let simplebot = db::bots::create_bot(&new_bot, conn)?;
|
2022-02-27 21:47:17 +01:00
|
|
|
|
|
|
|
let simplebot_code =
|
|
|
|
std::fs::read_to_string(SIMPLEBOT_PATH).expect("could not read simplebot code");
|
|
|
|
|
2022-10-12 22:52:15 +02:00
|
|
|
modules::bots::save_code_string(&simplebot_code, Some(simplebot.id), conn, config)?;
|
2022-02-27 21:47:17 +01:00
|
|
|
|
|
|
|
println!("initialized simplebot");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-04-27 20:43:12 +02:00
|
|
|
pub type DbPool = Pool<DieselConnectionManager<PgConnection>>;
|
|
|
|
|
2022-08-09 23:27:22 +02:00
|
|
|
pub async fn create_db_pool(config: &GlobalConfig) -> DbPool {
|
2022-07-16 21:57:12 +02:00
|
|
|
let manager = DieselConnectionManager::<PgConnection>::new(&config.database_url);
|
2022-08-09 23:27:22 +02:00
|
|
|
bb8::Pool::builder().build(manager).await.unwrap()
|
2022-02-27 20:35:22 +01:00
|
|
|
}
|
|
|
|
|
2022-07-17 17:07:53 +02:00
|
|
|
// create all directories required for further operation
|
|
|
|
fn init_directories(config: &GlobalConfig) -> std::io::Result<()> {
|
|
|
|
fs::create_dir_all(&config.bots_directory)?;
|
|
|
|
fs::create_dir_all(&config.maps_directory)?;
|
|
|
|
fs::create_dir_all(&config.match_logs_directory)?;
|
|
|
|
|
|
|
|
let registry_path = PathBuf::from(&config.registry_directory);
|
|
|
|
fs::create_dir_all(registry_path.join("sha256"))?;
|
|
|
|
fs::create_dir_all(registry_path.join("manifests"))?;
|
|
|
|
fs::create_dir_all(registry_path.join("uploads"))?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-30 17:28:24 +02:00
|
|
|
// just the routes, without connecting state
|
|
|
|
fn api() -> Router {
|
2022-03-13 15:20:03 +01:00
|
|
|
Router::new()
|
2021-12-30 16:38:02 +01:00
|
|
|
.route("/register", post(routes::users::register))
|
|
|
|
.route("/login", post(routes::users::login))
|
2021-12-29 16:11:27 +01:00
|
|
|
.route("/users/me", get(routes::users::current_user))
|
2022-07-24 15:15:09 +02:00
|
|
|
.route("/users/:user/bots", get(routes::bots::get_user_bots))
|
2022-01-01 11:26:49 +01:00
|
|
|
.route(
|
|
|
|
"/bots",
|
|
|
|
get(routes::bots::list_bots).post(routes::bots::create_bot),
|
|
|
|
)
|
2022-07-24 16:45:29 +02:00
|
|
|
.route("/bots/:bot_name", get(routes::bots::get_bot))
|
2022-10-11 22:58:42 +02:00
|
|
|
.route("/bots/:bot_name/stats", get(routes::bots::get_bot_stats))
|
2021-12-30 23:41:47 +01:00
|
|
|
.route(
|
2022-07-24 16:45:29 +02:00
|
|
|
"/bots/:bot_name/upload",
|
2021-12-30 23:41:47 +01:00
|
|
|
post(routes::bots::upload_code_multipart),
|
|
|
|
)
|
2022-09-10 18:56:03 +02:00
|
|
|
.route("/code/:version_id", get(routes::bots::get_code))
|
2022-08-02 20:12:34 +02:00
|
|
|
.route("/matches", get(routes::matches::list_recent_matches))
|
2022-02-12 23:52:45 +01:00
|
|
|
.route("/matches/:match_id", get(routes::matches::get_match_data))
|
|
|
|
.route(
|
|
|
|
"/matches/:match_id/log",
|
|
|
|
get(routes::matches::get_match_log),
|
|
|
|
)
|
2022-08-27 17:05:11 +02:00
|
|
|
.route("/maps", get(routes::maps::list_maps))
|
2022-05-17 21:13:29 +02:00
|
|
|
.route("/leaderboard", get(routes::bots::get_ranking))
|
2022-01-27 19:22:48 +01:00
|
|
|
.route("/submit_bot", post(routes::demo::submit_bot))
|
2022-02-28 22:44:06 +01:00
|
|
|
.route("/save_bot", post(routes::bots::save_bot))
|
2021-12-30 16:38:02 +01:00
|
|
|
}
|
|
|
|
|
2022-09-30 17:28:24 +02:00
|
|
|
// full service
|
|
|
|
pub fn create_pw_api(global_config: Arc<GlobalConfig>, db_pool: DbPool) -> Router {
|
|
|
|
Router::new()
|
|
|
|
.nest("/api", api())
|
|
|
|
.layer(Extension(db_pool))
|
|
|
|
.layer(Extension(global_config))
|
2022-10-10 13:23:18 +02:00
|
|
|
.layer(CompressionLayer::new())
|
2022-09-30 17:28:24 +02:00
|
|
|
}
|
|
|
|
|
2022-07-16 21:57:12 +02:00
|
|
|
pub fn get_config() -> Result<GlobalConfig, ConfigError> {
|
2022-04-27 20:43:12 +02:00
|
|
|
config::Config::builder()
|
2022-02-20 23:06:05 +01:00
|
|
|
.add_source(config::File::with_name("configuration.toml"))
|
|
|
|
.add_source(config::Environment::with_prefix("PLANETWARS"))
|
2022-04-27 20:43:12 +02:00
|
|
|
.build()?
|
2022-02-20 23:06:05 +01:00
|
|
|
.try_deserialize()
|
2022-04-27 20:43:12 +02:00
|
|
|
}
|
|
|
|
|
2022-07-16 21:47:22 +02:00
|
|
|
async fn run_registry(config: Arc<GlobalConfig>, db_pool: DbPool) {
|
2022-06-12 21:03:41 +02:00
|
|
|
// TODO: put in config
|
|
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 9001));
|
|
|
|
|
|
|
|
axum::Server::bind(&addr)
|
2022-06-20 22:01:26 +02:00
|
|
|
.serve(
|
|
|
|
registry_service()
|
|
|
|
.layer(Extension(db_pool))
|
2022-07-16 21:47:22 +02:00
|
|
|
.layer(Extension(config))
|
2022-06-20 22:01:26 +02:00
|
|
|
.into_make_service(),
|
|
|
|
)
|
2022-06-12 21:03:41 +02:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2022-04-27 20:43:12 +02:00
|
|
|
pub async fn run_app() {
|
2022-07-16 21:57:12 +02:00
|
|
|
let global_config = Arc::new(get_config().unwrap());
|
2022-08-09 23:27:22 +02:00
|
|
|
let db_pool = create_db_pool(&global_config).await;
|
|
|
|
seed_simplebot(&global_config, &db_pool).await;
|
2022-07-17 17:07:53 +02:00
|
|
|
init_directories(&global_config).unwrap();
|
2022-07-16 21:22:03 +02:00
|
|
|
|
2022-07-17 18:23:24 +02:00
|
|
|
if global_config.ranker_enabled {
|
|
|
|
tokio::spawn(run_ranker(global_config.clone(), db_pool.clone()));
|
|
|
|
}
|
2022-07-16 21:47:22 +02:00
|
|
|
tokio::spawn(run_registry(global_config.clone(), db_pool.clone()));
|
2022-07-25 22:16:50 +02:00
|
|
|
tokio::spawn(run_client_api(global_config.clone(), db_pool.clone()));
|
2022-04-27 20:43:12 +02:00
|
|
|
|
|
|
|
// TODO: put in config
|
|
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 9000));
|
|
|
|
|
2022-09-30 17:28:24 +02:00
|
|
|
let pw_api_service = create_pw_api(global_config, db_pool).into_make_service();
|
|
|
|
axum::Server::bind(&addr)
|
|
|
|
.serve(pw_api_service)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2021-12-29 16:11:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// we can also write a custom extractor that grabs a connection from the pool
|
|
|
|
// which setup is appropriate depends on your application
|
|
|
|
pub struct DatabaseConnection(PooledConnection<'static, DieselConnectionManager<PgConnection>>);
|
|
|
|
|
|
|
|
impl Deref for DatabaseConnection {
|
|
|
|
type Target = PooledConnection<'static, DieselConnectionManager<PgConnection>>;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-12 22:52:15 +02:00
|
|
|
impl DerefMut for DatabaseConnection {
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-29 16:11:27 +01:00
|
|
|
#[async_trait]
|
|
|
|
impl<B> FromRequest<B> for DatabaseConnection
|
|
|
|
where
|
|
|
|
B: Send,
|
|
|
|
{
|
|
|
|
type Rejection = (StatusCode, String);
|
|
|
|
|
|
|
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
|
|
|
let Extension(pool) = Extension::<ConnectionPool>::from_request(req)
|
|
|
|
.await
|
|
|
|
.map_err(internal_error)?;
|
|
|
|
|
|
|
|
let conn = pool.get_owned().await.map_err(internal_error)?;
|
|
|
|
|
|
|
|
Ok(Self(conn))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Utility function for mapping any error into a `500 Internal Server Error`
|
|
|
|
/// response.
|
|
|
|
fn internal_error<E>(err: E) -> (StatusCode, String)
|
|
|
|
where
|
|
|
|
E: std::error::Error,
|
|
|
|
{
|
|
|
|
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
|
2021-12-14 20:23:07 +01:00
|
|
|
}
|