atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
c4d70621d377d45e3d418ac9197443c1b5d36399
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/http/mod.rs

782069860236atagen2026-10-10 12:44 UTC1
mod ctx;
782069860236atagen2026-10-10 12:44 UTC2
mod meta;
782069860236atagen2026-10-10 12:44 UTC3
mod middleware;
159f0998263batagen2026-10-12 11:01 UTC4
mod registry;
782069860236atagen2026-10-10 12:44 UTC5
mod resource;
782069860236atagen2026-10-10 12:44 UTC6
mod response;
782069860236atagen2026-10-10 12:44 UTC7
mod run;
782069860236atagen2026-10-10 12:44 UTC8
mod runs;
782069860236atagen2026-10-10 12:44 UTC9
mod ui;
782069860236atagen2026-10-10 12:44 UTC10
159f0998263batagen2026-10-12 11:01 UTC11
use std::sync::Arc;
782069860236atagen2026-10-10 12:44 UTC12
782069860236atagen2026-10-10 12:44 UTC13
use axum::{
782069860236atagen2026-10-10 12:44 UTC14
    Router,
782069860236atagen2026-10-10 12:44 UTC15
    extract::State,
159f0998263batagen2026-10-12 11:01 UTC16
    response::{Redirect, Response},
782069860236atagen2026-10-10 12:44 UTC17
    routing::{MethodRouter, any},
782069860236atagen2026-10-10 12:44 UTC18
};
782069860236atagen2026-10-10 12:44 UTC19
7288fc269bb6atagen2026-10-10 13:36 UTC20
use tower_http::catch_panic::CatchPanicLayer;
7288fc269bb6atagen2026-10-10 13:36 UTC21
159f0998263batagen2026-10-12 11:01 UTC22
use crate::{config::Config, records::Records};
782069860236atagen2026-10-10 12:44 UTC23
use ctx::Ctx;
159f0998263batagen2026-10-12 11:01 UTC24
use registry::Registry;
782069860236atagen2026-10-10 12:44 UTC25
use resource::Resource;
782069860236atagen2026-10-10 12:44 UTC26
use run::RunResource;
782069860236atagen2026-10-10 12:44 UTC27
782069860236atagen2026-10-10 12:44 UTC28
pub struct AppState {
159f0998263batagen2026-10-12 11:01 UTC29
    runs: Registry,
159f0998263batagen2026-10-12 11:01 UTC30
    records: Records,
782069860236atagen2026-10-10 12:44 UTC31
    config: Config,
782069860236atagen2026-10-10 12:44 UTC32
}
782069860236atagen2026-10-10 12:44 UTC33
782069860236atagen2026-10-10 12:44 UTC34
impl AppState {
782069860236atagen2026-10-10 12:44 UTC35
    pub fn new(config: Config) -> Self {
159f0998263batagen2026-10-12 11:01 UTC36
        let records = match &config.db {
159f0998263batagen2026-10-12 11:01 UTC37
            Some(path) => Records::open(path).unwrap_or_else(|e| {
159f0998263batagen2026-10-12 11:01 UTC38
                panic!("the records at {} could not be opened: {e}", path.display())
159f0998263batagen2026-10-12 11:01 UTC39
            }),
159f0998263batagen2026-10-12 11:01 UTC40
            None => Records::in_memory(),
159f0998263batagen2026-10-12 11:01 UTC41
        };
782069860236atagen2026-10-10 12:44 UTC42
        Self {
159f0998263batagen2026-10-12 11:01 UTC43
            runs: Registry::default(),
159f0998263batagen2026-10-12 11:01 UTC44
            records,
782069860236atagen2026-10-10 12:44 UTC45
            config,
782069860236atagen2026-10-10 12:44 UTC46
        }
782069860236atagen2026-10-10 12:44 UTC47
    }
782069860236atagen2026-10-10 12:44 UTC48
782069860236atagen2026-10-10 12:44 UTC49
    #[cfg(test)]
159f0998263batagen2026-10-12 11:01 UTC50
    pub(crate) fn insert(&self, run: crate::game::Run) {
159f0998263batagen2026-10-12 11:01 UTC51
        self.runs.insert(run);
782069860236atagen2026-10-10 12:44 UTC52
    }
782069860236atagen2026-10-10 12:44 UTC53
}
782069860236atagen2026-10-10 12:44 UTC54
782069860236atagen2026-10-10 12:44 UTC55
impl Default for AppState {
782069860236atagen2026-10-10 12:44 UTC56
    fn default() -> Self {
782069860236atagen2026-10-10 12:44 UTC57
        Self::new(Config::default())
782069860236atagen2026-10-10 12:44 UTC58
    }
782069860236atagen2026-10-10 12:44 UTC59
}
782069860236atagen2026-10-10 12:44 UTC60
782069860236atagen2026-10-10 12:44 UTC61
type Shared = Arc<AppState>;
782069860236atagen2026-10-10 12:44 UTC62
782069860236atagen2026-10-10 12:44 UTC63
pub fn app(config: Config) -> Router {
782069860236atagen2026-10-10 12:44 UTC64
    router(Arc::new(AppState::new(config)))
782069860236atagen2026-10-10 12:44 UTC65
}
782069860236atagen2026-10-10 12:44 UTC66
782069860236atagen2026-10-10 12:44 UTC67
pub fn router(state: Shared) -> Router {
782069860236atagen2026-10-10 12:44 UTC68
    let api = api(state);
782069860236atagen2026-10-10 12:44 UTC69
    let ui = Router::new()
782069860236atagen2026-10-10 12:44 UTC70
        .route("/ui", any(|| async { Redirect::permanent("/ui/") }))
782069860236atagen2026-10-10 12:44 UTC71
        .route("/ui/", any(ui::page))
782069860236atagen2026-10-10 12:44 UTC72
        .route("/ui/send", any(ui::send))
782069860236atagen2026-10-10 12:44 UTC73
        .with_state(api.clone());
782069860236atagen2026-10-10 12:44 UTC74
    api.merge(ui)
7288fc269bb6atagen2026-10-10 13:36 UTC75
        .layer(CatchPanicLayer::custom(middleware::panicked))
782069860236atagen2026-10-10 12:44 UTC76
}
782069860236atagen2026-10-10 12:44 UTC77
782069860236atagen2026-10-10 12:44 UTC78
fn global(resource: &'static Resource<Shared>) -> MethodRouter<Shared> {
159f0998263batagen2026-10-12 11:01 UTC79
    any(move |State(state): State<Shared>, ctx: Ctx| async move {
159f0998263batagen2026-10-12 11:01 UTC80
        offload(state, move |mut state| resource.dispatch(&mut state, &ctx)).await
159f0998263batagen2026-10-12 11:01 UTC81
    })
782069860236atagen2026-10-10 12:44 UTC82
}
782069860236atagen2026-10-10 12:44 UTC83
782069860236atagen2026-10-10 12:44 UTC84
fn scoped(resource: &'static RunResource) -> MethodRouter<Shared> {
159f0998263batagen2026-10-12 11:01 UTC85
    any(move |State(state): State<Shared>, ctx: Ctx| async move {
159f0998263batagen2026-10-12 11:01 UTC86
        offload(state, move |state| run::serve(&state, ctx, resource)).await
159f0998263batagen2026-10-12 11:01 UTC87
    })
159f0998263batagen2026-10-12 11:01 UTC88
}
159f0998263batagen2026-10-12 11:01 UTC89
159f0998263batagen2026-10-12 11:01 UTC90
/// The model oracle blocks on HTTP for every roll, so its turns run on the blocking pool and
159f0998263batagen2026-10-12 11:01 UTC91
/// leave the async workers free. Dice are quick enough to roll in place.
159f0998263batagen2026-10-12 11:01 UTC92
async fn offload<F>(state: Shared, work: F) -> Response
159f0998263batagen2026-10-12 11:01 UTC93
where
159f0998263batagen2026-10-12 11:01 UTC94
    F: FnOnce(Shared) -> Response + Send + 'static,
