atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/http/meta.rs

782069860236atagen2026-10-10 12:44 UTC1
//! Resources that do not belong to a single run.
782069860236atagen2026-10-10 12:44 UTC2
782069860236atagen2026-10-10 12:44 UTC3
use axum::{
782069860236atagen2026-10-10 12:44 UTC4
    Json,
782069860236atagen2026-10-10 12:44 UTC5
    http::{StatusCode, header},
782069860236atagen2026-10-10 12:44 UTC6
    response::{IntoResponse, Response},
782069860236atagen2026-10-10 12:44 UTC7
};
782069860236atagen2026-10-10 12:44 UTC8
use rand::Rng;
8b8e83329a1datagen2026-10-12 12:01 UTC9
use serde_json::json;
782069860236atagen2026-10-10 12:44 UTC10
782069860236atagen2026-10-10 12:44 UTC11
use super::{
782069860236atagen2026-10-10 12:44 UTC12
    Shared,
782069860236atagen2026-10-10 12:44 UTC13
    ctx::Ctx,
8b8e83329a1datagen2026-10-12 12:01 UTC14
    leaderboard::{graveyard, leaderboard},
782069860236atagen2026-10-10 12:44 UTC15
    resource::{Resource, Verb},
8b8e83329a1datagen2026-10-12 12:01 UTC16
    run::{self, records_unavailable},
782069860236atagen2026-10-10 12:44 UTC17
};
782069860236atagen2026-10-10 12:44 UTC18
use crate::{
8b8e83329a1datagen2026-10-12 12:01 UTC19
    game::{ELDER_LAST_MODIFIED, FORUM_THREAD, Run, forum_thread},
782069860236atagen2026-10-10 12:44 UTC20
    headers::X_SEED,
8b8e83329a1datagen2026-10-12 12:01 UTC21
    problem::Problem,
782069860236atagen2026-10-10 12:44 UTC22
};
782069860236atagen2026-10-10 12:44 UTC23
782069860236atagen2026-10-10 12:44 UTC24
pub static INDEX: Resource<Shared> = Resource::new(&[Verb::new("GET", index)]);
782069860236atagen2026-10-10 12:44 UTC25
pub static RUNS: Resource<Shared> = Resource::new(&[Verb::new("POST", create_run)]);
782069860236atagen2026-10-10 12:44 UTC26
pub static THREAD: Resource<Shared> = Resource::new(&[Verb::new("GET", thread)]);
4f54028cba69atagen2026-10-14 11:45 UTC27
pub const GRAVEYARD_PATH: &str = "/graveyard";
4f54028cba69atagen2026-10-14 11:45 UTC28
pub const LEADERBOARD_PATH: &str = "/leaderboard";
4f54028cba69atagen2026-10-14 11:45 UTC29
782069860236atagen2026-10-10 12:44 UTC30
pub static GRAVEYARD: Resource<Shared> = Resource::new(&[Verb::new("GET", graveyard)]);
782069860236atagen2026-10-10 12:44 UTC31
pub static LEADERBOARD: Resource<Shared> = Resource::new(&[Verb::new("GET", leaderboard)]);
782069860236atagen2026-10-10 12:44 UTC32
782069860236atagen2026-10-10 12:44 UTC33
fn index(_: &mut Shared, ctx: &Ctx) -> Response {
782069860236atagen2026-10-10 12:44 UTC34
    let wants_html = ctx
782069860236atagen2026-10-10 12:44 UTC35
        .header(header::ACCEPT)
782069860236atagen2026-10-10 12:44 UTC36
        .is_some_and(|accept| accept.contains("text/html"));
782069860236atagen2026-10-10 12:44 UTC37
    if wants_html {
782069860236atagen2026-10-10 12:44 UTC38
        return (
782069860236atagen2026-10-10 12:44 UTC39
            StatusCode::SEE_OTHER,
782069860236atagen2026-10-10 12:44 UTC40
            [(header::LOCATION, "/ui/"), (header::VARY, "Accept")],
782069860236atagen2026-10-10 12:44 UTC41
        )
782069860236atagen2026-10-10 12:44 UTC42
            .into_response();
782069860236atagen2026-10-10 12:44 UTC43
    }
782069860236atagen2026-10-10 12:44 UTC44
    (
782069860236atagen2026-10-10 12:44 UTC45
        [(header::VARY, "Accept")],
782069860236atagen2026-10-10 12:44 UTC46
        Json(json!({
782069860236atagen2026-10-10 12:44 UTC47
            "detail": "This is jelqd. It is a roguelike. It is played over HTTP.",
782069860236atagen2026-10-10 12:44 UTC48
            "start": { "href": "/runs", "method": "POST" },
4f54028cba69atagen2026-10-14 11:45 UTC49
            "leaderboard": LEADERBOARD_PATH,
4f54028cba69atagen2026-10-14 11:45 UTC50
            "graveyard": GRAVEYARD_PATH,
782069860236atagen2026-10-10 12:44 UTC51
            "forum": FORUM_THREAD,
782069860236atagen2026-10-10 12:44 UTC52
            "ui": "/ui/",
782069860236atagen2026-10-10 12:44 UTC53
        })),
782069860236atagen2026-10-10 12:44 UTC54
    )
782069860236atagen2026-10-10 12:44 UTC55
        .into_response()
782069860236atagen2026-10-10 12:44 UTC56
}
782069860236atagen2026-10-10 12:44 UTC57
782069860236atagen2026-10-10 12:44 UTC58
fn create_run(state: &mut Shared, ctx: &Ctx) -> Response {
782069860236atagen2026-10-10 12:44 UTC59
    let seeded = ctx.query("seed").is_some();
782069860236atagen2026-10-10 12:44 UTC60
    let seed = match ctx.query("seed").map(str::parse::<u64>) {
782069860236atagen2026-10-10 12:44 UTC61
        None => rand::rng().random(),
782069860236atagen2026-10-10 12:44 UTC62
        Some(Ok(seed)) => seed,
782069860236atagen2026-10-10 12:44 UTC63
        Some(Err(_)) => {
782069860236atagen2026-10-10 12:44 UTC64
            return Problem::new(
782069860236atagen2026-10-10 12:44 UTC65
                StatusCode::BAD_REQUEST,
782069860236atagen2026-10-10 12:44 UTC66
                "bad-seed",
782069860236atagen2026-10-10 12:44 UTC67
                "The seed must be an unsigned 64-bit integer.",
782069860236atagen2026-10-10 12:44 UTC68
            )
782069860236atagen2026-10-10 12:44 UTC69
            .into_response();
782069860236atagen2026-10-10 12:44 UTC70
        }
782069860236atagen2026-10-10 12:44 UTC71
    };
782069860236atagen2026-10-10 12:44 UTC72
    let config = &state.config;
159f0998263batagen2026-10-12 11:01 UTC73
    if state.runs.len() >= config.max_runs {
782069860236atagen2026-10-10 12:44 UTC74
        return Problem::new(
782069860236atagen2026-10-10 12:44 UTC75
            StatusCode::SERVICE_UNAVAILABLE,
782069860236atagen2026-10-10 12:44 UTC76
            "server-full",
782069860236atagen2026-10-10 12:44 UTC77
            "The server is full. Everyone is in there.",
782069860236atagen2026-10-10 12:44 UTC78
        )
782069860236atagen2026-10-10 12:44 UTC79
        .header(header::RETRY_AFTER, "600")
782069860236atagen2026-10-10 12:44 UTC80
        .into_response();
782069860236atagen2026-10-10 12:44 UTC81
    }
8b8e83329a1datagen2026-10-12 12:01 UTC82
    let mut run = loop {
782069860236atagen2026-10-10 12:44 UTC83
        let id = format!("{:032x}", rand::rng().random::<u128>());
8b8e83329a1datagen2026-10-12 12:01 UTC84
        match state.records.find(&id) {
8b8e83329a1datagen2026-10-12 12:01 UTC85
            Ok(None) => {}
8b8e83329a1datagen2026-10-12 12:01 UTC86
            Ok(Some(_)) => continue,
8b8e83329a1datagen2026-10-12 12:01 UTC87
            Err(error) => return records_unavailable(&error),
782069860236atagen2026-10-10 12:44 UTC88
        }
8b8e83329a1datagen2026-10-12 12:01 UTC89
        break Run::with_oracle(
8b8e83329a1datagen2026-10-12 12:01 UTC90
            id,
8b8e83329a1datagen2026-10-12 12:01 UTC91
            format!("{:08x}", rand::rng().random::<u32>()),
8b8e83329a1datagen2026-10-12 12:01 UTC92
            config.secret.mix_seed(seed),
8b8e83329a1datagen2026-10-12 12:01 UTC93
            seeded,
8b8e83329a1datagen2026-10-12 12:01 UTC94
            &config.oracle,
8b8e83329a1datagen2026-10-12 12:01 UTC95
        );
782069860236atagen2026-10-10 12:44 UTC96
    };
782069860236atagen2026-10-10 12:44 UTC97
    let response = (
782069860236atagen2026-10-10 12:44 UTC98
        StatusCode::CREATED,
782069860236atagen2026-10-10 12:44 UTC99
        [
782069860236atagen2026-10-10 12:44 UTC100
            (header::LOCATION, run.path("")),
782069860236atagen2026-10-10 12:44 UTC101
            (header::ETAG, run.etag()),
782069860236atagen2026-10-10 12:44 UTC102
            (X_SEED, seed.to_string()),
782069860236atagen2026-10-10 12:44 UTC103
        ],
782069860236atagen2026-10-10 12:44 UTC104
        Json(run.view()),
782069860236atagen2026-10-10 12:44 UTC105
    )
782069860236atagen2026-10-10 12:44 UTC106
        .into_response();
782069860236atagen2026-10-10 12:44 UTC107
    let response = run::finish(&mut run, ctx, &config.secret, response);
8b8e83329a1datagen2026-10-12 12:01 UTC108
    if state.runs.insert_new(run) {
8b8e83329a1datagen2026-10-12 12:01 UTC109
        response
8b8e83329a1datagen2026-10-12 12:01 UTC110
    } else {
8b8e83329a1datagen2026-10-12 12:01 UTC111
        create_run(state, ctx)
8b8e83329a1datagen2026-10-12 12:01 UTC112
    }
782069860236atagen2026-10-10 12:44 UTC113
}
782069860236atagen2026-10-10 12:44 UTC114
782069860236atagen2026-10-10 12:44 UTC115
fn thread(_: &mut Shared, ctx: &Ctx) -> Response {
782069860236atagen2026-10-10 12:44 UTC116
    match ctx.param("year").parse::<u32>() {
782069860236atagen2026-10-10 12:44 UTC117
        Ok(2009) => (
782069860236atagen2026-10-10 12:44 UTC118
            [(header::LAST_MODIFIED, ELDER_LAST_MODIFIED)],
782069860236atagen2026-10-10 12:44 UTC119
            Json(forum_thread()),
782069860236atagen2026-10-10 12:44 UTC120
        )
782069860236atagen2026-10-10 12:44 UTC121
            .into_response(),
782069860236atagen2026-10-10 12:44 UTC122
        Ok(year) if year > 2009 => Problem::new(
782069860236atagen2026-10-10 12:44 UTC123
            StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC124
            "no-thread",
782069860236atagen2026-10-10 12:44 UTC125
            "Nobody has posted since 2009.",
782069860236atagen2026-10-10 12:44 UTC126
        )
782069860236atagen2026-10-10 12:44 UTC127
        .with("thread", FORUM_THREAD)
782069860236atagen2026-10-10 12:44 UTC128
        .into_response(),
782069860236atagen2026-10-10 12:44 UTC129
        _ => Problem::new(
782069860236atagen2026-10-10 12:44 UTC130
            StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC131
            "no-thread",
782069860236atagen2026-10-10 12:44 UTC132
            "The forum did not exist yet. The problem did.",
782069860236atagen2026-10-10 12:44 UTC133
        )
782069860236atagen2026-10-10 12:44 UTC134
        .with("thread", FORUM_THREAD)
782069860236atagen2026-10-10 12:44 UTC135
        .into_response(),
782069860236atagen2026-10-10 12:44 UTC136
    }
782069860236atagen2026-10-10 12:44 UTC137
}
782069860236atagen2026-10-10 12:44 UTC138
782069860236atagen2026-10-10 12:44 UTC139
pub async fn not_found() -> Problem {
782069860236atagen2026-10-10 12:44 UTC140
    Problem::new(
782069860236atagen2026-10-10 12:44 UTC141
        StatusCode::NOT_FOUND,
782069860236atagen2026-10-10 12:44 UTC142
        "not-found",
782069860236atagen2026-10-10 12:44 UTC143
        "The resource does not exist. This is not about you.",
782069860236atagen2026-10-10 12:44 UTC144
    )
782069860236atagen2026-10-10 12:44 UTC145
}