82 lines
5.2 KiB
SQL
82 lines
5.2 KiB
SQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TABLE users (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
username text NOT NULL,
|
|
username_normalized text NOT NULL UNIQUE,
|
|
email text NOT NULL,
|
|
email_normalized text NOT NULL UNIQUE,
|
|
role text NOT NULL DEFAULT 'user' CHECK (role IN ('user','admin')),
|
|
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','deleted')),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
CREATE TABLE credentials (
|
|
user_id uuid PRIMARY KEY REFERENCES users(id),
|
|
password_hash text NOT NULL,
|
|
password_changed_at timestamptz NOT NULL DEFAULT now(),
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE TABLE memberships (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id),
|
|
status text NOT NULL CHECK (status IN ('active','expired','revoked')),
|
|
starts_at timestamptz NOT NULL, ends_at timestamptz NOT NULL,
|
|
source text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(),
|
|
CHECK (ends_at > starts_at)
|
|
);
|
|
CREATE INDEX memberships_active_user_idx ON memberships(user_id, ends_at DESC) WHERE status = 'active';
|
|
CREATE TABLE refresh_token_families (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id),
|
|
revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE TABLE refresh_tokens (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), family_id uuid NOT NULL REFERENCES refresh_token_families(id),
|
|
token_hash bytea NOT NULL UNIQUE, expires_at timestamptz NOT NULL, consumed_at timestamptz,
|
|
replaced_by uuid, created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX refresh_tokens_family_idx ON refresh_tokens(family_id);
|
|
CREATE TABLE card_batches (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, card_type text NOT NULL CHECK (card_type IN ('registration','renewal')),
|
|
duration_days integer NOT NULL CHECK (duration_days > 0), created_by uuid REFERENCES users(id),
|
|
revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE TABLE cards (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), batch_id uuid NOT NULL REFERENCES card_batches(id),
|
|
code_hash bytea NOT NULL UNIQUE, remaining_uses integer NOT NULL DEFAULT 1 CHECK (remaining_uses >= 0),
|
|
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','exhausted')),
|
|
expires_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE TABLE card_usages (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), card_id uuid NOT NULL REFERENCES cards(id), user_id uuid NOT NULL REFERENCES users(id),
|
|
used_at timestamptz NOT NULL DEFAULT now(), UNIQUE(card_id, user_id)
|
|
);
|
|
CREATE INDEX card_usages_user_idx ON card_usages(user_id, used_at DESC);
|
|
CREATE TABLE audit_events (
|
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, actor_user_id uuid REFERENCES users(id), action text NOT NULL,
|
|
target_type text NOT NULL, target_id text NOT NULL, detail jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX audit_events_target_idx ON audit_events(target_type, target_id, created_at DESC);
|
|
CREATE TABLE policy_rules (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), subject_type text NOT NULL CHECK (subject_type IN ('global','membership','user')),
|
|
subject_id uuid, priority integer NOT NULL DEFAULT 0, enabled boolean NOT NULL DEFAULT true,
|
|
max_devices integer, max_online integer, max_playbacks integer, max_device_playbacks integer, max_bitrate_kbps integer,
|
|
downloads_allowed boolean, proxy_allowed boolean, transcode_allowed boolean, library_id text,
|
|
allowed_cidrs text[] NOT NULL DEFAULT '{}', countries text[] NOT NULL DEFAULT '{}', regions text[] NOT NULL DEFAULT '{}',
|
|
asns bigint[] NOT NULL DEFAULT '{}', carriers text[] NOT NULL DEFAULT '{}', route_id text, created_at timestamptz NOT NULL DEFAULT now(),
|
|
CHECK ((subject_type = 'global' AND subject_id IS NULL) OR (subject_type <> 'global' AND subject_id IS NOT NULL))
|
|
);
|
|
CREATE INDEX policy_rules_subject_idx ON policy_rules(subject_type, subject_id, priority DESC) WHERE enabled;
|
|
CREATE TABLE devices (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id), fingerprint_hash bytea NOT NULL,
|
|
first_seen_at timestamptz NOT NULL DEFAULT now(), last_seen_at timestamptz NOT NULL DEFAULT now(), revoked_at timestamptz,
|
|
UNIQUE(user_id, fingerprint_hash)
|
|
);
|
|
CREATE TABLE playback_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id), device_id uuid REFERENCES devices(id),
|
|
e_session_id text NOT NULL UNIQUE, library_id text NOT NULL, route_id text, state text NOT NULL CHECK (state IN ('reserved','active','released','expired')),
|
|
lease_token_hash bytea NOT NULL UNIQUE, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), released_at timestamptz
|
|
);
|
|
CREATE INDEX playback_sessions_active_user_idx ON playback_sessions(user_id, expires_at) WHERE state IN ('reserved','active');
|
|
CREATE INDEX playback_sessions_active_device_idx ON playback_sessions(device_id, expires_at) WHERE state IN ('reserved','active');
|