# Konsept: Den Asynkrone Gjesten **Filsti:** `docs/concepts/den_asynkrone_gjesten.md` ## 1. Konsept Mange interessante gjester har ikke tid til å stille i studio. Den Asynkrone Gjesten lar redaksjonen sende en unik lenke til en gjest som kan svare på spørsmål via tale — fra mobilen, når det passer dem. Svarene lander direkte i redaksjonens arbeidsflyt, transkriberes automatisk, og kan brukes i podcasten. ## 2. Brukeropplevelse ### 2.1 Redaksjonens side 1. Redaksjonen oppretter en "Gjestesesjon" knyttet til et Tema. 2. Legger inn spørsmål (tekst) som gjesten skal svare på. 3. Systemet genererer en unik, tidsbegrenset URL. 4. URL-en sendes til gjesten via e-post, SMS eller chat. 5. Gjestens svar (lydmeldinger) dukker opp i Tema-chatten som `voice_memo`-meldinger, automatisk transkribert. 6. Redaksjonen triagerer svarene — kan tagge, klippe inn i episode, eller bruke som research. ### 2.2 Gjestens side 1. Gjesten åpner lenken i mobilnettleseren. Ingen app, ingen konto, ingen registrering. 2. Ser en enkel, ren flate: podcast-logo, spørsmålene fra redaksjonen, og en opptaksknapp per spørsmål. 3. Trykker record, snakker, trykker stopp. Kan lytte tilbake og ta om igjen. 4. Ved innsending lastes lydfilene opp og gjesten ser en bekreftelse. 5. Lenken utløper etter gitt tid eller antall besøk. ### 2.3 Minimal friksjon - Ingen Authentik-innlogging — tilgang via signert token - Ingen app — ren PWA/nettleser - Ingen redigering — gjesten snakker bare - Responsivt, mobil-first design ## 3. Komponenter | Feature | Rolle | |---|---| | Lydmeldinger | Opptakskomponent gjenbrukes (se `docs/features/lydmeldinger.md`) | | Chat (channels) | Svarene lander i en channel knyttet til Temaet | | Live transkripsjon | Whisper transkriberer via jobbkø (se `docs/features/live_transkripsjon.md`) | | Podcastfabrikken | Lydklipp kan trekkes inn som segment (se `docs/concepts/podcastfabrikken.md`) | ## 4. Autentisering: Gjeste-tokens ### 4.1 Datamodell ```sql guest_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE, guest_name TEXT NOT NULL, -- Visningsnavn ("Erna Solberg") questions JSONB NOT NULL, -- [{ "sort": 1, "text": "Hva tenker du om...?" }] token TEXT UNIQUE NOT NULL, -- Kryptografisk sikker, URL-safe token expires_at TIMESTAMPTZ NOT NULL, max_recordings SMALLINT DEFAULT 10, -- Maks antall opptak recordings_count SMALLINT DEFAULT 0, created_by TEXT NOT NULL REFERENCES users(authentik_id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ) ``` ### 4.2 Sikkerhet - Token er kryptografisk tilfeldig (256-bit, URL-safe base64). - SvelteKit validerer token ved hvert request: sjekker expiry, recordings_count < max_recordings, og workspace-tilhørighet. - Gjestens meldinger merkes med `author_id = NULL` og `metadata.guest_name` + `metadata.guest_token_id` for sporbarhet. - Ingen tilgang til andre channels, workspaces eller funksjoner. - Tokenet kan revokeres manuelt av redaksjonen. ### 4.3 Flyt (teknisk) ``` Gjest åpner URL med token → SvelteKit validerer token → Viser spørsmål + opptaksknapp → Gjest tar opp svar → SvelteKit streamer lydfil til media/{workspace_slug}/voice/ → Oppretter message (voice_memo) i channelen → Oppretter whisper_transcribe-jobb i jobbkøen → Inkrementerer recordings_count → Redaksjonen ser svaret i Tema-chatten ``` ## 5. Dataklassifisering | Data | Kategori | Detaljer | |---|---|---| | Gjestens lydopptak | Kritisk (backup) | Unikt innhold | | Guest tokens | Flyktig (TTL) | Utløper automatisk, slett expired tokens periodisk | | Spørsmål (JSONB) | Kritisk (PG) | Redaksjonelt innhold | ## 6. Instruks for Claude Code * `guest_tokens`-tabellen er **ikke** en node i grafen — den er ren tilgangsstyring. * Gjeste-UI er en egen SvelteKit-rute (`/guest/[token]`) med minimal layout (ingen navbar, ingen workspace-switcher). * Gjenbruk lydmeldinger-komponenten — ikke bygg en egen opptaksflyt. * Meldinger fra gjester har `author_id = NULL`. Frontend må håndtere dette gracefully (vis `guest_name` i stedet). * Tokenet skal **aldri** gi tilgang til å lese andre meldinger i channelen — gjesten kan kun skrive. * Alt er workspace-scopet. Token bærer workspace_id eksplisitt.