159f0998263batagen2026-10-12 11:01 UTC95
{
159f0998263batagen2026-10-12 11:01 UTC96
    if !state.config.oracle.blocks() {
159f0998263batagen2026-10-12 11:01 UTC97
        return work(state);
159f0998263batagen2026-10-12 11:01 UTC98
    }
159f0998263batagen2026-10-12 11:01 UTC99
    tokio::task::spawn_blocking(move || work(state))
159f0998263batagen2026-10-12 11:01 UTC100
        .await
159f0998263batagen2026-10-12 11:01 UTC101
        .unwrap_or_else(|e| std::panic::resume_unwind(e.into_panic()))
782069860236atagen2026-10-10 12:44 UTC102
}
782069860236atagen2026-10-10 12:44 UTC103
782069860236atagen2026-10-10 12:44 UTC104
fn api(state: Shared) -> Router {
782069860236atagen2026-10-10 12:44 UTC105
    Router::new()
782069860236atagen2026-10-10 12:44 UTC106
        .route("/", global(&meta::INDEX))
782069860236atagen2026-10-10 12:44 UTC107
        .route("/runs", global(&meta::RUNS))
782069860236atagen2026-10-10 12:44 UTC108
        .route("/runs/{id}", scoped(&runs::RUN))
782069860236atagen2026-10-10 12:44 UTC109
        .route("/runs/{id}/length", scoped(&runs::LENGTH))
782069860236atagen2026-10-10 12:44 UTC110
        .route("/runs/{id}/foreskin", scoped(&runs::FORESKIN))
782069860236atagen2026-10-10 12:44 UTC111
        .route("/runs/{id}/inventory", scoped(&runs::INVENTORY))
782069860236atagen2026-10-10 12:44 UTC112
        .route("/runs/{id}/inventory/{item}", scoped(&runs::ITEM))
782069860236atagen2026-10-10 12:44 UTC113
        .route("/runs/{id}/techniques", scoped(&runs::TECHNIQUES))
782069860236atagen2026-10-10 12:44 UTC114
        .route("/runs/{id}/drawer", scoped(&runs::DRAWER))
782069860236atagen2026-10-10 12:44 UTC115
        .route("/runs/{id}/branches/{choice}", scoped(&runs::BRANCH))
0a0bfee8329eatagen2026-10-11 05:11 UTC116
        .route("/runs/{id}/kitchen", scoped(&runs::KITCHEN))
0a0bfee8329eatagen2026-10-11 05:11 UTC117
        .route("/runs/{id}/kitchen/watermelon", scoped(&runs::MELON))
b85a0d4536c3atagen2026-10-11 08:50 UTC118
        .route("/runs/{id}/re-read", scoped(&runs::REREAD))
782069860236atagen2026-10-10 12:44 UTC119
        .route("/runs/{id}/floor", scoped(&runs::FLOOR))
782069860236atagen2026-10-10 12:44 UTC120
        .route("/runs/{id}/shop", scoped(&runs::SHOP))
782069860236atagen2026-10-10 12:44 UTC121
        .route("/runs/{id}/shop/{item}", scoped(&runs::BUY))
782069860236atagen2026-10-10 12:44 UTC122
        .route("/runs/{id}/portal", scoped(&runs::PORTAL))
782069860236atagen2026-10-10 12:44 UTC123
        .route("/runs/{id}/elder", scoped(&runs::ELDER))
782069860236atagen2026-10-10 12:44 UTC124
        .route("/runs/{id}/grip", scoped(&runs::GRIP))
782069860236atagen2026-10-10 12:44 UTC125
        .route("/runs/{id}/diagnosis", scoped(&runs::DIAGNOSIS))
782069860236atagen2026-10-10 12:44 UTC126
        .route("/runs/{id}/footage", scoped(&runs::FOOTAGE))
782069860236atagen2026-10-10 12:44 UTC127
        .route("/forum/threads/{year}", global(&meta::THREAD))
782069860236atagen2026-10-10 12:44 UTC128
        .route("/graveyard", global(&meta::GRAVEYARD))
782069860236atagen2026-10-10 12:44 UTC129
        .route("/leaderboard", global(&meta::LEADERBOARD))
782069860236atagen2026-10-10 12:44 UTC130
        .fallback(meta::not_found)
782069860236atagen2026-10-10 12:44 UTC131
        .layer(axum::middleware::from_fn(middleware::chrome))
7288fc269bb6atagen2026-10-10 13:36 UTC132
        .layer(CatchPanicLayer::custom(middleware::panicked))
782069860236atagen2026-10-10 12:44 UTC133
        .with_state(state)
782069860236atagen2026-10-10 12:44 UTC134
}