atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
782069860236703e2dd3c52bb589b9e2d8d48176
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;
782069860236atagen2026-10-10 12:44 UTC4
mod resource;
782069860236atagen2026-10-10 12:44 UTC5
mod response;
782069860236atagen2026-10-10 12:44 UTC6
mod run;
782069860236atagen2026-10-10 12:44 UTC7
mod runs;
782069860236atagen2026-10-10 12:44 UTC8
mod ui;
782069860236atagen2026-10-10 12:44 UTC9
782069860236atagen2026-10-10 12:44 UTC10
use std::{
782069860236atagen2026-10-10 12:44 UTC11
    collections::HashMap,
782069860236atagen2026-10-10 12:44 UTC12
    sync::{Arc, Mutex, MutexGuard, PoisonError},
782069860236atagen2026-10-10 12:44 UTC13
};
782069860236atagen2026-10-10 12:44 UTC14
782069860236atagen2026-10-10 12:44 UTC15
use axum::{
782069860236atagen2026-10-10 12:44 UTC16
    Router,
782069860236atagen2026-10-10 12:44 UTC17
    extract::State,
782069860236atagen2026-10-10 12:44 UTC18
    response::Redirect,
782069860236atagen2026-10-10 12:44 UTC19
    routing::{MethodRouter, any},
782069860236atagen2026-10-10 12:44 UTC20
};
782069860236atagen2026-10-10 12:44 UTC21
782069860236atagen2026-10-10 12:44 UTC22
use crate::{config::Config, game::Run};
782069860236atagen2026-10-10 12:44 UTC23
use ctx::Ctx;
782069860236atagen2026-10-10 12:44 UTC24
use resource::Resource;
782069860236atagen2026-10-10 12:44 UTC25
use run::RunResource;
782069860236atagen2026-10-10 12:44 UTC26
782069860236atagen2026-10-10 12:44 UTC27
pub struct AppState {
782069860236atagen2026-10-10 12:44 UTC28
    runs: Mutex<HashMap<String, Run>>,
782069860236atagen2026-10-10 12:44 UTC29
    config: Config,
782069860236atagen2026-10-10 12:44 UTC30
}
782069860236atagen2026-10-10 12:44 UTC31
782069860236atagen2026-10-10 12:44 UTC32
impl AppState {
782069860236atagen2026-10-10 12:44 UTC33
    pub fn new(config: Config) -> Self {
782069860236atagen2026-10-10 12:44 UTC34
        Self {
782069860236atagen2026-10-10 12:44 UTC35
            runs: Mutex::default(),
782069860236atagen2026-10-10 12:44 UTC36
            config,
782069860236atagen2026-10-10 12:44 UTC37
        }
782069860236atagen2026-10-10 12:44 UTC38
    }
782069860236atagen2026-10-10 12:44 UTC39
782069860236atagen2026-10-10 12:44 UTC40
    /// A panic mid-request must not take every other run down with it, so a poisoned lock is recovered.
782069860236atagen2026-10-10 12:44 UTC41
    fn runs(&self) -> MutexGuard<'_, HashMap<String, Run>> {
782069860236atagen2026-10-10 12:44 UTC42
        self.runs.lock().unwrap_or_else(PoisonError::into_inner)
782069860236atagen2026-10-10 12:44 UTC43
    }
782069860236atagen2026-10-10 12:44 UTC44
782069860236atagen2026-10-10 12:44 UTC45
    #[cfg(test)]
782069860236atagen2026-10-10 12:44 UTC46
    pub(crate) fn insert(&self, run: Run) {
782069860236atagen2026-10-10 12:44 UTC47
        self.runs().insert(run.id.clone(), run);
782069860236atagen2026-10-10 12:44 UTC48
    }
