//! Unauthenticated, read-only endpoints safe to expose before a user has joined. use axum::Json; use axum::extract::State; use serde::Serialize; use crate::services::config; use crate::state::AppState; #[derive(Serialize)] pub struct PublicEventDto { pub name: String, pub slug: String, /// Whether the comment feature is on (env `COMMENTS_ENABLED`). The frontend hides /// the whole comment UI when false; exposed here so even the pre-auth shell knows. pub comments_enabled: bool, /// Active colour theme. `preset` is an id the frontend maps to a palette (or /// "custom"); `primary`/`accent` are the `#rrggbb` seeds the ramps derive from. /// Resolved as DB-config override → env default. Public so the theme applies on /// the very first (pre-auth) paint without a flash. pub theme_preset: String, pub theme_primary: String, pub theme_accent: String, /// The operator's data notice, if they set one. Empty string when unset (migration 009 /// defaults it to `''`). /// /// Exposed PUBLICLY — it was only on `/me/context`, which requires a token, so the one place a /// notice actually has to appear (before a name is collected) could not read it. The join page /// pairs this with a baseline notice of its own, precisely because this can be empty: relying /// on an operator-supplied string meant a stock deploy collected ~100 EU guests' photos of /// identifiable people, including children, with no notice at the point of collection at all. pub privacy_note: String, } /// Public event identity + presentation config, used by the pre-auth join/recover /// screens (which event am I joining, what does it look like). Only non-user-scoped /// fields are exposed, so this is safe without a token. Identity comes straight from /// instance config; the theme is resolved from the runtime `config` table (admin UI) /// falling back to the env-seeded default. pub async fn get_public_event(State(state): State) -> Json { let cache = &state.config_cache; Json(PublicEventDto { name: state.config.event_name.clone(), slug: state.config.event_slug.clone(), comments_enabled: state.config.comments_enabled, theme_preset: config::get_str(cache, "theme_preset", &state.config.default_theme_preset) .await, theme_primary: config::get_str(cache, "theme_primary", &state.config.default_theme_primary) .await, theme_accent: config::get_str(cache, "theme_accent", &state.config.default_theme_accent) .await, privacy_note: config::get_str(cache, "privacy_note", "").await, }) }