aboutsummaryrefslogtreecommitdiff
path: root/planetwars-server/src/lib.rs
blob: b74525bcfccca5c712396d2b3744b6a5625010d0 (plain)
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
#[macro_use]
extern crate diesel;

pub mod db;
pub mod db_types;
pub mod routes;
pub mod schema;

use std::ops::Deref;

use axum;
use bb8::PooledConnection;
use bb8_diesel::{self, DieselConnectionManager};
use diesel::PgConnection;
use serde::Deserialize;

use axum::{
    async_trait,
    extract::{Extension, FromRequest, RequestParts},
    http::StatusCode,
    routing::{get, post},
    AddExtensionLayer, Router,
};

// TODO: make these configurable
const BOTS_DIR: &str = "./data/bots";
const MATCHES_DIR: &str = "./data/matches";
const MAPS_DIR: &str = "./data/maps";

type ConnectionPool = bb8::Pool<DieselConnectionManager<PgConnection>>;

pub async fn api(configuration: Configuration) -> Router {
    let manager = DieselConnectionManager::<PgConnection>::new(configuration.database_url);
    let pool = bb8::Pool::builder().build(manager).await.unwrap();

    let api = Router::new()
        .route("/register", post(routes::users::register))
        .route("/login", post(routes::users::login))
        .route("/users/me", get(routes::users::current_user))
        .route(
            "/bots",
            get(routes::bots::list_bots).post(routes::bots::create_bot),
        )
        .route("/bots/my_bots", get(routes::bots::get_my_bots))
        .route("/bots/:bot_id", get(routes::bots::get_bot))
        .route(
            "/bots/:bot_id/upload",
            post(routes::bots::upload_code_multipart),
        )
        .route(
            "/matches",
            get(routes::matches::list_matches).post(routes::matches::play_match),
        )
        .route("/matches/:match_id", get(routes::matches::get_match_data))
        .route(
            "/matches/:match_id/log",
            get(routes::matches::get_match_log),
        )
        .route("/submit_bot", post(routes::demo::submit_bot))
        .layer(AddExtensionLayer::new(pool));
    api
}

pub async fn app() -> Router {
    let configuration = config::Config::builder()
        .add_source(config::File::with_name("configuration.toml"))
        .add_source(config::Environment::with_prefix("PLANETWARS"))
        .build()
        .unwrap()
        .try_deserialize()
        .unwrap();
    let api = api(configuration).await;
    Router::new().nest("/api", api)
}

#[derive(Deserialize)]
pub struct Configuration {
    pub database_url: String,
}

// 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())
}