Files
portal/src/server/mod.rs
T
Bendik Aagaard LynghaugandClaude Fable 5.1 f395ace556
docs / deploy (push) Successful in 3s
Test / test (push) Successful in 1m1s
Storylines: the same site told several ways, one per visitor, counted
Any text a person reads may be a list, one entry per storyline, every
list the same length. A visitor is given one storyline for the whole
site and reads it on every page and in every mail about their case.
Records and events carry the storyline; GET /report gives counts per
bucket, storyline and state, and nothing else.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
2026-09-30 15:11:27 +02:00

61 lines
2.7 KiB
Rust

#![cfg(feature = "ssr")]
pub mod oidc;
pub mod storyline;
use arc_swap::ArcSwap;
use axum::extract::FromRef;
use leptos::prelude::LeptosOptions;
use std::collections::HashMap;
use std::sync::Arc;
use crate::content::Question;
#[derive(Clone)]
pub struct AppState {
pub leptos_options: LeptosOptions,
pub nats: async_nats::Client,
/// JetStream context - source of every NATS KV bucket this app
/// reads/writes (applicants, and whatever a `ResourceSpec` names).
pub jetstream: async_nats::jetstream::Context,
/// Swapped out wholesale on a `content::CONTENT_RELOAD_SUBJECT`
/// message (see `content::watch_for_reload`) - readers never hold
/// a lock, just an atomic pointer load, so a reload never blocks or
/// is blocked by an in-flight request.
pub questions: Arc<ArcSwap<crate::content::Storylines<HashMap<String, Question>>>>,
/// Every bucket's event-sourced state graph, loaded from
/// `aggregates.yaml` (see `content::load_aggregates_from_gitea`) and
/// hot-swapped alongside `questions` on the same reload. A bucket
/// with no entry here isn't event-sourced - plain KV mutate-in-place
/// still works (see `answers.rs`).
pub aggregates: Arc<ArcSwap<crate::content::Storylines<HashMap<String, crate::aggregates::AggregateSchema>>>>,
/// Site branding from the content repo's optional `site.yaml`
/// (title, wordmark, hero kind) - hot-swapped with `questions`/
/// `aggregates` on the same reload. Default = the historical uhhm
/// look.
pub site: Arc<ArcSwap<crate::content::Storylines<crate::content::SiteConfig>>>,
/// The access policy compiled from the content above (see
/// `access.rs`), swapped with it on every reload. Every server entry
/// point decides through this and nothing else.
pub access: Arc<ArcSwap<crate::access::Policy>>,
/// `scheme://host` of the Gitea instance content is loaded from
/// (see `content::gitea_api_base`) - kept alongside `questions`
/// rather than re-derived per call, since `resolve_gitea_repo` needs
/// it too and has no other reason to see `CONTENT_REPO` itself.
pub gitea_base: String,
/// `{gitea}/api/v1/repos/{owner}/{repo}/raw` and the branch - what
/// `content::site_asset_handler` proxies content-shipped assets from.
pub content_raw_base: String,
pub content_branch: String,
pub oidc: Arc<oidc::Oidc>,
/// `None` when `GARAGE_*` env vars aren't set - uploads are the one
/// optional feature, everything else works without Garage.
pub garage: Option<crate::upload::Garage>,
}
impl FromRef<AppState> for LeptosOptions {
fn from_ref(state: &AppState) -> Self {
state.leptos_options.clone()
}
}