-- 991_terms_acceptance.sql -- v1.0.10 légal item 3 — CGU / CGV / mentions légales versionnées. -- -- RGPD demands that any change to terms-of-service-class documents be -- explicitly re-accepted by the user. To prove acceptance in case of -- a contentieux, we need a per-user, per-document, per-version row -- with the timestamp and the originating IP address. -- -- The current version of each document is hardcoded in the Go service -- (auth.CurrentTerms.{CGU,CGV,Mentions}, ISO date strings). When the -- text is edited, bump the constant ; the next time the user lands on -- /api/v1/legal/terms/current the response signals "you have unaccepted -- versions" and the SPA forces the AcceptanceModal before any other -- action is allowed. -- -- terms_type is a string (not enum) so a new doc class can be added -- without a schema migration. CREATE TABLE IF NOT EXISTS public.terms_acceptances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, -- Document class. Conventional values : 'cgu' (terms of service), -- 'cgv' (sales terms), 'mentions' (legal mentions / impressum), -- 'privacy' (privacy policy). Lowercased ASCII so URL slugs and -- DB rows agree. terms_type VARCHAR(32) NOT NULL, -- Version identifier. Convention : the publication date in ISO -- format (YYYY-MM-DD). Sortable, unambiguous, no minor-version -- ambiguity. Long enough for a hash suffix if a same-day rev needs -- one (e.g. '2026-04-30-r2'). version VARCHAR(32) NOT NULL, accepted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Legally useful : if a user later disputes acceptance, the IP at -- accept time is one piece of corroborating evidence (alongside the -- session log). NULL only if the request didn't carry one (CLI -- scripts, internal seed paths) — should never be NULL on the -- /api/v1/legal/terms/accept path. ip_address INET, -- The handler may want to record the user-agent for the same -- audit purpose. Keep it bounded so a hostile UA can't bloat the -- table. user_agent VARCHAR(512) ); COMMENT ON TABLE public.terms_acceptances IS 'Per-user / per-document / per-version acceptance ledger. v1.0.10 légal item 3.'; COMMENT ON COLUMN public.terms_acceptances.version IS 'Version label, conventionally the publication ISO date. Hardcoded server-side.'; COMMENT ON COLUMN public.terms_acceptances.ip_address IS 'Originating IP at accept time, for legal evidence in case of dispute.'; -- Lookup by user (the most frequent query — "has this user accepted -- the current versions?"). The combination is unique per row : a -- user accepting CGU v2 a second time should be a no-op, not a -- duplicate insertion. UNIQUE captures this. CREATE UNIQUE INDEX IF NOT EXISTS idx_terms_acceptances_user_doc_version ON public.terms_acceptances(user_id, terms_type, version); -- Reverse-direction lookup : "how many users have accepted version -- 2026-04-30 of CGU" — useful for the operator dashboard / audit. CREATE INDEX IF NOT EXISTS idx_terms_acceptances_doc_version ON public.terms_acceptances(terms_type, version);