782069860236atagen2026-10-10 12:44 UTC49
}
782069860236atagen2026-10-10 12:44 UTC50
782069860236atagen2026-10-10 12:44 UTC51
impl Default for AppState {
782069860236atagen2026-10-10 12:44 UTC52
    fn default() -> Self {
782069860236atagen2026-10-10 12:44 UTC53
        Self::new(Config::default())
782069860236atagen2026-10-10 12:44 UTC54
    }
782069860236atagen2026-10-10 12:44 UTC55
}
782069860236atagen2026-10-10 12:44 UTC56
782069860236atagen2026-10-10 12:44 UTC57
type Shared = Arc<AppState>;
782069860236atagen2026-10-10 12:44 UTC58
782069860236atagen2026-10-10 12:44 UTC59
pub fn app(config: Config) -> Router {
782069860236atagen2026-10-10 12:44 UTC60
    router(Arc::new(AppState::new(config)))
782069860236atagen2026-10-10 12:44 UTC61
}
782069860236atagen2026-10-10 12:44 UTC62
782069860236atagen2026-10-10 12:44 UTC63
pub fn router(state: Shared) -> Router {
782069860236atagen2026-10-10 12:44 UTC64
    let api = api(state);
782069860236atagen2026-10-10 12:44 UTC65
    let ui = Router::new()
782069860236atagen2026-10-10 12:44 UTC66
        .route("/ui", any(|| async { Redirect::permanent("/ui/") }))
782069860236atagen2026-10-10 12:44 UTC67
        .route("/ui/", any(ui::page))
782069860236atagen2026-10-10 12:44 UTC68
        .route("/ui/send", any(ui::send))
782069860236atagen2026-10-10 12:44 UTC69
        .with_state(api.clone());
782069860236atagen2026-10-10 12:44 UTC70
    api.merge(ui)
782069860236atagen2026-10-10 12:44 UTC71
}
782069860236atagen2026-10-10 12:44 UTC72
782069860236atagen2026-10-10 12:44 UTC73
fn global(resource: &'static Resource<Shared>) -> MethodRouter<Shared> {
782069860236atagen2026-10-10 12:44 UTC74
    any(
782069860236atagen2026-10-10 12:44 UTC75
        move |State(mut state): State<Shared>, ctx: Ctx| async move {
782069860236atagen2026-10-10 12:44 UTC76
            resource.dispatch(&mut state, &ctx)
782069860236atagen2026-10-10 12:44 UTC77
        },
782069860236atagen2026-10-10 12:44 UTC78
    )
782069860236atagen2026-10-10 12:44 UTC79
}
782069860236atagen2026-10-10 12:44 UTC80
782069860236atagen2026-10-10 12:44 UTC81
fn scoped(resource: &'static RunResource) -> MethodRouter<Shared> {
782069860236atagen2026-10-10 12:44 UTC82
    any(
782069860236atagen2026-10-10 12:44 UTC83
        move |State(state): State<Shared>, ctx: Ctx| async move { run::serve(&state, ctx, resource) },
782069860236atagen2026-10-10 12:44 UTC84
    )
782069860236atagen2026-10-10 12:44 UTC85
}
782069860236atagen2026-10-10 12:44 UTC86
782069860236atagen2026-10-10 12:44 UTC87
fn api(state: Shared) -> Router {
782069860236atagen2026-10-10 12:44 UTC88
    Router::new()
782069860236atagen2026-10-10 12:44 UTC89
        .route("/", global(&meta::INDEX))
782069860236atagen2026-10-10 12:44 UTC90
        .route("/runs", global(&meta::RUNS))
782069860236atagen2026-10-10 12:44 UTC91
        .route("/runs/{id}", scoped(&runs::RUN))
782069860236atagen2026-10-10 12:44 UTC92
        .route("/runs/{id}/length", scoped(&runs::LENGTH))
782069860236atagen2026-10-10 12:44 UTC93
        .route("/runs/{id}/foreskin", scoped(&runs::FORESKIN))
782069860236atagen2026-10-10 12:44 UTC94
        .route("/runs/{id}/inventory", scoped(&runs::INVENTORY))
782069860236atagen2026-10-10 12:44 UTC95
        .route("/runs/{id}/inventory/{item}", scoped(&runs::ITEM))
782069860236atagen2026-10-10 12:44 UTC96
        .route("/runs/{id}/techniques", scoped(&runs::TECHNIQUES))
782069860236atagen2026-10-10 12:44 UTC97
        .route("/runs/{id}/drawer", scoped(&runs::DRAWER))
782069860236atagen2026-10-10 12:44 UTC98
        .route("/runs/{id}/branches/{choice}", scoped(&runs::BRANCH))
782069860236atagen2026-10-10 12:44 UTC99
        .route("/runs/{id}/floor", scoped(&runs::FLOOR))
782069860236atagen2026-10-10 12:44 UTC100
        .route("/runs/{id}/shop", scoped(&runs::SHOP))
782069860236atagen2026-10-10 12:44 UTC101
        .route("/runs/{id}/shop/{item}", scoped(&runs::BUY))
782069860236atagen2026-10-10 12:44 UTC102
        .route("/runs/{id}/portal", scoped(&runs::PORTAL))
782069860236atagen2026-10-10 12:44 UTC103
        .route("/runs/{id}/elder", scoped(&runs::ELDER))
782069860236atagen2026-10-10 12:44 UTC104
        .route("/runs/{id}/grip", scoped(&runs::GRIP))
782069860236atagen2026-10-10 12:44 UTC105
        .route("/runs/{id}/diagnosis", scoped(&runs::DIAGNOSIS))
782069860236atagen2026-10-10 12:44 UTC106
        .route("/runs/{id}/footage", scoped(&runs::FOOTAGE))
782069860236atagen2026-10-10 12:44 UTC107
        .route("/forum/threads/{year}", global(&meta::THREAD))
782069860236atagen2026-10-10 12:44 UTC108
        .route("/graveyard", global(&meta::GRAVEYARD))
782069860236atagen2026-10-10 12:44 UTC109
        .route("/leaderboard", global(&meta::LEADERBOARD))
782069860236atagen2026-10-10 12:44 UTC110
        .fallback(meta::not_found)
782069860236atagen2026-10-10 12:44 UTC111
        .layer(axum::middleware::from_fn(middleware::chrome))
782069860236atagen2026-10-10 12:44 UTC112
        .with_state(state)
782069860236atagen2026-10-10 12:44 UTC113
}