Ekte parallell til frittstående runders RoundMessages (ADR-044), ikke en utvidelse av Banter Board (ADR-025) -- ny org-scopet (RLS) tournament_round_message-tabell, én tråd per turneringsrunde. Reaksjoner og trådede kommentarer med via eksisterende PostEngagement-komponent uendret; @-tagging og sanntid bevisst utenfor v1 (se ADR-102). Migrasjon 090 IKKE kjørt mot ekte teecup_db ennå -- venter på brukerbekreftelse før utrulling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
109 lines
5.5 KiB
SQL
109 lines
5.5 KiB
SQL
-- =====================================================================
|
|
-- TeeCup — Rundespesifikk kommentartråd for org-turneringer, migrasjon
|
|
-- 090 (ADR-102). Speiler `058_round_messages.sql` +
|
|
-- `059_post_reactions_and_comments.sql` sin round_message/
|
|
-- round_message_reaction/round_message_comment, MEN med organization_id
|
|
-- + full RLS -- i motsetning til den frittstående sidens bevisst
|
|
-- RLS-frie plain_connection()-mønster (ADR-033 Beslutning A), er
|
|
-- turneringer ER org-scopet (CLAUDE.md-invarianten).
|
|
--
|
|
-- Én tråd PER TURNERINGSRUNDE (hele feltet på tvers av flighter deler
|
|
-- tråd, IKKE per flight) -- bevisst valgt av bruker, se ADR-102.
|
|
--
|
|
-- Skriverett = leserett = org-medlemskap (get_authorized_org), IKKE en
|
|
-- ny synlighetsmodell -- samme presedens som individual_tournaments.py
|
|
-- sin egen modul-docstring allerede fastslår for runde-/deltaker-
|
|
-- OPPSETT ("vanlig org-medlemsnivå", ikke det strengere self-only-kravet
|
|
-- update_hole har for selve SCORING-en). Moderering: forfatter ELLER
|
|
-- org-admin (is_org_admin), samme regel som Banter Board (ADR-025).
|
|
--
|
|
-- Bevisst UTENFOR omfang i v1 (se ADR-102): ingen @-tagging (round_
|
|
-- message_tag-motparten er utelatt), ingen WebSocket-kringkasting
|
|
-- (individual-tournament-detail.tsx har ingen sanntids-tilkobling i
|
|
-- det hele tatt ennå -- eget, uprioritert gap).
|
|
-- =====================================================================
|
|
\set ON_ERROR_STOP on
|
|
|
|
CREATE TABLE tournament_round_message (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id uuid NOT NULL,
|
|
tournament_round_id uuid NOT NULL,
|
|
author_user_id uuid NOT NULL REFERENCES app_user(id),
|
|
-- Frosset ved skrivetidspunkt, samme prinsipp som round_message.author_display_name
|
|
-- -- fullt navn (roster-/forfatter-kontekst, ikke direkte adressering,
|
|
-- CLAUDE.md navneformat-regel).
|
|
author_display_name text NOT NULL,
|
|
body text,
|
|
image_key text,
|
|
-- Offline-kø-idempotens (samme mønster som round_message.client_message_id,
|
|
-- migrasjon 073) -- et gjentatt POST med samme id (avbrutt synk prøvd på
|
|
-- nytt) returnerer den allerede opprettede meldingen fremfor en duplikat.
|
|
client_message_id uuid,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
FOREIGN KEY (organization_id, tournament_round_id)
|
|
REFERENCES tournament_round(organization_id, id) ON DELETE CASCADE,
|
|
CHECK (body IS NOT NULL OR image_key IS NOT NULL)
|
|
);
|
|
|
|
CREATE INDEX ON tournament_round_message (organization_id, tournament_round_id, created_at);
|
|
CREATE INDEX ON tournament_round_message (organization_id, author_user_id);
|
|
CREATE UNIQUE INDEX ON tournament_round_message (tournament_round_id, client_message_id)
|
|
WHERE client_message_id IS NOT NULL;
|
|
|
|
CREATE TABLE tournament_round_message_reaction (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id uuid NOT NULL,
|
|
tournament_round_message_id uuid NOT NULL REFERENCES tournament_round_message(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES app_user(id),
|
|
emoji text NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
UNIQUE (tournament_round_message_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX ON tournament_round_message_reaction (organization_id, tournament_round_message_id);
|
|
|
|
CREATE TABLE tournament_round_message_comment (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id uuid NOT NULL,
|
|
tournament_round_message_id uuid NOT NULL REFERENCES tournament_round_message(id) ON DELETE CASCADE,
|
|
parent_comment_id uuid REFERENCES tournament_round_message_comment(id) ON DELETE CASCADE,
|
|
author_user_id uuid NOT NULL REFERENCES app_user(id),
|
|
author_display_name text NOT NULL,
|
|
body text NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
|
|
CHECK (length(btrim(body)) > 0)
|
|
);
|
|
|
|
CREATE INDEX ON tournament_round_message_comment (organization_id, tournament_round_message_id, created_at);
|
|
CREATE INDEX ON tournament_round_message_comment (organization_id, parent_comment_id);
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- Row-Level Security (samme org_isolation-policy-mønster som 040)
|
|
-- ---------------------------------------------------------------------
|
|
DO $$
|
|
DECLARE t text;
|
|
BEGIN
|
|
FOREACH t IN ARRAY ARRAY[
|
|
'tournament_round_message', 'tournament_round_message_reaction',
|
|
'tournament_round_message_comment'
|
|
]
|
|
LOOP
|
|
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY;', t);
|
|
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY;', t);
|
|
EXECUTE format($p$
|
|
CREATE POLICY org_isolation ON %I
|
|
USING (organization_id = current_setting('app.current_org', true)::uuid)
|
|
WITH CHECK (organization_id = current_setting('app.current_org', true)::uuid);
|
|
$p$, t);
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- Samme "slett og opprett på nytt, aldri rediger"-prinsipp som
|
|
-- round_message/message -- ingen UPDATE-grant på selve meldingen/
|
|
-- kommentaren. Reaksjoner får UPDATE (emoji-bytte er en UPSERT).
|
|
GRANT SELECT, INSERT, DELETE ON tournament_round_message TO teecup_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON tournament_round_message_reaction TO teecup_app;
|
|
GRANT SELECT, INSERT, DELETE ON tournament_round_message_comment TO teecup_app;
|