63 lines
3.1 KiB
MySQL
63 lines
3.1 KiB
MySQL
|
|
-- 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);
|