96 lines
4.2 KiB
MySQL
96 lines
4.2 KiB
MySQL
|
|
-- 988_dmca_notices.sql
|
||
|
|
-- v1.0.9 W3 Day 14 — DMCA notice handler + workflow.
|
||
|
|
--
|
||
|
|
-- Creates the table that holds incoming DMCA takedown requests + the
|
||
|
|
-- admin's response trail, and adds a `dmca_blocked` flag on tracks
|
||
|
|
-- so a takedown is enforced both at the visibility layer (is_public)
|
||
|
|
-- AND at the playback layer (dmca_blocked stops authenticated owners
|
||
|
|
-- from playing back too).
|
||
|
|
--
|
||
|
|
-- Workflow states :
|
||
|
|
-- pending — submitted by claimant, waiting for admin review
|
||
|
|
-- takedown — admin honored the takedown ; track is_public=false + dmca_blocked=true
|
||
|
|
-- dismissed — admin rejected the notice (insufficient grounds, fraud, ...)
|
||
|
|
-- counter_notice — uploader filed a counter-notice ; tracking only, no auto-restore
|
||
|
|
-- restored — admin reviewed the counter-notice and restored the track
|
||
|
|
--
|
||
|
|
-- audit_log : JSONB array of {ts, actor_user_id, action, note} entries,
|
||
|
|
-- appended on every state change. Lets us reconstruct the trail without
|
||
|
|
-- joining audit_logs (which has cardinality issues on hot queries).
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS public.dmca_notices (
|
||
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
|
|
status VARCHAR(32) NOT NULL DEFAULT 'pending'
|
||
|
|
CHECK (status IN ('pending', 'takedown', 'dismissed', 'counter_notice', 'restored')),
|
||
|
|
|
||
|
|
-- Claimant identity (required by 17 USC § 512(c)(3) — DMCA safe harbor).
|
||
|
|
claimant_email VARCHAR(255) NOT NULL,
|
||
|
|
claimant_name VARCHAR(255) NOT NULL,
|
||
|
|
claimant_address TEXT NOT NULL,
|
||
|
|
work_description TEXT NOT NULL,
|
||
|
|
|
||
|
|
-- The allegedly infringing track. ON DELETE SET NULL — keep the
|
||
|
|
-- record if the track is deleted later (audit trail must persist).
|
||
|
|
infringing_track_id UUID REFERENCES public.tracks(id) ON DELETE SET NULL,
|
||
|
|
|
||
|
|
-- "Under penalty of perjury" statement — the claimant must check
|
||
|
|
-- a box; we record the timestamp it was acknowledged. Required by
|
||
|
|
-- DMCA § 512(c)(3)(A)(vi).
|
||
|
|
sworn_statement_at TIMESTAMPTZ NOT NULL,
|
||
|
|
|
||
|
|
-- Admin action trail.
|
||
|
|
takedown_at TIMESTAMPTZ,
|
||
|
|
counter_notice_at TIMESTAMPTZ,
|
||
|
|
restored_at TIMESTAMPTZ,
|
||
|
|
|
||
|
|
-- Free-form audit trail. Each entry : {ts, actor_user_id, action, note}.
|
||
|
|
audit_log JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||
|
|
|
||
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Pending queue index — admin lists pending notices oldest-first.
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_dmca_notices_pending
|
||
|
|
ON public.dmca_notices(created_at ASC)
|
||
|
|
WHERE status = 'pending';
|
||
|
|
|
||
|
|
-- Lookup by track — when an admin opens a track they should see if any
|
||
|
|
-- DMCA history exists.
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_dmca_notices_track
|
||
|
|
ON public.dmca_notices(infringing_track_id)
|
||
|
|
WHERE infringing_track_id IS NOT NULL;
|
||
|
|
|
||
|
|
COMMENT ON TABLE public.dmca_notices IS
|
||
|
|
'DMCA takedown requests + admin response trail. v1.0.9 W3 Day 14.';
|
||
|
|
COMMENT ON COLUMN public.dmca_notices.audit_log IS
|
||
|
|
'JSONB array of {ts, actor_user_id, action, note} appended on each state change.';
|
||
|
|
|
||
|
|
-- ---------------------------------------------------------------------
|
||
|
|
-- Track flag : dmca_blocked. When TRUE, no playback path serves the
|
||
|
|
-- track regardless of `is_public`. The visibility flag (is_public)
|
||
|
|
-- gets flipped too, but `dmca_blocked` is the authoritative gate
|
||
|
|
-- because an owner can re-public their own track in the UI ; only
|
||
|
|
-- a `restored_at` action on the notice clears the block.
|
||
|
|
-- ---------------------------------------------------------------------
|
||
|
|
ALTER TABLE public.tracks
|
||
|
|
ADD COLUMN IF NOT EXISTS dmca_blocked BOOLEAN NOT NULL DEFAULT FALSE;
|
||
|
|
|
||
|
|
COMMENT ON COLUMN public.tracks.dmca_blocked IS
|
||
|
|
'TRUE when a DMCA takedown is in force ; gates playback regardless of is_public. v1.0.9 W3 Day 14.';
|
||
|
|
|
||
|
|
-- updated_at maintenance trigger, mirroring the pattern used elsewhere.
|
||
|
|
CREATE OR REPLACE FUNCTION dmca_notices_updated_at()
|
||
|
|
RETURNS TRIGGER AS $$
|
||
|
|
BEGIN
|
||
|
|
NEW.updated_at = now();
|
||
|
|
RETURN NEW;
|
||
|
|
END;
|
||
|
|
$$ LANGUAGE plpgsql;
|
||
|
|
|
||
|
|
DROP TRIGGER IF EXISTS dmca_notices_updated_at_trg ON public.dmca_notices;
|
||
|
|
CREATE TRIGGER dmca_notices_updated_at_trg
|
||
|
|
BEFORE UPDATE ON public.dmca_notices
|
||
|
|
FOR EACH ROW
|
||
|
|
EXECUTE FUNCTION dmca_notices_updated_at();
|