an http based roguelike rpg about jelqing
Blame: src/http/meta.rs
782069860236 | atagen | 2026-10-10 12:44 UTC | 1 | //! Resources that do not belong to a single run. |
782069860236 | atagen | 2026-10-10 12:44 UTC | 2 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 3 | use axum::{ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 4 | Json, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 5 | http::{StatusCode, header}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 6 | response::{IntoResponse, Response}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 7 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 8 | use rand::Rng; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 9 | use serde_json::json; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 10 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 11 | use super::{ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 12 | Shared, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 13 | ctx::Ctx, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 14 | leaderboard::{graveyard, leaderboard}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 15 | resource::{Resource, Verb}, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 16 | run::{self, records_unavailable}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 17 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 18 | use crate::{ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 19 | game::{ELDER_LAST_MODIFIED, FORUM_THREAD, Run, forum_thread}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 20 | headers::X_SEED, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 21 | problem::Problem, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 22 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 23 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 24 | pub static INDEX: Resource<Shared> = Resource::new(&[Verb::new("GET", index)]); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 25 | pub static RUNS: Resource<Shared> = Resource::new(&[Verb::new("POST", create_run)]); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 26 | pub static THREAD: Resource<Shared> = Resource::new(&[Verb::new("GET", thread)]); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 27 | pub static GRAVEYARD: Resource<Shared> = Resource::new(&[Verb::new("GET", graveyard)]); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 28 | pub static LEADERBOARD: Resource<Shared> = Resource::new(&[Verb::new("GET", leaderboard)]); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 29 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 30 | fn index(_: &mut Shared, ctx: &Ctx) -> Response { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 31 | let wants_html = ctx |
782069860236 | atagen | 2026-10-10 12:44 UTC | 32 | .header(header::ACCEPT) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 33 | .is_some_and(|accept| accept.contains("text/html")); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 34 | if wants_html { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 35 | return ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 36 | StatusCode::SEE_OTHER, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 37 | [(header::LOCATION, "/ui/"), (header::VARY, "Accept")], |
782069860236 | atagen | 2026-10-10 12:44 UTC | 38 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 39 | .into_response(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 40 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 41 | ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 42 | [(header::VARY, "Accept")], |
782069860236 | atagen | 2026-10-10 12:44 UTC | 43 | Json(json!({ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 44 | "detail": "This is jelqd. It is a roguelike. It is played over HTTP.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 45 | "start": { "href": "/runs", "method": "POST" }, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 46 | "leaderboard": "/leaderboard", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 47 | "graveyard": "/graveyard", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 48 | "forum": FORUM_THREAD, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 49 | "ui": "/ui/", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 50 | })), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 51 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 52 | .into_response() |
782069860236 | atagen | 2026-10-10 12:44 UTC | 53 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 54 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 55 | fn create_run(state: &mut Shared, ctx: &Ctx) -> Response { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 56 | let seeded = ctx.query("seed").is_some(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 57 | let seed = match ctx.query("seed").map(str::parse::<u64>) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 58 | None => rand::rng().random(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 59 | Some(Ok(seed)) => seed, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 60 | Some(Err(_)) => { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 61 | return Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 62 | StatusCode::BAD_REQUEST, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 63 | "bad-seed", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 64 | "The seed must be an unsigned 64-bit integer.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 65 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 66 | .into_response(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 67 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 68 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 69 | let config = &state.config; |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 70 | if state.runs.len() >= config.max_runs { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 71 | return Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 72 | StatusCode::SERVICE_UNAVAILABLE, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 73 | "server-full", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 74 | "The server is full. Everyone is in there.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 75 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 76 | .header(header::RETRY_AFTER, "600") |
782069860236 | atagen | 2026-10-10 12:44 UTC | 77 | .into_response(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 78 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | let mut run = loop { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 80 | let id = format!("{:032x}", rand::rng().random::<u128>()); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 81 | match state.records.find(&id) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 82 | Ok(None) => {} |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 83 | Ok(Some(_)) => continue, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 84 | Err(error) => return records_unavailable(&error), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 85 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 86 | break Run::with_oracle( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 87 | id, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 88 | format!("{:08x}", rand::rng().random::<u32>()), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 89 | config.secret.mix_seed(seed), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 90 | seeded, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 91 | &config.oracle, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 92 | ); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 93 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 94 | let response = ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 95 | StatusCode::CREATED, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 96 | [ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 97 | (header::LOCATION, run.path("")), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 98 | (header::ETAG, run.etag()), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 99 | (X_SEED, seed.to_string()), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 100 | ], |
782069860236 | atagen | 2026-10-10 12:44 UTC | 101 | Json(run.view()), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 102 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 103 | .into_response(); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 104 | let response = run::finish(&mut run, ctx, &config.secret, response); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 105 | if state.runs.insert_new(run) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 106 | response |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 107 | } else { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 108 | create_run(state, ctx) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 109 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 110 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 111 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 112 | fn thread(_: &mut Shared, ctx: &Ctx) -> Response { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 113 | match ctx.param("year").parse::<u32>() { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 114 | Ok(2009) => ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 115 | [(header::LAST_MODIFIED, ELDER_LAST_MODIFIED)], |
782069860236 | atagen | 2026-10-10 12:44 UTC | 116 | Json(forum_thread()), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 117 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 118 | .into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 119 | Ok(year) if year > 2009 => Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 120 | StatusCode::NOT_FOUND, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 121 | "no-thread", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 122 | "Nobody has posted since 2009.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 123 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 124 | .with("thread", FORUM_THREAD) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 125 | .into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 126 | _ => Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 127 | StatusCode::NOT_FOUND, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 128 | "no-thread", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 129 | "The forum did not exist yet. The problem did.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 130 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 131 | .with("thread", FORUM_THREAD) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 132 | .into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 133 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 134 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 135 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 136 | pub async fn not_found() -> Problem { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 137 | Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 138 | StatusCode::NOT_FOUND, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 139 | "not-found", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 140 | "The resource does not exist. This is not about you.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 141 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 142 | } |