219 lines
11 KiB
SQL
219 lines
11 KiB
SQL
ALTER TABLE users DROP CONSTRAINT users_role_check;
|
|
ALTER TABLE users ADD CONSTRAINT users_role_check CHECK (role IN ('user','owner','admin','operator','support','auditor'));
|
|
|
|
ALTER TABLE user_permissions DROP CONSTRAINT user_permissions_permission_check;
|
|
ALTER TABLE user_permissions ADD CONSTRAINT user_permissions_permission_check CHECK (permission IN (
|
|
'users.read','users.write','memberships.manage','cards.manage','invites.manage',
|
|
'requests.manage','tickets.manage','notifications.manage','security.manage',
|
|
'operations.manage','e.manage','audit.read','logs.read','backups.manage',
|
|
'updates.manage','settings.manage','licenses.manage'
|
|
));
|
|
|
|
CREATE TABLE membership_plans (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code text NOT NULL UNIQUE CHECK (code ~ '^[a-z0-9][a-z0-9._-]{0,63}$'),
|
|
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
|
|
duration_days integer NOT NULL CHECK (duration_days > 0),
|
|
max_playbacks integer NOT NULL DEFAULT 0 CHECK (max_playbacks >= 0),
|
|
max_devices integer NOT NULL DEFAULT 0 CHECK (max_devices >= 0),
|
|
max_bitrate_kbps integer NOT NULL DEFAULT 0 CHECK (max_bitrate_kbps >= 0),
|
|
enabled boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
ALTER TABLE memberships DROP CONSTRAINT memberships_status_check;
|
|
ALTER TABLE memberships ADD CONSTRAINT memberships_status_check CHECK (status IN ('active','paused','expired','revoked'));
|
|
ALTER TABLE memberships
|
|
ADD COLUMN plan_id uuid REFERENCES membership_plans(id) ON DELETE SET NULL,
|
|
ADD COLUMN paused_at timestamptz,
|
|
ADD COLUMN pause_reason text NOT NULL DEFAULT '',
|
|
ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now();
|
|
|
|
ALTER TABLE media_requests DROP CONSTRAINT media_requests_status_check;
|
|
UPDATE media_requests SET status='submitted' WHERE status='open';
|
|
UPDATE media_requests SET status='accepted' WHERE status='planned';
|
|
ALTER TABLE media_requests ADD CONSTRAINT media_requests_status_check CHECK (status IN (
|
|
'submitted','reviewing','accepted','existing','downloading','fulfilled','failed','rejected','cancelled'
|
|
));
|
|
ALTER TABLE media_requests
|
|
ADD COLUMN media_type text NOT NULL DEFAULT 'other' CHECK (media_type IN ('movie','series','season','episode','other')),
|
|
ADD COLUMN release_year integer CHECK (release_year BETWEEN 1800 AND 3000),
|
|
ADD COLUMN priority text NOT NULL DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')),
|
|
ADD COLUMN assignee_user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
ADD COLUMN public_reply text NOT NULL DEFAULT '',
|
|
ADD COLUMN internal_note text NOT NULL DEFAULT '',
|
|
ADD COLUMN merged_into uuid REFERENCES media_requests(id) ON DELETE SET NULL,
|
|
ADD COLUMN closed_at timestamptz;
|
|
|
|
CREATE TABLE media_request_messages (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
request_id uuid NOT NULL REFERENCES media_requests(id) ON DELETE CASCADE,
|
|
author_user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
visibility text NOT NULL CHECK (visibility IN ('user','internal')),
|
|
body text NOT NULL CHECK (length(btrim(body)) BETWEEN 1 AND 5000),
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX media_request_messages_request_idx ON media_request_messages(request_id,created_at);
|
|
|
|
ALTER TABLE tickets DROP CONSTRAINT tickets_status_check;
|
|
UPDATE tickets SET status='in_progress' WHERE status='pending';
|
|
ALTER TABLE tickets ADD CONSTRAINT tickets_status_check CHECK (status IN ('open','in_progress','waiting_user','resolved','closed'));
|
|
ALTER TABLE tickets DROP CONSTRAINT tickets_priority_check;
|
|
ALTER TABLE tickets ADD CONSTRAINT tickets_priority_check CHECK (priority IN ('low','normal','high','urgent'));
|
|
ALTER TABLE tickets
|
|
ADD COLUMN sla_due_at timestamptz,
|
|
ADD COLUMN last_user_reply_at timestamptz,
|
|
ADD COLUMN last_staff_reply_at timestamptz,
|
|
ADD COLUMN reopened_at timestamptz;
|
|
ALTER TABLE ticket_replies
|
|
ADD COLUMN visibility text NOT NULL DEFAULT 'user' CHECK (visibility IN ('user','internal'));
|
|
|
|
CREATE TABLE announcements (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
title text NOT NULL CHECK (length(btrim(title)) BETWEEN 1 AND 200),
|
|
body text NOT NULL CHECK (length(btrim(body)) BETWEEN 1 AND 20000),
|
|
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','scheduled','published','withdrawn')),
|
|
audience text NOT NULL DEFAULT 'all' CHECK (audience IN ('all','active_members','staff')),
|
|
pinned boolean NOT NULL DEFAULT false,
|
|
publish_at timestamptz,
|
|
expires_at timestamptz,
|
|
created_by uuid NOT NULL REFERENCES users(id),
|
|
updated_by uuid NOT NULL REFERENCES users(id),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
CHECK (expires_at IS NULL OR publish_at IS NULL OR expires_at > publish_at)
|
|
);
|
|
CREATE INDEX announcements_status_publish_idx ON announcements(status,publish_at DESC);
|
|
|
|
CREATE TABLE announcement_reads (
|
|
announcement_id uuid NOT NULL REFERENCES announcements(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
read_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (announcement_id,user_id)
|
|
);
|
|
|
|
CREATE TABLE notification_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
event_type text NOT NULL,
|
|
target_user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
|
title text NOT NULL CHECK (length(btrim(title)) BETWEEN 1 AND 200),
|
|
body text NOT NULL CHECK (length(body) <= 10000),
|
|
link_path text NOT NULL DEFAULT '' CHECK (link_path='' OR link_path ~ '^/'),
|
|
related_type text NOT NULL DEFAULT '',
|
|
related_id text NOT NULL DEFAULT '',
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX notification_events_target_idx ON notification_events(target_user_id,created_at DESC);
|
|
|
|
CREATE TABLE notification_deliveries (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
event_id uuid NOT NULL REFERENCES notification_events(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
channel text NOT NULL CHECK (channel IN ('in_app','email','telegram')),
|
|
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sending','sent','failed','cancelled')),
|
|
attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
next_attempt_at timestamptz,
|
|
last_error text NOT NULL DEFAULT '',
|
|
read_at timestamptz,
|
|
deleted_at timestamptz,
|
|
sent_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE(event_id,user_id,channel)
|
|
);
|
|
CREATE INDEX notification_deliveries_user_idx ON notification_deliveries(user_id,channel,created_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX notification_deliveries_pending_idx ON notification_deliveries(next_attempt_at,created_at) WHERE status IN ('pending','failed');
|
|
|
|
CREATE TABLE risk_rules (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
|
|
event_type text NOT NULL CHECK (event_type IN ('concurrency','multi_region','device_churn','auth_failures','manual')),
|
|
window_seconds integer NOT NULL CHECK (window_seconds BETWEEN 60 AND 2592000),
|
|
threshold integer NOT NULL CHECK (threshold > 0),
|
|
action text NOT NULL DEFAULT 'review' CHECK (action IN ('observe','review','revoke_sessions','disable_user')),
|
|
enabled boolean NOT NULL DEFAULT true,
|
|
created_by uuid NOT NULL REFERENCES users(id),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE risk_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
rule_id uuid REFERENCES risk_rules(id) ON DELETE SET NULL,
|
|
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
event_type text NOT NULL,
|
|
severity text NOT NULL CHECK (severity IN ('info','low','medium','high','critical')),
|
|
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','reviewing','resolved','dismissed','appealed')),
|
|
evidence jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(evidence)='object'),
|
|
resolution text NOT NULL DEFAULT '',
|
|
resolved_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
resolved_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX risk_events_status_idx ON risk_events(status,severity,created_at DESC);
|
|
|
|
ALTER TABLE refresh_token_families
|
|
ADD COLUMN device_name text NOT NULL DEFAULT '',
|
|
ADD COLUMN client_name text NOT NULL DEFAULT '',
|
|
ADD COLUMN platform text NOT NULL DEFAULT '',
|
|
ADD COLUMN ip_hash text NOT NULL DEFAULT '',
|
|
ADD COLUMN last_seen_at timestamptz NOT NULL DEFAULT now();
|
|
|
|
ALTER TABLE devices
|
|
ADD COLUMN device_name text NOT NULL DEFAULT '',
|
|
ADD COLUMN client_name text NOT NULL DEFAULT '',
|
|
ADD COLUMN platform text NOT NULL DEFAULT '',
|
|
ADD COLUMN client_version text NOT NULL DEFAULT '',
|
|
ADD COLUMN last_ip_hash text NOT NULL DEFAULT '',
|
|
ADD COLUMN trusted boolean NOT NULL DEFAULT false,
|
|
ADD COLUMN blocked_at timestamptz,
|
|
ADD COLUMN blocked_by uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
|
|
CREATE TABLE configuration_versions (
|
|
revision bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
domain text NOT NULL CHECK (domain IN ('s','e')),
|
|
values jsonb NOT NULL CHECK (jsonb_typeof(values)='object'),
|
|
state text NOT NULL DEFAULT 'draft' CHECK (state IN ('draft','published','superseded','failed')),
|
|
source_revision bigint REFERENCES configuration_versions(revision),
|
|
created_by uuid NOT NULL REFERENCES users(id),
|
|
published_at timestamptz,
|
|
failure_reason text NOT NULL DEFAULT '',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE UNIQUE INDEX configuration_versions_published_domain_idx ON configuration_versions(domain) WHERE state='published';
|
|
|
|
CREATE TABLE operation_jobs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
job_type text NOT NULL,
|
|
resource_type text NOT NULL DEFAULT '',
|
|
resource_id text NOT NULL DEFAULT '',
|
|
idempotency_key text NOT NULL UNIQUE,
|
|
state text NOT NULL DEFAULT 'queued' CHECK (state IN ('queued','running','pausing','paused','cancelling','cancelled','succeeded','partial_failed','failed')),
|
|
progress_total bigint NOT NULL DEFAULT 0 CHECK (progress_total >= 0),
|
|
progress_completed bigint NOT NULL DEFAULT 0 CHECK (progress_completed >= 0),
|
|
progress_failed bigint NOT NULL DEFAULT 0 CHECK (progress_failed >= 0),
|
|
error_code text NOT NULL DEFAULT '',
|
|
error_summary text NOT NULL DEFAULT '',
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
started_at timestamptz,
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
finished_at timestamptz,
|
|
CHECK (progress_completed + progress_failed <= progress_total OR progress_total=0)
|
|
);
|
|
CREATE INDEX operation_jobs_state_idx ON operation_jobs(state,created_at DESC);
|
|
|
|
CREATE TABLE api_idempotency_keys (
|
|
actor_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
operation text NOT NULL,
|
|
idempotency_key text NOT NULL,
|
|
resource_id text NOT NULL DEFAULT '',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
expires_at timestamptz NOT NULL DEFAULT now() + interval '24 hours',
|
|
PRIMARY KEY (actor_user_id,operation,idempotency_key)
|
|
);
|
|
CREATE INDEX api_idempotency_keys_expiry_idx ON api_idempotency_keys(expires_at);
|