planetwars.dev/planetwars-server/src/lib.rs

227 lines
6.9 KiB
Rust
Raw Normal View History

2021-12-14 19:23:07 +00:00
#[macro_use]
extern crate diesel;
2021-12-18 23:16:46 +00:00
pub mod db;
2022-01-03 22:33:00 +00:00
pub mod db_types;
pub mod modules;
2021-12-18 23:16:46 +00:00
pub mod routes;
pub mod schema;
pub mod util;
2021-12-14 19:23:07 +00:00
2021-12-29 15:11:27 +00:00
use std::ops::Deref;
2022-07-17 16:23:24 +00:00
use std::path::PathBuf;
use std::sync::Arc;
2022-07-17 16:23:24 +00:00
use std::{fs, net::SocketAddr};
2021-12-29 15:11:27 +00:00
use bb8::{Pool, PooledConnection};
2021-12-29 15:11:27 +00:00
use bb8_diesel::{self, DieselConnectionManager};
use config::ConfigError;
use diesel::{Connection, PgConnection};
2022-07-21 18:29:01 +00:00
use modules::bot_api::run_bot_api;
2022-07-14 19:50:42 +00:00
use modules::ranking::run_ranker;
2022-06-12 19:03:41 +00:00
use modules::registry::registry_service;
use serde::{Deserialize, Serialize};
2021-12-14 19:23:07 +00:00
2021-12-29 15:11:27 +00:00
use axum::{
async_trait,
extract::{Extension, FromRequest, RequestParts},
http::StatusCode,
routing::{get, post},
2022-06-20 20:01:26 +00:00
Router,
2021-12-29 15:11:27 +00:00
};
type ConnectionPool = bb8::Pool<DieselConnectionManager<PgConnection>>;
2022-07-17 13:10:17 +00:00
// this should probably be modularized a bit as the config grows
#[derive(Serialize, Deserialize)]
2022-07-14 19:50:42 +00:00
pub struct GlobalConfig {
/// url for the postgres database
pub database_url: String,
/// which image to use for running python bots
2022-07-14 19:50:42 +00:00
pub python_runner_image: String,
/// url for the internal container registry
/// this will be used when running bots
2022-07-14 19:50:42 +00:00
pub container_registry_url: String,
2022-07-16 19:22:03 +00:00
2022-07-21 19:42:47 +00:00
/// webserver root url, used to construct links
pub root_url: String,
/// directory where bot code will be stored
2022-07-16 19:22:03 +00:00
pub bots_directory: String,
/// directory where match logs will be stored
2022-07-16 19:22:03 +00:00
pub match_logs_directory: String,
/// directory where map files will be stored
2022-07-16 19:22:03 +00:00
pub maps_directory: String,
2022-07-16 19:47:22 +00:00
/// base directory for registry data
2022-07-16 19:47:22 +00:00
pub registry_directory: String,
/// secret admin password for internal docker login
/// used to pull bots when running matches
2022-07-16 19:47:22 +00:00
pub registry_admin_password: String,
2022-07-17 16:23:24 +00:00
/// Whether to run the ranker
pub ranker_enabled: bool,
2022-07-14 19:50:42 +00:00
}
2022-07-17 13:10:17 +00:00
// TODO: do we still need this? Is there a better way?
const SIMPLEBOT_PATH: &str = "../simplebot/simplebot.py";
2022-07-16 19:22:03 +00:00
pub async fn seed_simplebot(config: &GlobalConfig, pool: &ConnectionPool) {
2022-02-27 20:47:17 +00:00
let conn = pool.get().await.expect("could not get database connection");
// This transaction is expected to fail when simplebot already exists.
let _res = conn.transaction::<(), diesel::result::Error, _>(|| {
use db::bots::NewBot;
let new_bot = NewBot {
name: "simplebot",
owner_id: None,
};
let simplebot = db::bots::create_bot(&new_bot, &conn)?;
let simplebot_code =
std::fs::read_to_string(SIMPLEBOT_PATH).expect("could not read simplebot code");
2022-07-16 19:22:03 +00:00
modules::bots::save_code_string(&simplebot_code, Some(simplebot.id), &conn, config)?;
2022-02-27 20:47:17 +00:00
println!("initialized simplebot");
Ok(())
});
}
pub type DbPool = Pool<DieselConnectionManager<PgConnection>>;
pub async fn prepare_db(config: &GlobalConfig) -> DbPool {
let manager = DieselConnectionManager::<PgConnection>::new(&config.database_url);
2021-12-29 15:11:27 +00:00
let pool = bb8::Pool::builder().build(manager).await.unwrap();
2022-07-17 13:10:17 +00:00
seed_simplebot(config, &pool).await;
2022-03-13 14:20:03 +00:00
pool
}
// 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(())
}
pub fn api() -> Router {
2022-03-13 14:20:03 +00:00
Router::new()
2021-12-30 15:38:02 +00:00
.route("/register", post(routes::users::register))
.route("/login", post(routes::users::login))
2021-12-29 15:11:27 +00:00
.route("/users/me", get(routes::users::current_user))
2022-07-24 13:15:09 +00:00
.route("/users/:user/bots", get(routes::bots::get_user_bots))
2022-01-01 10:26:49 +00:00
.route(
"/bots",
get(routes::bots::list_bots).post(routes::bots::create_bot),
)
2021-12-29 15:11:27 +00:00
.route("/bots/:bot_id", get(routes::bots::get_bot))
2021-12-30 22:41:47 +00:00
.route(
"/bots/:bot_id/upload",
post(routes::bots::upload_code_multipart),
)
2022-07-05 18:34:20 +00:00
.route("/matches", get(routes::matches::list_matches))
.route("/matches/:match_id", get(routes::matches::get_match_data))
.route(
"/matches/:match_id/log",
get(routes::matches::get_match_log),
)
2022-05-17 19:13:29 +00:00
.route("/leaderboard", get(routes::bots::get_ranking))
.route("/submit_bot", post(routes::demo::submit_bot))
2022-02-28 21:44:06 +00:00
.route("/save_bot", post(routes::bots::save_bot))
2021-12-30 15:38:02 +00:00
}
pub fn get_config() -> Result<GlobalConfig, ConfigError> {
config::Config::builder()
2022-02-20 22:06:05 +00:00
.add_source(config::File::with_name("configuration.toml"))
.add_source(config::Environment::with_prefix("PLANETWARS"))
.build()?
2022-02-20 22:06:05 +00:00
.try_deserialize()
}
2022-07-16 19:47:22 +00:00
async fn run_registry(config: Arc<GlobalConfig>, db_pool: DbPool) {
2022-06-12 19:03:41 +00:00
// TODO: put in config
let addr = SocketAddr::from(([127, 0, 0, 1], 9001));
axum::Server::bind(&addr)
2022-06-20 20:01:26 +00:00
.serve(
registry_service()
.layer(Extension(db_pool))
2022-07-16 19:47:22 +00:00
.layer(Extension(config))
2022-06-20 20:01:26 +00:00
.into_make_service(),
)
2022-06-12 19:03:41 +00:00
.await
.unwrap();
}
pub async fn run_app() {
let global_config = Arc::new(get_config().unwrap());
let db_pool = prepare_db(&global_config).await;
init_directories(&global_config).unwrap();
2022-07-16 19:22:03 +00:00
2022-07-17 16:23:24 +00:00
if global_config.ranker_enabled {
tokio::spawn(run_ranker(global_config.clone(), db_pool.clone()));
}
2022-07-16 19:47:22 +00:00
tokio::spawn(run_registry(global_config.clone(), db_pool.clone()));
2022-07-21 18:29:01 +00:00
tokio::spawn(run_bot_api(global_config.clone(), db_pool.clone()));
let api_service = Router::new()
.nest("/api", api())
2022-06-20 20:01:26 +00:00
.layer(Extension(db_pool))
2022-07-16 19:22:03 +00:00
.layer(Extension(global_config))
.into_make_service();
// TODO: put in config
let addr = SocketAddr::from(([127, 0, 0, 1], 9000));
axum::Server::bind(&addr).serve(api_service).await.unwrap();
2021-12-29 15:11:27 +00: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
}
}
#[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 19:23:07 +00:00
}