chore: keep migrations inside deployment archive

This commit is contained in:
root committed 2026-09-05 14:09:45 +00:00
1 parent e55cc3913f
commit a047ffc040
114 files changed
-3288

No files matched your search

-81
View File
@@ -1,81 +0,0 @@
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');
-64
View File
@@ -1,64 +0,0 @@
CREATE TABLE e_command_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), actor_user_id uuid NOT NULL REFERENCES users(id),
command text NOT NULL, target_id text NOT NULL, idempotency_key text NOT NULL UNIQUE,
state text NOT NULL CHECK (state IN ('accepted','completed','failed')), created_at timestamptz NOT NULL DEFAULT now(), completed_at timestamptz
);
CREATE INDEX e_command_requests_actor_idx ON e_command_requests(actor_user_id, created_at DESC);
CREATE TABLE e_event_inbox (
event_id uuid PRIMARY KEY, event_type text NOT NULL, occurred_at timestamptz NOT NULL,
payload jsonb NOT NULL, received_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE invite_codes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), creator_user_id uuid NOT NULL REFERENCES users(id),
code_hash bytea NOT NULL UNIQUE, max_uses integer NOT NULL DEFAULT 1 CHECK (max_uses > 0), used_count integer NOT NULL DEFAULT 0 CHECK (used_count >= 0),
expires_at timestamptz, revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE invite_relations (
parent_user_id uuid NOT NULL REFERENCES users(id), child_user_id uuid NOT NULL REFERENCES users(id), invite_code_id uuid NOT NULL REFERENCES invite_codes(id),
risk_state text NOT NULL DEFAULT 'normal' CHECK (risk_state IN ('normal','review','blocked')), detached_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY(parent_user_id, child_user_id), CHECK (parent_user_id <> child_user_id)
);
CREATE UNIQUE INDEX invite_relations_child_active_idx ON invite_relations(child_user_id) WHERE detached_at IS NULL;
CREATE TABLE media_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id), query_text text NOT NULL,
e_media_id uuid, availability text NOT NULL CHECK (availability IN ('unknown','available','unavailable')), status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','planned','fulfilled','rejected','cancelled')),
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX media_requests_status_idx ON media_requests(status, created_at DESC);
CREATE TABLE ticket_types (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL UNIQUE, enabled boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE tickets (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), owner_user_id uuid NOT NULL REFERENCES users(id), type_id uuid REFERENCES ticket_types(id), subject text NOT NULL,
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','pending','resolved','closed')), priority text NOT NULL DEFAULT 'normal' CHECK (priority IN ('low','normal','high')),
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), closed_at timestamptz
);
CREATE INDEX tickets_owner_idx ON tickets(owner_user_id, updated_at DESC);
CREATE INDEX tickets_status_idx ON tickets(status, updated_at DESC);
CREATE TABLE ticket_replies (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), ticket_id uuid NOT NULL REFERENCES tickets(id), author_user_id uuid NOT NULL REFERENCES users(id), body text NOT NULL, created_at timestamptz NOT NULL DEFAULT now());
CREATE INDEX ticket_replies_ticket_idx ON ticket_replies(ticket_id, created_at);
CREATE TABLE ticket_assignments (ticket_id uuid PRIMARY KEY REFERENCES tickets(id), admin_user_id uuid NOT NULL REFERENCES users(id), assigned_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE telegram_accounts (user_id uuid PRIMARY KEY REFERENCES users(id), telegram_user_id bigint NOT NULL UNIQUE, username text, bound_at timestamptz NOT NULL DEFAULT now(), disabled_at timestamptz);
CREATE TABLE telegram_bind_codes (code_hash bytea PRIMARY KEY, user_id uuid NOT NULL REFERENCES users(id), expires_at timestamptz NOT NULL, consumed_at timestamptz, created_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE license_products (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, edition text NOT NULL, max_s_instances integer NOT NULL CHECK (max_s_instances > 0),
max_e_nodes integer NOT NULL CHECK (max_e_nodes >= 0), feature_flags text[] NOT NULL DEFAULT '{}', valid_days integer NOT NULL CHECK (valid_days > 0), offline_days integer NOT NULL DEFAULT 60 CHECK (offline_days BETWEEN 0 AND 60), created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE license_activations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), product_id uuid NOT NULL REFERENCES license_products(id), activation_hash bytea NOT NULL UNIQUE,
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','exhausted')), max_instances integer NOT NULL DEFAULT 1 CHECK (max_instances > 0), created_at timestamptz NOT NULL DEFAULT now(), revoked_at timestamptz
);
CREATE TABLE license_instances (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), activation_id uuid NOT NULL REFERENCES license_activations(id), instance_id uuid NOT NULL,
fingerprint_hash bytea NOT NULL, signed_license text NOT NULL, expires_at timestamptz NOT NULL, offline_until timestamptz NOT NULL, last_seen_at timestamptz NOT NULL DEFAULT now(), unbound_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(activation_id, instance_id)
);
CREATE INDEX license_instances_active_idx ON license_instances(activation_id) WHERE unbound_at IS NULL;
CREATE TABLE outbox_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), topic text NOT NULL, aggregate_type text NOT NULL, aggregate_id text NOT NULL,
payload jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), published_at timestamptz, attempts integer NOT NULL DEFAULT 0, last_error text
, locked_by text, locked_until timestamptz
);
CREATE INDEX outbox_events_pending_idx ON outbox_events(created_at) WHERE published_at IS NULL;
CREATE TABLE runtime_config (key text PRIMARY KEY, value jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), updated_by uuid REFERENCES users(id));
-21
View File
@@ -1,21 +0,0 @@
ALTER TABLE users
ADD COLUMN ban_reason text,
ADD COLUMN banned_until timestamptz;
CREATE TABLE user_permissions (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
permission text NOT NULL CHECK (permission IN (
'users.read',
'users.write',
'cards.manage',
'operations.manage',
'e.manage',
'audit.read',
'licenses.manage'
)),
granted_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, permission)
);
CREATE INDEX user_permissions_permission_idx ON user_permissions(permission, user_id);
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE users
ADD COLUMN auth_version bigint NOT NULL DEFAULT 1 CHECK (auth_version > 0);
-6
View File
@@ -1,6 +0,0 @@
CREATE TABLE internal_auth_nonces (
nonce_hash bytea PRIMARY KEY,
expires_at timestamptz NOT NULL
);
CREATE INDEX internal_auth_nonces_expiry_idx ON internal_auth_nonces(expires_at);
@@ -1,22 +0,0 @@
CREATE TABLE playback_decisions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
media_id uuid NOT NULL,
library_id uuid NOT NULL,
source_id uuid NOT NULL,
device_id text NOT NULL,
route_id text,
allowed boolean NOT NULL,
deny_code text,
max_bitrate bigint NOT NULL DEFAULT 0 CHECK (max_bitrate >= 0),
allow_download boolean NOT NULL DEFAULT false,
max_playbacks integer NOT NULL DEFAULT 0 CHECK (max_playbacks >= 0),
max_device_playbacks integer NOT NULL DEFAULT 0 CHECK (max_device_playbacks >= 0),
policy_revision bigint NOT NULL CHECK (policy_revision >= 0),
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX playback_decisions_expiry_idx ON playback_decisions(expires_at);
CREATE INDEX playback_decisions_user_idx ON playback_decisions(user_id, created_at DESC);
-16
View File
@@ -1,16 +0,0 @@
CREATE TABLE commercial_license_cache (
instance_id text PRIMARY KEY,
license_id text NOT NULL,
signed_license text NOT NULL,
edition text NOT NULL,
features text[] NOT NULL DEFAULT '{}',
max_s_instances integer NOT NULL CHECK (max_s_instances > 0),
max_e_nodes integer NOT NULL CHECK (max_e_nodes > 0),
expires_at timestamptz NOT NULL,
offline_until timestamptz NOT NULL,
status text NOT NULL CHECK (status IN ('active','revoked','expired')),
last_verified_at timestamptz NOT NULL DEFAULT now(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX commercial_license_cache_status_expiry_idx ON commercial_license_cache(status,expires_at);
-86
View File
@@ -1,86 +0,0 @@
CREATE TABLE gateway_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
public_origin text NOT NULL DEFAULT '',
trusted_proxy_cidrs text[] NOT NULL DEFAULT '{}',
active_revision bigint,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO gateway_settings(singleton) VALUES (true);
CREATE TABLE gateway_service_pools (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
code text NOT NULL UNIQUE CHECK (code ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
enabled boolean NOT NULL DEFAULT true,
is_default boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX gateway_service_pools_one_default_idx ON gateway_service_pools(is_default) WHERE is_default;
INSERT INTO gateway_service_pools(code,name,is_default) VALUES ('default','Default',true);
CREATE TABLE gateway_nodes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
node_id text NOT NULL UNIQUE CHECK (node_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
gateway_url text NOT NULL CHECK (length(gateway_url) BETWEEN 8 AND 2048),
pool_id uuid NOT NULL REFERENCES gateway_service_pools(id),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
city text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
weight integer NOT NULL DEFAULT 100 CHECK (weight BETWEEN 1 AND 10000),
max_connections bigint NOT NULL DEFAULT 0 CHECK (max_connections >= 0),
max_playback_sessions bigint NOT NULL DEFAULT 0 CHECK (max_playback_sessions >= 0),
max_transcode_sessions bigint NOT NULL DEFAULT 0 CHECK (max_transcode_sessions >= 0),
bandwidth_capacity_mbps bigint NOT NULL DEFAULT 0 CHECK (bandwidth_capacity_mbps >= 0),
library_ids text[] NOT NULL DEFAULT '{}',
storage_ids text[] NOT NULL DEFAULT '{}',
enabled boolean NOT NULL DEFAULT true,
allow_login boolean NOT NULL DEFAULT true,
allow_new_playback boolean NOT NULL DEFAULT true,
maintenance boolean NOT NULL DEFAULT false,
draining boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_nodes_pool_idx ON gateway_nodes(pool_id,enabled,node_id);
CREATE INDEX gateway_nodes_route_idx ON gateway_nodes(enabled,maintenance,draining,country,region,carrier,asn);
CREATE TABLE gateway_user_routes (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
forced_node_id text REFERENCES gateway_nodes(node_id) ON UPDATE CASCADE,
forced_pool_id uuid REFERENCES gateway_service_pools(id),
preferred_country text NOT NULL DEFAULT '',
preferred_region text NOT NULL DEFAULT '',
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE gateway_config_versions (
revision bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
snapshot jsonb NOT NULL CHECK (jsonb_typeof(snapshot)='object'),
source_revision bigint REFERENCES gateway_config_versions(revision),
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_config_versions_created_idx ON gateway_config_versions(created_at DESC);
ALTER TABLE gateway_settings ADD CONSTRAINT gateway_settings_active_revision_fk FOREIGN KEY(active_revision) REFERENCES gateway_config_versions(revision);
CREATE TABLE gateway_route_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
request_id text NOT NULL,
node_id text,
outcome text NOT NULL CHECK (outcome IN ('routed','rejected','upstream_failed')),
reason text NOT NULL DEFAULT '',
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0,
latency_ms integer NOT NULL DEFAULT 0 CHECK (latency_ms >= 0),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_route_events_recent_idx ON gateway_route_events(created_at DESC);
CREATE INDEX gateway_route_events_node_idx ON gateway_route_events(node_id,created_at DESC);
-19
View File
@@ -1,19 +0,0 @@
ALTER TABLE gateway_settings
ADD COLUMN routing_weights jsonb NOT NULL DEFAULT '{"country":1000,"region":100,"city":20,"carrier":10,"asn":10}'::jsonb,
ADD COLUMN recovery_ramp_seconds integer NOT NULL DEFAULT 300 CHECK (recovery_ramp_seconds BETWEEN 0 AND 3600),
ADD CONSTRAINT gateway_settings_routing_weights_object CHECK (jsonb_typeof(routing_weights) = 'object');
ALTER TABLE gateway_route_events
ADD COLUMN username text NOT NULL DEFAULT '',
ADD COLUMN device_hash text NOT NULL DEFAULT '',
ADD COLUMN resource_id text NOT NULL DEFAULT '',
ADD COLUMN path_class text NOT NULL DEFAULT 'other'
CHECK (path_class IN ('login','browse','image','playback','transcode','websocket','other')),
ADD COLUMN selection_source text NOT NULL DEFAULT '';
CREATE INDEX gateway_route_events_outcome_created_idx
ON gateway_route_events(outcome,created_at DESC);
CREATE INDEX gateway_route_events_location_created_idx
ON gateway_route_events(country,region,carrier,created_at DESC);
CREATE INDEX gateway_route_events_username_created_idx
ON gateway_route_events(username,created_at DESC) WHERE username <> '';
-10
View File
@@ -1,10 +0,0 @@
ALTER TABLE commercial_license_cache
ADD COLUMN customer_id text NOT NULL DEFAULT '',
ADD COLUMN product text NOT NULL DEFAULT 'overcooked',
ADD COLUMN max_users integer NOT NULL DEFAULT 0 CHECK (max_users >= 0),
ADD COLUMN max_service_pools integer NOT NULL DEFAULT 0 CHECK (max_service_pools >= 0),
ADD COLUMN not_before timestamptz NOT NULL DEFAULT now(),
ADD COLUMN maintenance_until timestamptz;
CREATE INDEX commercial_license_cache_product_status_idx
ON commercial_license_cache(product,status,offline_until);
-75
View File
@@ -1,75 +0,0 @@
CREATE TABLE e_address_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
trusted_proxy_cidrs text[] NOT NULL DEFAULT '{}',
default_address_id uuid,
rule_revision bigint NOT NULL DEFAULT 1,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO e_address_settings(singleton) VALUES (true);
CREATE TABLE e_addresses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
public_url text NOT NULL CHECK (public_url ~ '^https?://[^/?#[:space:]]+'),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
city text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
node_group text NOT NULL DEFAULT '',
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN -100000 AND 100000),
enabled boolean NOT NULL DEFAULT true,
healthy boolean NOT NULL DEFAULT false,
license_valid boolean NOT NULL DEFAULT false,
allow_user_publish boolean NOT NULL DEFAULT true,
maintenance boolean NOT NULL DEFAULT false,
description text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX e_addresses_public_url_idx ON e_addresses(public_url);
CREATE INDEX e_addresses_selection_idx ON e_addresses(enabled,healthy,license_valid,allow_user_publish,maintenance,priority);
ALTER TABLE e_address_settings
ADD CONSTRAINT e_address_settings_default_fk FOREIGN KEY(default_address_id) REFERENCES e_addresses(id) ON DELETE SET NULL;
CREATE TABLE e_address_rules (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN -100000 AND 100000),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
node_group text NOT NULL DEFAULT '',
address_id uuid NOT NULL REFERENCES e_addresses(id) ON DELETE CASCADE,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_address_rules_match_idx ON e_address_rules(enabled,priority,country,region,carrier,asn,node_group);
CREATE TABLE e_user_assignments (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
address_id uuid NOT NULL REFERENCES e_addresses(id),
fixed boolean NOT NULL DEFAULT false,
rule_revision bigint NOT NULL DEFAULT 1,
reason text NOT NULL DEFAULT '',
source_ip_hash text NOT NULL DEFAULT '',
assigned_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_user_assignments_expiry_idx ON e_user_assignments(expires_at);
CREATE TABLE e_assignment_history (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
address_id uuid REFERENCES e_addresses(id) ON DELETE SET NULL,
source_ip_hash text NOT NULL DEFAULT '',
reason text NOT NULL DEFAULT '',
rule_revision bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_assignment_history_user_idx ON e_assignment_history(user_id,created_at DESC);
-48
View File
@@ -1,48 +0,0 @@
INSERT INTO e_addresses (
id, name, public_url, country, region, city, carrier, asn, node_group,
priority, enabled, healthy, license_valid, allow_user_publish,
maintenance, description, created_at, updated_at
)
SELECT
node.id,
node.name,
rtrim(node.gateway_url, '/'),
node.country,
node.region,
node.city,
node.carrier,
node.asn,
pool.code,
node.weight,
node.enabled,
false,
false,
false,
node.maintenance OR node.draining,
'Migrated from retired proxy node ' || node.node_id,
node.created_at,
node.updated_at
FROM gateway_nodes AS node
JOIN gateway_service_pools AS pool ON pool.id = node.pool_id
WHERE node.gateway_url ~ '^https?://[^/?#[:space:]]+'
ON CONFLICT (public_url) DO UPDATE SET
name = EXCLUDED.name,
country = EXCLUDED.country,
region = EXCLUDED.region,
city = EXCLUDED.city,
carrier = EXCLUDED.carrier,
asn = EXCLUDED.asn,
node_group = EXCLUDED.node_group,
priority = EXCLUDED.priority,
description = CASE
WHEN e_addresses.description = '' THEN EXCLUDED.description
ELSE e_addresses.description
END,
updated_at = GREATEST(e_addresses.updated_at, EXCLUDED.updated_at);
DROP TABLE gateway_route_events;
DROP TABLE gateway_settings;
DROP TABLE gateway_config_versions;
DROP TABLE gateway_user_routes;
DROP TABLE gateway_nodes;
DROP TABLE gateway_service_pools;
-22
View File
@@ -1,22 +0,0 @@
ALTER TABLE e_address_rules
ADD COLUMN match_count bigint NOT NULL DEFAULT 0 CHECK (match_count >= 0),
ADD COLUMN last_matched_at timestamptz;
CREATE TABLE e_address_selection_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
address_id uuid REFERENCES e_addresses(id) ON DELETE SET NULL,
rule_id uuid REFERENCES e_address_rules(id) ON DELETE SET NULL,
outcome text NOT NULL CHECK (outcome IN ('selected','unavailable','access_denied')),
reason text NOT NULL DEFAULT '',
source_ip_hash text NOT NULL DEFAULT '',
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_address_selection_events_created_idx ON e_address_selection_events(created_at DESC);
CREATE INDEX e_address_selection_events_outcome_idx ON e_address_selection_events(outcome,created_at DESC);
CREATE INDEX e_address_selection_events_user_idx ON e_address_selection_events(user_id,created_at DESC);
-218
View File
@@ -1,218 +0,0 @@
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);
@@ -1,2 +0,0 @@
ALTER TABLE e_address_selection_events DROP CONSTRAINT e_address_selection_events_outcome_check;
ALTER TABLE e_address_selection_events ADD CONSTRAINT e_address_selection_events_outcome_check CHECK (outcome IN ('selected','unavailable','access_denied','rate_limited'));
-90
View File
@@ -1,90 +0,0 @@
ALTER TABLE audit_events
ADD COLUMN request_id text NOT NULL DEFAULT '';
CREATE INDEX audit_events_request_idx ON audit_events(request_id, created_at DESC) WHERE request_id <> '';
CREATE TABLE runtime_log_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
level text NOT NULL CHECK (level IN ('debug','info','warn','error')),
message text NOT NULL CHECK (length(message) BETWEEN 1 AND 256),
request_id text NOT NULL DEFAULT '',
method text NOT NULL DEFAULT '',
path text NOT NULL DEFAULT '',
status_code integer NOT NULL DEFAULT 0 CHECK (status_code BETWEEN 0 AND 599),
duration_ms bigint NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
detail jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(detail)='object'),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX runtime_log_events_time_idx ON runtime_log_events(created_at DESC);
CREATE INDEX runtime_log_events_request_idx ON runtime_log_events(request_id,created_at DESC) WHERE request_id <> '';
ALTER TABLE operation_jobs
ADD COLUMN schema_version integer NOT NULL DEFAULT 1,
ADD COLUMN stage text NOT NULL DEFAULT 'queued',
ADD COLUMN priority integer NOT NULL DEFAULT 0 CHECK (priority BETWEEN -100 AND 100),
ADD COLUMN progress_skipped bigint NOT NULL DEFAULT 0 CHECK (progress_skipped >= 0),
ADD COLUMN retryable boolean NOT NULL DEFAULT true,
ADD COLUMN next_retry_at timestamptz,
ADD COLUMN claimed_by text NOT NULL DEFAULT '',
ADD COLUMN payload jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(payload)='object');
CREATE TABLE backup_policies (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL CHECK (scope IN ('s','e','auth','all')),
enabled boolean NOT NULL DEFAULT true,
interval_hours integer NOT NULL DEFAULT 24 CHECK (interval_hours BETWEEN 1 AND 720),
retention_count integer NOT NULL DEFAULT 14 CHECK (retention_count BETWEEN 1 AND 365),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(scope)
);
CREATE TABLE backup_artifacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL CHECK (scope IN ('s','e','auth','all')),
state text NOT NULL DEFAULT 'queued' CHECK (state IN ('queued','running','succeeded','failed','expired','deleted')),
storage_key text NOT NULL,
size_bytes bigint NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
sha256 text NOT NULL DEFAULT '' CHECK (sha256 = '' OR sha256 ~ '^[0-9a-f]{64}$'),
job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
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(),
completed_at timestamptz,
deleted_at timestamptz
);
CREATE INDEX backup_artifacts_scope_idx ON backup_artifacts(scope,created_at DESC);
CREATE TABLE system_runtime_state (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
maintenance boolean NOT NULL DEFAULT false,
maintenance_reason text NOT NULL DEFAULT '',
maintenance_job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO system_runtime_state(singleton) VALUES (true) ON CONFLICT DO NOTHING;
CREATE TABLE update_artifacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product text NOT NULL CHECK (product IN ('s','e','auth')),
platform text NOT NULL,
version text NOT NULL,
channel text NOT NULL CHECK (channel IN ('stable','preview')),
download_url text NOT NULL DEFAULT '',
sha256 text NOT NULL CHECK (sha256 ~ '^[0-9a-f]{64}$'),
signature text NOT NULL DEFAULT '',
min_s_version text NOT NULL DEFAULT '',
min_e_version text NOT NULL DEFAULT '',
state text NOT NULL DEFAULT 'authorized' CHECK (state IN ('authorized','downloaded','verified','staged','applied','rolled_back','failed')),
storage_key text NOT NULL DEFAULT '',
job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
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(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(product,platform,version,channel)
);
CREATE INDEX update_artifacts_state_idx ON update_artifacts(product,state,updated_at DESC);
@@ -1,124 +0,0 @@
ALTER TABLE users
ADD COLUMN deleted_by uuid REFERENCES users(id) ON DELETE SET NULL,
ADD COLUMN deletion_reason text NOT NULL DEFAULT '',
ADD COLUMN restored_at timestamptz;
ALTER TABLE card_batches
ADD COLUMN membership_plan_id uuid REFERENCES membership_plans(id) ON DELETE SET NULL,
ADD COLUMN edition text NOT NULL DEFAULT '' CHECK (char_length(edition) <= 64),
ADD COLUMN max_uses integer NOT NULL DEFAULT 1 CHECK (max_uses > 0);
ALTER TABLE card_batches DROP CONSTRAINT card_batches_card_type_check;
ALTER TABLE card_batches ADD CONSTRAINT card_batches_card_type_check CHECK (card_type IN ('registration','renewal','membership'));
ALTER TABLE cards
ADD COLUMN label text NOT NULL DEFAULT '' CHECK (char_length(label) <= 128);
CREATE TABLE notification_channel_configs (
channel text PRIMARY KEY CHECK (channel IN ('smtp','telegram')),
enabled boolean NOT NULL DEFAULT false,
settings jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(settings)='object'),
secret_cipher bytea,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO notification_channel_configs(channel) VALUES ('smtp'),('telegram');
CREATE TABLE notification_templates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
template_key text NOT NULL CHECK (template_key ~ '^[a-z0-9][a-z0-9._-]{0,127}$'),
channel text NOT NULL CHECK (channel IN ('email','telegram')),
subject text NOT NULL DEFAULT '' CHECK (char_length(subject) <= 300),
body text NOT NULL CHECK (char_length(body) BETWEEN 1 AND 20000),
enabled boolean NOT NULL DEFAULT true,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(template_key,channel)
);
CREATE TABLE notification_delivery_attempts (
id bigserial PRIMARY KEY,
delivery_id uuid NOT NULL REFERENCES notification_deliveries(id) ON DELETE CASCADE,
attempt integer NOT NULL CHECK (attempt > 0),
status text NOT NULL CHECK (status IN ('sent','failed','skipped')),
error_code text NOT NULL DEFAULT '',
error_summary text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(delivery_id,attempt)
);
CREATE TABLE telegram_runtime_state (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
update_offset bigint NOT NULL DEFAULT 0,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO telegram_runtime_state(singleton) VALUES(true);
CREATE TABLE user_ip_history (
id bigserial PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ip_hash text NOT NULL CHECK (char_length(ip_hash)=64),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint,
first_seen_at timestamptz NOT NULL DEFAULT now(),
last_seen_at timestamptz NOT NULL DEFAULT now(),
seen_count bigint NOT NULL DEFAULT 1 CHECK (seen_count > 0),
UNIQUE(user_id,ip_hash)
);
CREATE INDEX user_ip_history_user_time_idx ON user_ip_history(user_id,last_seen_at DESC);
CREATE TABLE security_list_entries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
subject_type text NOT NULL CHECK (subject_type IN ('ip','cidr','device','user')),
mode text NOT NULL CHECK (mode IN ('allow','deny')),
subject_value text NOT NULL CHECK (char_length(subject_value) BETWEEN 1 AND 512),
reason text NOT NULL CHECK (char_length(btrim(reason)) BETWEEN 1 AND 1000),
expires_at timestamptz,
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz,
revoked_by uuid REFERENCES users(id) ON DELETE SET NULL,
UNIQUE(subject_type,mode,subject_value)
);
CREATE INDEX security_list_active_idx ON security_list_entries(subject_type,mode,expires_at) WHERE revoked_at IS NULL;
CREATE TABLE user_appeals (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
risk_event_id uuid REFERENCES risk_events(id) ON DELETE SET NULL,
body text NOT NULL CHECK (char_length(btrim(body)) BETWEEN 1 AND 5000),
status text NOT NULL DEFAULT 'submitted' CHECK (status IN ('submitted','reviewing','accepted','rejected')),
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 TABLE media_request_callbacks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
callback_url text NOT NULL CHECK (callback_url ~ '^https://'),
secret_cipher bytea NOT NULL,
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 media_request_callback_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
callback_id uuid NOT NULL REFERENCES media_request_callbacks(id) ON DELETE CASCADE,
external_event_id text NOT NULL CHECK (char_length(external_event_id) BETWEEN 1 AND 256),
request_id uuid REFERENCES media_requests(id) ON DELETE SET NULL,
payload jsonb NOT NULL CHECK (jsonb_typeof(payload)='object'),
signature_valid boolean NOT NULL,
applied boolean NOT NULL DEFAULT false,
error_summary text NOT NULL DEFAULT '',
received_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(callback_id,external_event_id)
);
-9
View File
@@ -1,9 +0,0 @@
CREATE TABLE system_installation (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
installation_id uuid NOT NULL DEFAULT gen_random_uuid(),
setup_completed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO system_installation(singleton) VALUES(true);
-6
View File
@@ -1,6 +0,0 @@
ALTER TABLE e_address_rules
ALTER COLUMN address_id DROP NOT NULL;
ALTER TABLE e_address_rules
ADD CONSTRAINT e_address_rules_target_check
CHECK (address_id IS NOT NULL OR length(btrim(node_group)) > 0);
-17
View File
@@ -1,17 +0,0 @@
ALTER TABLE playback_decisions
ADD COLUMN device_name text NOT NULL DEFAULT '',
ADD COLUMN client_name text NOT NULL DEFAULT '',
ADD COLUMN client_version text NOT NULL DEFAULT '',
ADD COLUMN remote_ip_hash text NOT NULL DEFAULT '';
ALTER TABLE playback_sessions
ADD COLUMN media_id uuid,
ADD COLUMN source_id uuid,
ADD COLUMN node_id uuid,
ADD COLUMN position_ticks bigint NOT NULL DEFAULT 0 CHECK (position_ticks >= 0),
ADD COLUMN device_name text NOT NULL DEFAULT '',
ADD COLUMN client_name text NOT NULL DEFAULT '',
ADD COLUMN client_version text NOT NULL DEFAULT '';
CREATE INDEX playback_sessions_recent_idx
ON playback_sessions(updated_at DESC, id DESC);
-55
View File
@@ -1,55 +0,0 @@
CREATE TABLE subtitle_sources (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
source_type text NOT NULL DEFAULT 'json_rest_v1' CHECK (source_type IN ('json_rest_v1')),
base_url text NOT NULL CHECK (base_url ~ '^https://'),
api_key_cipher bytea,
enabled boolean NOT NULL DEFAULT true,
languages text[] NOT NULL DEFAULT '{}',
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN 1 AND 10000),
timeout_seconds integer NOT NULL DEFAULT 10 CHECK (timeout_seconds BETWEEN 2 AND 30),
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(),
UNIQUE (name)
);
CREATE INDEX subtitle_sources_enabled_idx
ON subtitle_sources(priority,id) WHERE enabled;
CREATE TABLE subtitle_search_candidates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source_id uuid NOT NULL REFERENCES subtitle_sources(id) ON DELETE CASCADE,
media_item_id uuid NOT NULL,
media_source_id uuid NOT NULL,
external_id text NOT NULL CHECK (char_length(external_id) BETWEEN 1 AND 512),
title text NOT NULL CHECK (char_length(title) BETWEEN 1 AND 512),
language text NOT NULL DEFAULT '' CHECK (char_length(language) <= 64),
codec text NOT NULL CHECK (codec IN ('srt','ass','ssa','vtt','sup','sub','idx')),
release_group text NOT NULL DEFAULT '' CHECK (char_length(release_group) <= 256),
score double precision NOT NULL DEFAULT 0 CHECK (score >= 0 AND score <= 1),
download_path text NOT NULL CHECK (char_length(download_path) BETWEEN 1 AND 2048),
hearing_impaired boolean NOT NULL DEFAULT false,
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL DEFAULT (now() + interval '1 hour')
);
CREATE INDEX subtitle_search_candidates_actor_idx
ON subtitle_search_candidates(created_by,created_at DESC);
CREATE INDEX subtitle_search_candidates_expiry_idx
ON subtitle_search_candidates(expires_at);
CREATE TABLE subtitle_imports (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
candidate_id uuid NOT NULL REFERENCES subtitle_search_candidates(id) ON DELETE CASCADE,
actor_user_id uuid NOT NULL REFERENCES users(id),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 16 AND 128),
state text NOT NULL DEFAULT 'processing' CHECK (state IN ('processing','succeeded','failed')),
result jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(result)='object'),
error_code text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(actor_user_id,idempotency_key)
);
@@ -1,10 +0,0 @@
CREATE TABLE metadata_provider_credentials (
provider text PRIMARY KEY CHECK (provider IN ('tmdb','bangumi')),
token_cipher bytea,
enabled boolean NOT NULL DEFAULT true,
updated_by uuid NOT NULL REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX metadata_provider_credentials_enabled_idx
ON metadata_provider_credentials(provider) WHERE enabled;
-80
View File
@@ -1,80 +0,0 @@
CREATE TABLE point_rules (
rule_key text PRIMARY KEY CHECK (rule_key ~ '^[a-z0-9][a-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
points integer NOT NULL CHECK (points BETWEEN -100000 AND 100000),
enabled boolean NOT NULL DEFAULT true,
description text NOT NULL DEFAULT '' CHECK (char_length(description) <= 1000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO point_rules(rule_key,name,points,description) VALUES
('playback.completed','有效观看',5,'单次观看达到 10 分钟后奖励,每个播放会话仅奖励一次'),
('feedback.accepted','有效反馈',10,'管理员确认媒体问题后奖励');
CREATE TABLE point_accounts (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
balance bigint NOT NULL DEFAULT 0 CHECK (balance >= 0),
lifetime_earned bigint NOT NULL DEFAULT 0 CHECK (lifetime_earned >= 0),
lifetime_spent bigint NOT NULL DEFAULT 0 CHECK (lifetime_spent >= 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE point_ledger (
id bigserial PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
delta integer NOT NULL CHECK (delta <> 0 AND delta BETWEEN -100000 AND 100000),
balance_after bigint NOT NULL CHECK (balance_after >= 0),
reason text NOT NULL CHECK (char_length(btrim(reason)) BETWEEN 1 AND 200),
source_type text NOT NULL DEFAULT 'manual' CHECK (source_type IN ('manual','playback_session','media_feedback','redemption')),
source_id text NOT NULL DEFAULT '' CHECK (char_length(source_id) <= 128),
actor_user_id uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(user_id,source_type,source_id)
);
CREATE INDEX point_ledger_user_time_idx ON point_ledger(user_id,created_at DESC,id DESC);
CREATE TABLE media_feedback (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
media_id uuid,
source_id uuid,
playback_session_id uuid REFERENCES playback_sessions(id) ON DELETE SET NULL,
category text NOT NULL CHECK (category IN ('playback','subtitle','audio','metadata','quality','other')),
title text NOT NULL CHECK (char_length(btrim(title)) BETWEEN 1 AND 200),
detail text NOT NULL CHECK (char_length(btrim(detail)) BETWEEN 1 AND 5000),
status text NOT NULL DEFAULT 'submitted' CHECK (status IN ('submitted','reviewing','resolved','rejected')),
priority text NOT NULL DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')),
resolution text NOT NULL DEFAULT '' CHECK (char_length(resolution) <= 5000),
assigned_to uuid REFERENCES users(id) ON DELETE SET NULL,
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 media_feedback_user_time_idx ON media_feedback(user_id,created_at DESC);
CREATE INDEX media_feedback_queue_idx ON media_feedback(status,priority,updated_at DESC);
CREATE TABLE telegram_bot_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
enabled boolean NOT NULL DEFAULT true,
commands_enabled boolean NOT NULL DEFAULT true,
rate_limit_per_minute integer NOT NULL DEFAULT 12 CHECK (rate_limit_per_minute BETWEEN 1 AND 120),
welcome_message text NOT NULL DEFAULT '欢迎使用站点助手,请先绑定账户。' CHECK (char_length(welcome_message) BETWEEN 1 AND 1000),
maintenance_message text NOT NULL DEFAULT '' CHECK (char_length(maintenance_message) <= 1000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO telegram_bot_settings(singleton) VALUES(true);
CREATE TABLE telegram_command_logs (
id bigserial PRIMARY KEY,
telegram_user_id bigint NOT NULL,
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
command text NOT NULL CHECK (char_length(command) BETWEEN 1 AND 64),
result text NOT NULL CHECK (char_length(result) BETWEEN 1 AND 64),
duration_ms integer NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX telegram_command_logs_rate_idx ON telegram_command_logs(telegram_user_id,created_at DESC);
CREATE INDEX telegram_command_logs_admin_idx ON telegram_command_logs(created_at DESC,id DESC);
-20
View File
@@ -1,20 +0,0 @@
ALTER TABLE point_ledger DROP CONSTRAINT point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check
CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin'));
INSERT INTO point_rules(rule_key,name,points,description) VALUES
('daily_checkin.base','每日签到',5,'每天首次签到获得的积分'),
('daily_checkin.streak_7','连续签到 7 天',10,'连续签到第 7 天的额外积分'),
('daily_checkin.streak_30','连续签到 30 天',30,'连续签到第 30 天的额外积分')
ON CONFLICT(rule_key) DO NOTHING;
CREATE TABLE daily_checkins (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
checkin_date date NOT NULL,
streak integer NOT NULL CHECK(streak > 0),
reward_points integer NOT NULL CHECK(reward_points >= 0),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(user_id,checkin_date)
);
CREATE INDEX daily_checkins_user_time_idx ON daily_checkins(user_id,checkin_date DESC);
@@ -1,34 +0,0 @@
ALTER TABLE membership_plans
ADD COLUMN price_points integer NOT NULL DEFAULT 0 CHECK(price_points >= 0),
ADD COLUMN self_service boolean NOT NULL DEFAULT false;
INSERT INTO membership_plans(code,name,duration_days,max_playbacks,max_devices,max_bitrate_kbps,price_points,self_service,enabled)
VALUES('points-30d','30 天会员',30,1,3,0,100,true,true)
ON CONFLICT(code) DO NOTHING;
ALTER TABLE point_ledger DROP CONSTRAINT point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check
CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin','membership_purchase'));
CREATE TABLE membership_purchases (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
plan_id uuid NOT NULL REFERENCES membership_plans(id),
price_points integer NOT NULL CHECK(price_points >= 0),
membership_id uuid REFERENCES memberships(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX membership_purchases_user_time_idx ON membership_purchases(user_id,created_at DESC);
CREATE TABLE watch_calendar_entries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title text NOT NULL CHECK(length(btrim(title)) BETWEEN 1 AND 200),
episode_label text NOT NULL DEFAULT '' CHECK(length(episode_label) <= 100),
next_episode_at timestamptz NOT NULL,
notes text NOT NULL DEFAULT '' CHECK(length(notes) <= 1000),
completed boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX watch_calendar_user_time_idx ON watch_calendar_entries(user_id,completed,next_episode_at);
@@ -1,9 +0,0 @@
ALTER TABLE telegram_bot_settings
ADD COLUMN server_name text NOT NULL DEFAULT 'OverCooked' CHECK(char_length(btrim(server_name)) BETWEEN 1 AND 80),
ADD COLUMN icon text NOT NULL DEFAULT '🎬' CHECK(char_length(btrim(icon)) BETWEEN 1 AND 16);
CREATE TABLE telegram_chat_states (
chat_id bigint PRIMARY KEY,
message_id bigint NOT NULL CHECK(message_id > 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
-17
View File
@@ -1,17 +0,0 @@
CREATE TABLE site_brand_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
server_name text NOT NULL DEFAULT 'OverCooked' CHECK (char_length(btrim(server_name)) BETWEEN 1 AND 80),
emby_display_name text NOT NULL DEFAULT 'OverCooked' CHECK (char_length(btrim(emby_display_name)) BETWEEN 1 AND 80),
icon_data_url text NOT NULL DEFAULT '' CHECK (char_length(icon_data_url) <= 700000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO site_brand_settings(singleton) VALUES(true);
ALTER TABLE telegram_bot_settings
ADD COLUMN official_chat_id bigint NOT NULL DEFAULT 0,
ADD COLUMN official_chat_title text NOT NULL DEFAULT '' CHECK (char_length(official_chat_title) <= 120);
ALTER TABLE telegram_chat_states
ADD COLUMN message_kind text NOT NULL DEFAULT 'text' CHECK (message_kind IN ('text','photo'));
-13
View File
@@ -1,13 +0,0 @@
ALTER TABLE media_requests
ADD COLUMN provider text NOT NULL DEFAULT '',
ADD COLUMN external_id text NOT NULL DEFAULT '',
ADD COLUMN original_title text NOT NULL DEFAULT '';
ALTER TABLE media_requests
ADD CONSTRAINT media_requests_provider_check CHECK (provider IN ('', 'tmdb', 'bangumi')),
ADD CONSTRAINT media_requests_external_id_length CHECK (length(external_id) <= 512),
ADD CONSTRAINT media_requests_original_title_length CHECK (length(original_title) <= 300);
CREATE INDEX media_requests_external_metadata_idx
ON media_requests(provider, external_id)
WHERE provider <> '' AND external_id <> '';
-15
View File
@@ -1,15 +0,0 @@
CREATE TABLE support_attachments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id uuid REFERENCES tickets(id) ON DELETE CASCADE,
media_feedback_id uuid REFERENCES media_feedback(id) ON DELETE CASCADE,
uploader_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
filename text NOT NULL CHECK (length(filename) BETWEEN 1 AND 255),
mime_type text NOT NULL CHECK (length(mime_type) BETWEEN 1 AND 128),
size_bytes bigint NOT NULL CHECK (size_bytes BETWEEN 1 AND 8388608),
content bytea NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK ((ticket_id IS NOT NULL)::int + (media_feedback_id IS NOT NULL)::int = 1)
);
CREATE INDEX support_attachments_ticket_idx ON support_attachments(ticket_id,created_at,id) WHERE ticket_id IS NOT NULL;
CREATE INDEX support_attachments_feedback_idx ON support_attachments(media_feedback_id,created_at,id) WHERE media_feedback_id IS NOT NULL;
-14
View File
@@ -1,14 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN banner_enabled boolean NOT NULL DEFAULT false,
ADD COLUMN banner_image_data_url text NOT NULL DEFAULT '' CHECK (char_length(banner_image_data_url) <= 2800000),
ADD COLUMN banner_link_url text NOT NULL DEFAULT '' CHECK (char_length(banner_link_url) <= 2000);
CREATE TABLE telegram_password_flows (
telegram_user_id bigint PRIMARY KEY,
stage text NOT NULL CHECK (stage IN ('new_password','confirm_password')),
password_hash text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX telegram_password_flows_expiry_idx ON telegram_password_flows(expires_at);
-3
View File
@@ -1,3 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN login_background_data_url text NOT NULL DEFAULT ''
CHECK (char_length(login_background_data_url) <= 5600000);
-9
View File
@@ -1,9 +0,0 @@
ALTER TABLE site_brand_settings
DROP CONSTRAINT IF EXISTS site_brand_settings_banner_image_data_url_check,
DROP CONSTRAINT IF EXISTS site_brand_settings_login_background_data_url_check;
ALTER TABLE site_brand_settings
ADD CONSTRAINT site_brand_settings_banner_image_data_url_check
CHECK (char_length(banner_image_data_url) <= 28000000),
ADD CONSTRAINT site_brand_settings_login_background_data_url_check
CHECK (char_length(login_background_data_url) <= 28000000);
-5
View File
@@ -1,5 +0,0 @@
ALTER TABLE point_accounts
DROP CONSTRAINT IF EXISTS point_accounts_balance_check;
ALTER TABLE point_ledger
DROP CONSTRAINT IF EXISTS point_ledger_balance_after_check;
@@ -1,54 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN IF NOT EXISTS points_name text NOT NULL DEFAULT '积分'
CHECK (char_length(btrim(points_name)) BETWEEN 1 AND 20);
UPDATE memberships
SET ends_at = timestamptz '9999-12-31 23:59:59+00'
WHERE ends_at > timestamptz '9999-12-31 23:59:59+00';
UPDATE card_batches SET duration_days = 365000 WHERE duration_days > 365000;
UPDATE membership_plans SET duration_days = 365000 WHERE duration_days > 365000;
ALTER TABLE card_batches
ADD CONSTRAINT card_batches_duration_days_limit CHECK (duration_days BETWEEN 1 AND 365000);
ALTER TABLE membership_plans
ADD CONSTRAINT membership_plans_duration_days_limit CHECK (duration_days BETWEEN 1 AND 365000);
ALTER TABLE point_ledger DROP CONSTRAINT IF EXISTS point_ledger_source_type_check;
ALTER TABLE point_ledger
ADD CONSTRAINT point_ledger_source_type_check CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin','membership_purchase','telegram_red_packet'));
CREATE TABLE telegram_red_packets (
id uuid PRIMARY KEY,
chat_id bigint NOT NULL,
creator_user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
creator_telegram_id bigint NOT NULL,
allocation_mode text NOT NULL CHECK (allocation_mode IN ('normal','random')),
claim_mode text NOT NULL CHECK (claim_mode IN ('button','password')),
total_points integer NOT NULL CHECK (total_points BETWEEN 1 AND 100000),
packet_count integer NOT NULL CHECK (packet_count BETWEEN 1 AND 1000),
remaining_points integer NOT NULL CHECK (remaining_points >= 0),
remaining_count integer NOT NULL CHECK (remaining_count >= 0),
password_hash text NOT NULL DEFAULT '',
message text NOT NULL DEFAULT '' CHECK (char_length(message) <= 200),
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','expired','closed')),
message_id bigint NOT NULL DEFAULT 0,
expires_at timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (remaining_count <= packet_count),
CHECK (remaining_points <= total_points)
);
CREATE INDEX telegram_red_packets_chat_open_idx ON telegram_red_packets(chat_id, expires_at DESC) WHERE status = 'open';
CREATE TABLE telegram_red_packet_claims (
id bigserial PRIMARY KEY,
packet_id uuid NOT NULL REFERENCES telegram_red_packets(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
telegram_user_id bigint NOT NULL,
amount integer NOT NULL CHECK (amount > 0),
claimed_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(packet_id, user_id),
UNIQUE(packet_id, telegram_user_id)
);
@@ -1,17 +0,0 @@
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','plugins.manage','audit.read','logs.read','backups.manage',
'updates.manage','settings.manage','licenses.manage'
));
INSERT INTO user_permissions (user_id, permission, granted_by)
SELECT id, 'plugins.manage', id
FROM users
WHERE role = 'owner'
ON CONFLICT (user_id, permission) DO NOTHING;
ALTER TABLE e_command_requests
ADD COLUMN response_status integer CHECK (response_status BETWEEN 100 AND 599),
ADD COLUMN response_body jsonb;
-14
View File
@@ -1,14 +0,0 @@
-- The plugin control plane is authenticated by E, but existing S transactions
-- require a UUID actor for foreign keys and role checks. This principal has no
-- credential row and is excluded from all user-management views.
INSERT INTO users (id, username, username_normalized, email, email_normalized, role, status)
VALUES (
'00000000-0000-0000-0000-000000000036',
'__plugin_service__',
'__plugin_service__',
'plugin-service@internal.invalid',
'plugin-service@internal.invalid',
'admin',
'active'
)
ON CONFLICT (id) DO NOTHING;
-34
View File
@@ -1,34 +0,0 @@
CREATE TABLE user_groups (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
name_normalized text NOT NULL UNIQUE,
description text NOT NULL DEFAULT '',
created_by uuid REFERENCES users(id),
updated_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (char_length(btrim(name)) BETWEEN 1 AND 80),
CHECK (char_length(description) <= 1000)
);
CREATE TABLE user_group_members (
group_id uuid NOT NULL REFERENCES user_groups(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (group_id, user_id)
);
CREATE INDEX user_group_members_user_idx ON user_group_members(user_id, group_id);
CREATE TABLE user_group_permissions (
group_id uuid NOT NULL REFERENCES user_groups(id) ON DELETE CASCADE,
permission text NOT NULL,
granted_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (group_id, permission),
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','plugins.manage','audit.read','logs.read',
'backups.manage','updates.manage','settings.manage','licenses.manage'
))
);
-12
View File
@@ -1,12 +0,0 @@
CREATE TABLE store_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
title text NOT NULL DEFAULT '会员商店',
introduction text NOT NULL DEFAULT '',
notice text NOT NULL DEFAULT '',
enabled boolean NOT NULL DEFAULT true,
allow_point_purchase boolean NOT NULL DEFAULT true,
updated_by text NOT NULL DEFAULT '',
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO store_settings(singleton) VALUES(true) ON CONFLICT (singleton) DO NOTHING;
@@ -1,3 +0,0 @@
ALTER TABLE telegram_bot_settings
ADD COLUMN command_auto_delete_seconds integer NOT NULL DEFAULT 0
CHECK (command_auto_delete_seconds BETWEEN 0 AND 3600);
@@ -1,9 +0,0 @@
ALTER TABLE telegram_bot_settings
ALTER COLUMN command_auto_delete_seconds SET DEFAULT 60;
-- The preceding migration introduced this setting with a disabled default.
-- Enable the requested cleanup for existing installations while retaining 0
-- as the explicit opt-out value in the administration UI.
UPDATE telegram_bot_settings
SET command_auto_delete_seconds = 60
WHERE singleton AND command_auto_delete_seconds = 0;
@@ -1,10 +0,0 @@
-- WebUI refresh-token sessions are account sessions, not playback devices.
-- Retire legacy rows created by the old login path only when they have never
-- been used to reserve or play media, preserving real browser playback data.
UPDATE devices device
SET revoked_at = COALESCE(device.revoked_at, now())
WHERE device.revoked_at IS NULL
AND lower(device.client_name) LIKE '%webui%'
AND NOT EXISTS (
SELECT 1 FROM playback_sessions session WHERE session.device_id = device.id
);
-11
View File
@@ -1,11 +0,0 @@
CREATE TABLE auth_login_failures (
subject_type text NOT NULL CHECK (subject_type IN ('account','ip')),
subject_hash text NOT NULL,
failed_count integer NOT NULL DEFAULT 0 CHECK (failed_count >= 0),
window_started_at timestamptz NOT NULL DEFAULT now(),
locked_until timestamptz,
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (subject_type, subject_hash)
);
CREATE INDEX auth_login_failures_cleanup_idx ON auth_login_failures(updated_at);
-104
View File
@@ -1,104 +0,0 @@
CREATE TABLE IF NOT EXISTS plugin_user_policies (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
is_disabled boolean NOT NULL DEFAULT false,
is_administrator boolean NOT NULL DEFAULT false,
allow_video_playback boolean NOT NULL DEFAULT true,
allow_audio_playback boolean NOT NULL DEFAULT true,
allow_content_downloading boolean NOT NULL DEFAULT false,
enable_remote_access boolean NOT NULL DEFAULT true,
enabled_library_ids text[] NOT NULL DEFAULT '{}',
blocked_library_ids text[] NOT NULL DEFAULT '{}',
max_concurrent_streams integer NOT NULL DEFAULT 0 CHECK (max_concurrent_streams >= 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE playback_decisions
ADD COLUMN IF NOT EXISTS media_type text NOT NULL DEFAULT '' CHECK (media_type IN ('','video','audio'));
ALTER TABLE playback_sessions
ADD COLUMN IF NOT EXISTS media_type text NOT NULL DEFAULT '' CHECK (media_type IN ('','video','audio'));
CREATE INDEX IF NOT EXISTS playback_sessions_plugin_report_idx ON playback_sessions(created_at DESC,user_id);
CREATE TABLE IF NOT EXISTS user_group_media_policies (
group_id uuid PRIMARY KEY REFERENCES user_groups(id) ON DELETE CASCADE,
enabled_library_ids text[] NOT NULL DEFAULT '{}',
blocked_library_ids text[] NOT NULL DEFAULT '{}',
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (cardinality(enabled_library_ids) <= 500),
CHECK (cardinality(blocked_library_ids) <= 500)
);
ALTER TABLE invite_codes
ADD COLUMN IF NOT EXISTS owner_reward_points integer NOT NULL DEFAULT 0 CHECK (owner_reward_points >= 0),
ADD COLUMN IF NOT EXISTS owner_reward_membership_days integer NOT NULL DEFAULT 0 CHECK (owner_reward_membership_days >= 0),
ADD COLUMN IF NOT EXISTS redeemer_reward_points integer NOT NULL DEFAULT 0 CHECK (redeemer_reward_points >= 0),
ADD COLUMN IF NOT EXISTS redeemer_reward_membership_days integer NOT NULL DEFAULT 0 CHECK (redeemer_reward_membership_days >= 0);
CREATE TABLE IF NOT EXISTS invite_redemptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
invite_id uuid NOT NULL REFERENCES invite_codes(id) ON DELETE CASCADE,
redeemed_by_user_id uuid NOT NULL REFERENCES users(id),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 1 AND 256),
owner_reward_points integer NOT NULL DEFAULT 0,
owner_reward_membership_days integer NOT NULL DEFAULT 0,
redeemer_reward_points integer NOT NULL DEFAULT 0,
redeemer_reward_membership_days integer NOT NULL DEFAULT 0,
redeemed_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (invite_id, redeemed_by_user_id),
UNIQUE (invite_id, idempotency_key)
);
CREATE INDEX IF NOT EXISTS invite_redemptions_user_time_idx ON invite_redemptions(redeemed_by_user_id,redeemed_at DESC);
INSERT INTO membership_plans(code,name,duration_days,max_playbacks,max_devices,max_bitrate_kbps,price_points,self_service,enabled)
VALUES('points-7d','7 天会员',7,1,3,0,30,true,true)
ON CONFLICT(code) DO NOTHING;
ALTER TABLE point_ledger DROP CONSTRAINT IF EXISTS point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check CHECK (source_type IN (
'manual','playback_session','media_feedback','redemption','daily_checkin',
'membership_purchase','telegram_red_packet','invite_reward'
));
CREATE TABLE IF NOT EXISTS plugin_telegram_contexts (
token_hash bytea PRIMARY KEY,
plugin_id text NOT NULL,
tenant_id text NOT NULL,
telegram_user_id bigint NOT NULL,
chat_id bigint NOT NULL,
telegram_username text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (expires_at > created_at)
);
CREATE INDEX IF NOT EXISTS plugin_telegram_contexts_expiry_idx ON plugin_telegram_contexts(expires_at) WHERE consumed_at IS NULL;
CREATE TABLE IF NOT EXISTS plugin_telegram_sends (
plugin_id text NOT NULL,
tenant_id text NOT NULL,
action text NOT NULL CHECK (action IN ('send_photo','send_document')),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 1 AND 256),
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent')),
message_id bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (plugin_id,tenant_id,action,idempotency_key)
);
CREATE INDEX IF NOT EXISTS plugin_telegram_sends_pending_idx ON plugin_telegram_sends(updated_at) WHERE status='pending';
CREATE TABLE IF NOT EXISTS plugin_audit_records (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
plugin_id text NOT NULL,
plugin_version text NOT NULL,
tenant_id text NOT NULL,
actor_type text NOT NULL,
actor_id text NOT NULL,
action text NOT NULL,
target_type text NOT NULL,
target_id text NOT NULL DEFAULT '',
request_id text NOT NULL DEFAULT '',
trace_id text NOT NULL DEFAULT '',
result text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS plugin_audit_records_plugin_time_idx ON plugin_audit_records(plugin_id,tenant_id,created_at DESC,id DESC);
-14
View File
@@ -1,14 +0,0 @@
CREATE TABLE telegram_membership_signup_flows (
token_hash text PRIMARY KEY,
actor_user_id uuid NOT NULL REFERENCES users(id),
telegram_user_id bigint NOT NULL,
telegram_username text NOT NULL DEFAULT '',
started_at timestamptz,
consumed_at timestamptz,
expires_at timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX telegram_membership_signup_flows_active_target_idx
ON telegram_membership_signup_flows(telegram_user_id)
WHERE consumed_at IS NULL;
@@ -1,8 +0,0 @@
ALTER TABLE plugin_telegram_contexts
ADD COLUMN IF NOT EXISTS message_id bigint NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS chat_type text NOT NULL DEFAULT 'private',
ADD COLUMN IF NOT EXISTS inbound_media jsonb NOT NULL DEFAULT '[]'::jsonb;
ALTER TABLE plugin_telegram_sends DROP CONSTRAINT IF EXISTS plugin_telegram_sends_action_check;
ALTER TABLE plugin_telegram_sends ADD CONSTRAINT plugin_telegram_sends_action_check
CHECK (action IN ('send_message','send_photo','send_document','resend_inbound_media','bot_api','chat_api'));
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE plugin_telegram_contexts
ADD COLUMN IF NOT EXISTS chat_type text NOT NULL DEFAULT 'private';
@@ -1,3 +0,0 @@
ALTER TABLE plugin_telegram_sends DROP CONSTRAINT IF EXISTS plugin_telegram_sends_action_check;
ALTER TABLE plugin_telegram_sends ADD CONSTRAINT plugin_telegram_sends_action_check
CHECK (action IN ('send_message','send_photo','send_document','resend_inbound_media','bot_api','chat_api'));
-3
View File
@@ -1,3 +0,0 @@
ALTER TABLE telegram_membership_signup_flows
ADD COLUMN membership_duration_days integer NOT NULL DEFAULT 30
CHECK (membership_duration_days BETWEEN 1 AND 365000);
-21
View File
@@ -1,21 +0,0 @@
CREATE TABLE telegram_group_members (
chat_id bigint NOT NULL,
telegram_user_id bigint NOT NULL,
username text NOT NULL DEFAULT '',
first_seen_at timestamptz NOT NULL DEFAULT now(),
last_seen_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (chat_id, telegram_user_id)
);
CREATE TABLE telegram_group_cleanup_flows (
token_hash text PRIMARY KEY,
actor_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
chat_id bigint NOT NULL,
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX telegram_group_cleanup_flows_active_idx
ON telegram_group_cleanup_flows(actor_user_id, chat_id)
WHERE consumed_at IS NULL;
@@ -1,7 +0,0 @@
-- A disabled row records binding history only. It must not reserve the numeric
-- Telegram identity forever, otherwise a user who unbinds cannot bind a newly
-- registered panel account.
ALTER TABLE telegram_accounts DROP CONSTRAINT IF EXISTS telegram_accounts_telegram_user_id_key;
CREATE UNIQUE INDEX IF NOT EXISTS telegram_accounts_telegram_user_id_active_idx
ON telegram_accounts(telegram_user_id)
WHERE disabled_at IS NULL;
-13
View File
@@ -1,13 +0,0 @@
CREATE TABLE storage_webhook_events (
event_id text PRIMARY KEY,
library_id uuid NOT NULL,
event_type text NOT NULL,
paths jsonb NOT NULL,
task_ids jsonb NOT NULL DEFAULT '[]'::jsonb,
state text NOT NULL CHECK (state IN ('processing','accepted','failed')),
error_code text NOT NULL DEFAULT '',
received_at timestamptz NOT NULL DEFAULT now(),
completed_at timestamptz
);
CREATE INDEX storage_webhook_events_received_idx ON storage_webhook_events(received_at DESC);
-3
View File
@@ -1,3 +0,0 @@
ALTER TABLE security_list_entries DROP CONSTRAINT security_list_entries_subject_type_check;
ALTER TABLE security_list_entries ADD CONSTRAINT security_list_entries_subject_type_check
CHECK (subject_type IN ('ip','cidr','device','user','user_agent'));
@@ -1,3 +0,0 @@
ALTER TABLE security_list_entries DROP CONSTRAINT security_list_entries_subject_type_check;
ALTER TABLE security_list_entries ADD CONSTRAINT security_list_entries_subject_type_check
CHECK (subject_type IN ('ip','cidr','device','user','user_agent','client_name','client_version'));
-13
View File
@@ -1,13 +0,0 @@
CREATE TABLE api_keys (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
prefix text NOT NULL UNIQUE CHECK (prefix ~ '^oc_[a-z0-9]{8}$'),
key_hash bytea NOT NULL UNIQUE,
expires_at timestamptz,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
last_used_at timestamptz
);
CREATE INDEX api_keys_user_idx ON api_keys(user_id,created_at DESC);
CREATE INDEX api_keys_active_idx ON api_keys(prefix) WHERE enabled;
-3
View File
@@ -1,3 +0,0 @@
ALTER TABLE playback_sessions ADD COLUMN IF NOT EXISTS bytes_served bigint NOT NULL DEFAULT 0 CHECK (bytes_served >= 0);
ALTER TABLE playback_sessions ADD COLUMN IF NOT EXISTS estimated_bytes bigint NOT NULL DEFAULT 0 CHECK (estimated_bytes >= 0);
CREATE INDEX IF NOT EXISTS playback_sessions_traffic_user_idx ON playback_sessions(user_id, updated_at DESC);
-1
View File
@@ -1 +0,0 @@
ALTER TABLE playback_sessions ADD COLUMN IF NOT EXISTS estimated_bytes bigint NOT NULL DEFAULT 0 CHECK (estimated_bytes >= 0);
-60
View File
@@ -1,60 +0,0 @@
CREATE TABLE agent_settings (
id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1),
enabled boolean NOT NULL DEFAULT false,
provider_name text NOT NULL DEFAULT '',
provider_type text NOT NULL DEFAULT 'openai_compatible' CHECK (provider_type IN ('openai_compatible','openai','custom_http')),
base_url text NOT NULL DEFAULT '',
model text NOT NULL DEFAULT '',
encrypted_api_key bytea,
timeout_ms integer NOT NULL DEFAULT 30000 CHECK (timeout_ms BETWEEN 1000 AND 120000),
max_tokens integer NOT NULL DEFAULT 2048 CHECK (max_tokens BETWEEN 1 AND 32768),
temperature double precision NOT NULL DEFAULT 0.2 CHECK (temperature BETWEEN 0 AND 2),
system_prompt text NOT NULL DEFAULT '',
max_history integer NOT NULL DEFAULT 20 CHECK (max_history BETWEEN 1 AND 100),
rate_limit integer NOT NULL DEFAULT 30 CHECK (rate_limit BETWEEN 1 AND 600),
request_logging boolean NOT NULL DEFAULT false,
tool_enabled boolean NOT NULL DEFAULT true,
confirmation_required boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
updated_by uuid REFERENCES users(id)
);
CREATE TABLE agent_sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
admin_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title text NOT NULL DEFAULT 'OverCooked Admin Agent',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL DEFAULT (now() + interval '24 hours')
);
CREATE INDEX agent_sessions_admin_idx ON agent_sessions(admin_user_id, updated_at DESC);
CREATE TABLE agent_messages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
session_id uuid NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
role text NOT NULL CHECK (role IN ('system','user','assistant','tool')),
content text NOT NULL,
tool_name text,
tool_call_id text,
confirmation_state text NOT NULL DEFAULT 'none' CHECK (confirmation_state IN ('none','pending','confirmed','cancelled','expired')),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX agent_messages_session_idx ON agent_messages(session_id, created_at);
CREATE TABLE agent_audit (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
session_id uuid REFERENCES agent_sessions(id) ON DELETE SET NULL,
admin_user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
capability text NOT NULL,
action text NOT NULL,
target text NOT NULL DEFAULT '',
request_summary text NOT NULL DEFAULT '',
result jsonb NOT NULL DEFAULT '{}'::jsonb,
status text NOT NULL CHECK (status IN ('planned','pending_confirmation','success','cancelled','failed')),
error_code text NOT NULL DEFAULT '',
trace_id text NOT NULL DEFAULT '',
latency_ms bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX agent_audit_admin_idx ON agent_audit(admin_user_id, created_at DESC);
-81
View File
@@ -1,81 +0,0 @@
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');
-64
View File
@@ -1,64 +0,0 @@
CREATE TABLE e_command_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), actor_user_id uuid NOT NULL REFERENCES users(id),
command text NOT NULL, target_id text NOT NULL, idempotency_key text NOT NULL UNIQUE,
state text NOT NULL CHECK (state IN ('accepted','completed','failed')), created_at timestamptz NOT NULL DEFAULT now(), completed_at timestamptz
);
CREATE INDEX e_command_requests_actor_idx ON e_command_requests(actor_user_id, created_at DESC);
CREATE TABLE e_event_inbox (
event_id uuid PRIMARY KEY, event_type text NOT NULL, occurred_at timestamptz NOT NULL,
payload jsonb NOT NULL, received_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE invite_codes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), creator_user_id uuid NOT NULL REFERENCES users(id),
code_hash bytea NOT NULL UNIQUE, max_uses integer NOT NULL DEFAULT 1 CHECK (max_uses > 0), used_count integer NOT NULL DEFAULT 0 CHECK (used_count >= 0),
expires_at timestamptz, revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE invite_relations (
parent_user_id uuid NOT NULL REFERENCES users(id), child_user_id uuid NOT NULL REFERENCES users(id), invite_code_id uuid NOT NULL REFERENCES invite_codes(id),
risk_state text NOT NULL DEFAULT 'normal' CHECK (risk_state IN ('normal','review','blocked')), detached_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY(parent_user_id, child_user_id), CHECK (parent_user_id <> child_user_id)
);
CREATE UNIQUE INDEX invite_relations_child_active_idx ON invite_relations(child_user_id) WHERE detached_at IS NULL;
CREATE TABLE media_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id), query_text text NOT NULL,
e_media_id uuid, availability text NOT NULL CHECK (availability IN ('unknown','available','unavailable')), status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','planned','fulfilled','rejected','cancelled')),
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX media_requests_status_idx ON media_requests(status, created_at DESC);
CREATE TABLE ticket_types (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL UNIQUE, enabled boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE tickets (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), owner_user_id uuid NOT NULL REFERENCES users(id), type_id uuid REFERENCES ticket_types(id), subject text NOT NULL,
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','pending','resolved','closed')), priority text NOT NULL DEFAULT 'normal' CHECK (priority IN ('low','normal','high')),
created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), closed_at timestamptz
);
CREATE INDEX tickets_owner_idx ON tickets(owner_user_id, updated_at DESC);
CREATE INDEX tickets_status_idx ON tickets(status, updated_at DESC);
CREATE TABLE ticket_replies (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), ticket_id uuid NOT NULL REFERENCES tickets(id), author_user_id uuid NOT NULL REFERENCES users(id), body text NOT NULL, created_at timestamptz NOT NULL DEFAULT now());
CREATE INDEX ticket_replies_ticket_idx ON ticket_replies(ticket_id, created_at);
CREATE TABLE ticket_assignments (ticket_id uuid PRIMARY KEY REFERENCES tickets(id), admin_user_id uuid NOT NULL REFERENCES users(id), assigned_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE telegram_accounts (user_id uuid PRIMARY KEY REFERENCES users(id), telegram_user_id bigint NOT NULL UNIQUE, username text, bound_at timestamptz NOT NULL DEFAULT now(), disabled_at timestamptz);
CREATE TABLE telegram_bind_codes (code_hash bytea PRIMARY KEY, user_id uuid NOT NULL REFERENCES users(id), expires_at timestamptz NOT NULL, consumed_at timestamptz, created_at timestamptz NOT NULL DEFAULT now());
CREATE TABLE license_products (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, edition text NOT NULL, max_s_instances integer NOT NULL CHECK (max_s_instances > 0),
max_e_nodes integer NOT NULL CHECK (max_e_nodes >= 0), feature_flags text[] NOT NULL DEFAULT '{}', valid_days integer NOT NULL CHECK (valid_days > 0), offline_days integer NOT NULL DEFAULT 60 CHECK (offline_days BETWEEN 0 AND 60), created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE license_activations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), product_id uuid NOT NULL REFERENCES license_products(id), activation_hash bytea NOT NULL UNIQUE,
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','exhausted')), max_instances integer NOT NULL DEFAULT 1 CHECK (max_instances > 0), created_at timestamptz NOT NULL DEFAULT now(), revoked_at timestamptz
);
CREATE TABLE license_instances (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), activation_id uuid NOT NULL REFERENCES license_activations(id), instance_id uuid NOT NULL,
fingerprint_hash bytea NOT NULL, signed_license text NOT NULL, expires_at timestamptz NOT NULL, offline_until timestamptz NOT NULL, last_seen_at timestamptz NOT NULL DEFAULT now(), unbound_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(activation_id, instance_id)
);
CREATE INDEX license_instances_active_idx ON license_instances(activation_id) WHERE unbound_at IS NULL;
CREATE TABLE outbox_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), topic text NOT NULL, aggregate_type text NOT NULL, aggregate_id text NOT NULL,
payload jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), published_at timestamptz, attempts integer NOT NULL DEFAULT 0, last_error text
, locked_by text, locked_until timestamptz
);
CREATE INDEX outbox_events_pending_idx ON outbox_events(created_at) WHERE published_at IS NULL;
CREATE TABLE runtime_config (key text PRIMARY KEY, value jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), updated_by uuid REFERENCES users(id));
@@ -1,21 +0,0 @@
ALTER TABLE users
ADD COLUMN ban_reason text,
ADD COLUMN banned_until timestamptz;
CREATE TABLE user_permissions (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
permission text NOT NULL CHECK (permission IN (
'users.read',
'users.write',
'cards.manage',
'operations.manage',
'e.manage',
'audit.read',
'licenses.manage'
)),
granted_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, permission)
);
CREATE INDEX user_permissions_permission_idx ON user_permissions(permission, user_id);
@@ -1,2 +0,0 @@
ALTER TABLE users
ADD COLUMN auth_version bigint NOT NULL DEFAULT 1 CHECK (auth_version > 0);
@@ -1,6 +0,0 @@
CREATE TABLE internal_auth_nonces (
nonce_hash bytea PRIMARY KEY,
expires_at timestamptz NOT NULL
);
CREATE INDEX internal_auth_nonces_expiry_idx ON internal_auth_nonces(expires_at);
@@ -1,22 +0,0 @@
CREATE TABLE playback_decisions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
media_id uuid NOT NULL,
library_id uuid NOT NULL,
source_id uuid NOT NULL,
device_id text NOT NULL,
route_id text,
allowed boolean NOT NULL,
deny_code text,
max_bitrate bigint NOT NULL DEFAULT 0 CHECK (max_bitrate >= 0),
allow_download boolean NOT NULL DEFAULT false,
max_playbacks integer NOT NULL DEFAULT 0 CHECK (max_playbacks >= 0),
max_device_playbacks integer NOT NULL DEFAULT 0 CHECK (max_device_playbacks >= 0),
policy_revision bigint NOT NULL CHECK (policy_revision >= 0),
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX playback_decisions_expiry_idx ON playback_decisions(expires_at);
CREATE INDEX playback_decisions_user_idx ON playback_decisions(user_id, created_at DESC);
@@ -1,16 +0,0 @@
CREATE TABLE commercial_license_cache (
instance_id text PRIMARY KEY,
license_id text NOT NULL,
signed_license text NOT NULL,
edition text NOT NULL,
features text[] NOT NULL DEFAULT '{}',
max_s_instances integer NOT NULL CHECK (max_s_instances > 0),
max_e_nodes integer NOT NULL CHECK (max_e_nodes > 0),
expires_at timestamptz NOT NULL,
offline_until timestamptz NOT NULL,
status text NOT NULL CHECK (status IN ('active','revoked','expired')),
last_verified_at timestamptz NOT NULL DEFAULT now(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX commercial_license_cache_status_expiry_idx ON commercial_license_cache(status,expires_at);
@@ -1,86 +0,0 @@
CREATE TABLE gateway_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
public_origin text NOT NULL DEFAULT '',
trusted_proxy_cidrs text[] NOT NULL DEFAULT '{}',
active_revision bigint,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO gateway_settings(singleton) VALUES (true);
CREATE TABLE gateway_service_pools (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
code text NOT NULL UNIQUE CHECK (code ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
enabled boolean NOT NULL DEFAULT true,
is_default boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX gateway_service_pools_one_default_idx ON gateway_service_pools(is_default) WHERE is_default;
INSERT INTO gateway_service_pools(code,name,is_default) VALUES ('default','Default',true);
CREATE TABLE gateway_nodes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
node_id text NOT NULL UNIQUE CHECK (node_id ~ '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
gateway_url text NOT NULL CHECK (length(gateway_url) BETWEEN 8 AND 2048),
pool_id uuid NOT NULL REFERENCES gateway_service_pools(id),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
city text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
weight integer NOT NULL DEFAULT 100 CHECK (weight BETWEEN 1 AND 10000),
max_connections bigint NOT NULL DEFAULT 0 CHECK (max_connections >= 0),
max_playback_sessions bigint NOT NULL DEFAULT 0 CHECK (max_playback_sessions >= 0),
max_transcode_sessions bigint NOT NULL DEFAULT 0 CHECK (max_transcode_sessions >= 0),
bandwidth_capacity_mbps bigint NOT NULL DEFAULT 0 CHECK (bandwidth_capacity_mbps >= 0),
library_ids text[] NOT NULL DEFAULT '{}',
storage_ids text[] NOT NULL DEFAULT '{}',
enabled boolean NOT NULL DEFAULT true,
allow_login boolean NOT NULL DEFAULT true,
allow_new_playback boolean NOT NULL DEFAULT true,
maintenance boolean NOT NULL DEFAULT false,
draining boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_nodes_pool_idx ON gateway_nodes(pool_id,enabled,node_id);
CREATE INDEX gateway_nodes_route_idx ON gateway_nodes(enabled,maintenance,draining,country,region,carrier,asn);
CREATE TABLE gateway_user_routes (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
forced_node_id text REFERENCES gateway_nodes(node_id) ON UPDATE CASCADE,
forced_pool_id uuid REFERENCES gateway_service_pools(id),
preferred_country text NOT NULL DEFAULT '',
preferred_region text NOT NULL DEFAULT '',
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE gateway_config_versions (
revision bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
snapshot jsonb NOT NULL CHECK (jsonb_typeof(snapshot)='object'),
source_revision bigint REFERENCES gateway_config_versions(revision),
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_config_versions_created_idx ON gateway_config_versions(created_at DESC);
ALTER TABLE gateway_settings ADD CONSTRAINT gateway_settings_active_revision_fk FOREIGN KEY(active_revision) REFERENCES gateway_config_versions(revision);
CREATE TABLE gateway_route_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
request_id text NOT NULL,
node_id text,
outcome text NOT NULL CHECK (outcome IN ('routed','rejected','upstream_failed')),
reason text NOT NULL DEFAULT '',
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0,
latency_ms integer NOT NULL DEFAULT 0 CHECK (latency_ms >= 0),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX gateway_route_events_recent_idx ON gateway_route_events(created_at DESC);
CREATE INDEX gateway_route_events_node_idx ON gateway_route_events(node_id,created_at DESC);
@@ -1,19 +0,0 @@
ALTER TABLE gateway_settings
ADD COLUMN routing_weights jsonb NOT NULL DEFAULT '{"country":1000,"region":100,"city":20,"carrier":10,"asn":10}'::jsonb,
ADD COLUMN recovery_ramp_seconds integer NOT NULL DEFAULT 300 CHECK (recovery_ramp_seconds BETWEEN 0 AND 3600),
ADD CONSTRAINT gateway_settings_routing_weights_object CHECK (jsonb_typeof(routing_weights) = 'object');
ALTER TABLE gateway_route_events
ADD COLUMN username text NOT NULL DEFAULT '',
ADD COLUMN device_hash text NOT NULL DEFAULT '',
ADD COLUMN resource_id text NOT NULL DEFAULT '',
ADD COLUMN path_class text NOT NULL DEFAULT 'other'
CHECK (path_class IN ('login','browse','image','playback','transcode','websocket','other')),
ADD COLUMN selection_source text NOT NULL DEFAULT '';
CREATE INDEX gateway_route_events_outcome_created_idx
ON gateway_route_events(outcome,created_at DESC);
CREATE INDEX gateway_route_events_location_created_idx
ON gateway_route_events(country,region,carrier,created_at DESC);
CREATE INDEX gateway_route_events_username_created_idx
ON gateway_route_events(username,created_at DESC) WHERE username <> '';
@@ -1,10 +0,0 @@
ALTER TABLE commercial_license_cache
ADD COLUMN customer_id text NOT NULL DEFAULT '',
ADD COLUMN product text NOT NULL DEFAULT 'overcooked',
ADD COLUMN max_users integer NOT NULL DEFAULT 0 CHECK (max_users >= 0),
ADD COLUMN max_service_pools integer NOT NULL DEFAULT 0 CHECK (max_service_pools >= 0),
ADD COLUMN not_before timestamptz NOT NULL DEFAULT now(),
ADD COLUMN maintenance_until timestamptz;
CREATE INDEX commercial_license_cache_product_status_idx
ON commercial_license_cache(product,status,offline_until);
@@ -1,75 +0,0 @@
CREATE TABLE e_address_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
trusted_proxy_cidrs text[] NOT NULL DEFAULT '{}',
default_address_id uuid,
rule_revision bigint NOT NULL DEFAULT 1,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO e_address_settings(singleton) VALUES (true);
CREATE TABLE e_addresses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (length(btrim(name)) BETWEEN 1 AND 128),
public_url text NOT NULL CHECK (public_url ~ '^https?://[^/?#[:space:]]+'),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
city text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
node_group text NOT NULL DEFAULT '',
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN -100000 AND 100000),
enabled boolean NOT NULL DEFAULT true,
healthy boolean NOT NULL DEFAULT false,
license_valid boolean NOT NULL DEFAULT false,
allow_user_publish boolean NOT NULL DEFAULT true,
maintenance boolean NOT NULL DEFAULT false,
description text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX e_addresses_public_url_idx ON e_addresses(public_url);
CREATE INDEX e_addresses_selection_idx ON e_addresses(enabled,healthy,license_valid,allow_user_publish,maintenance,priority);
ALTER TABLE e_address_settings
ADD CONSTRAINT e_address_settings_default_fk FOREIGN KEY(default_address_id) REFERENCES e_addresses(id) ON DELETE SET NULL;
CREATE TABLE e_address_rules (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN -100000 AND 100000),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
node_group text NOT NULL DEFAULT '',
address_id uuid NOT NULL REFERENCES e_addresses(id) ON DELETE CASCADE,
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_address_rules_match_idx ON e_address_rules(enabled,priority,country,region,carrier,asn,node_group);
CREATE TABLE e_user_assignments (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
address_id uuid NOT NULL REFERENCES e_addresses(id),
fixed boolean NOT NULL DEFAULT false,
rule_revision bigint NOT NULL DEFAULT 1,
reason text NOT NULL DEFAULT '',
source_ip_hash text NOT NULL DEFAULT '',
assigned_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_user_assignments_expiry_idx ON e_user_assignments(expires_at);
CREATE TABLE e_assignment_history (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
address_id uuid REFERENCES e_addresses(id) ON DELETE SET NULL,
source_ip_hash text NOT NULL DEFAULT '',
reason text NOT NULL DEFAULT '',
rule_revision bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_assignment_history_user_idx ON e_assignment_history(user_id,created_at DESC);
-48
View File
@@ -1,48 +0,0 @@
INSERT INTO e_addresses (
id, name, public_url, country, region, city, carrier, asn, node_group,
priority, enabled, healthy, license_valid, allow_user_publish,
maintenance, description, created_at, updated_at
)
SELECT
node.id,
node.name,
rtrim(node.gateway_url, '/'),
node.country,
node.region,
node.city,
node.carrier,
node.asn,
pool.code,
node.weight,
node.enabled,
false,
false,
false,
node.maintenance OR node.draining,
'Migrated from retired proxy node ' || node.node_id,
node.created_at,
node.updated_at
FROM gateway_nodes AS node
JOIN gateway_service_pools AS pool ON pool.id = node.pool_id
WHERE node.gateway_url ~ '^https?://[^/?#[:space:]]+'
ON CONFLICT (public_url) DO UPDATE SET
name = EXCLUDED.name,
country = EXCLUDED.country,
region = EXCLUDED.region,
city = EXCLUDED.city,
carrier = EXCLUDED.carrier,
asn = EXCLUDED.asn,
node_group = EXCLUDED.node_group,
priority = EXCLUDED.priority,
description = CASE
WHEN e_addresses.description = '' THEN EXCLUDED.description
ELSE e_addresses.description
END,
updated_at = GREATEST(e_addresses.updated_at, EXCLUDED.updated_at);
DROP TABLE gateway_route_events;
DROP TABLE gateway_settings;
DROP TABLE gateway_config_versions;
DROP TABLE gateway_user_routes;
DROP TABLE gateway_nodes;
DROP TABLE gateway_service_pools;
@@ -1,22 +0,0 @@
ALTER TABLE e_address_rules
ADD COLUMN match_count bigint NOT NULL DEFAULT 0 CHECK (match_count >= 0),
ADD COLUMN last_matched_at timestamptz;
CREATE TABLE e_address_selection_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
address_id uuid REFERENCES e_addresses(id) ON DELETE SET NULL,
rule_id uuid REFERENCES e_address_rules(id) ON DELETE SET NULL,
outcome text NOT NULL CHECK (outcome IN ('selected','unavailable','access_denied')),
reason text NOT NULL DEFAULT '',
source_ip_hash text NOT NULL DEFAULT '',
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint NOT NULL DEFAULT 0 CHECK (asn BETWEEN 0 AND 4294967295),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX e_address_selection_events_created_idx ON e_address_selection_events(created_at DESC);
CREATE INDEX e_address_selection_events_outcome_idx ON e_address_selection_events(outcome,created_at DESC);
CREATE INDEX e_address_selection_events_user_idx ON e_address_selection_events(user_id,created_at DESC);
@@ -1,218 +0,0 @@
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);
@@ -1,2 +0,0 @@
ALTER TABLE e_address_selection_events DROP CONSTRAINT e_address_selection_events_outcome_check;
ALTER TABLE e_address_selection_events ADD CONSTRAINT e_address_selection_events_outcome_check CHECK (outcome IN ('selected','unavailable','access_denied','rate_limited'));
@@ -1,90 +0,0 @@
ALTER TABLE audit_events
ADD COLUMN request_id text NOT NULL DEFAULT '';
CREATE INDEX audit_events_request_idx ON audit_events(request_id, created_at DESC) WHERE request_id <> '';
CREATE TABLE runtime_log_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
level text NOT NULL CHECK (level IN ('debug','info','warn','error')),
message text NOT NULL CHECK (length(message) BETWEEN 1 AND 256),
request_id text NOT NULL DEFAULT '',
method text NOT NULL DEFAULT '',
path text NOT NULL DEFAULT '',
status_code integer NOT NULL DEFAULT 0 CHECK (status_code BETWEEN 0 AND 599),
duration_ms bigint NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
detail jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(detail)='object'),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX runtime_log_events_time_idx ON runtime_log_events(created_at DESC);
CREATE INDEX runtime_log_events_request_idx ON runtime_log_events(request_id,created_at DESC) WHERE request_id <> '';
ALTER TABLE operation_jobs
ADD COLUMN schema_version integer NOT NULL DEFAULT 1,
ADD COLUMN stage text NOT NULL DEFAULT 'queued',
ADD COLUMN priority integer NOT NULL DEFAULT 0 CHECK (priority BETWEEN -100 AND 100),
ADD COLUMN progress_skipped bigint NOT NULL DEFAULT 0 CHECK (progress_skipped >= 0),
ADD COLUMN retryable boolean NOT NULL DEFAULT true,
ADD COLUMN next_retry_at timestamptz,
ADD COLUMN claimed_by text NOT NULL DEFAULT '',
ADD COLUMN payload jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(payload)='object');
CREATE TABLE backup_policies (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL CHECK (scope IN ('s','e','auth','all')),
enabled boolean NOT NULL DEFAULT true,
interval_hours integer NOT NULL DEFAULT 24 CHECK (interval_hours BETWEEN 1 AND 720),
retention_count integer NOT NULL DEFAULT 14 CHECK (retention_count BETWEEN 1 AND 365),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(scope)
);
CREATE TABLE backup_artifacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL CHECK (scope IN ('s','e','auth','all')),
state text NOT NULL DEFAULT 'queued' CHECK (state IN ('queued','running','succeeded','failed','expired','deleted')),
storage_key text NOT NULL,
size_bytes bigint NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
sha256 text NOT NULL DEFAULT '' CHECK (sha256 = '' OR sha256 ~ '^[0-9a-f]{64}$'),
job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
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(),
completed_at timestamptz,
deleted_at timestamptz
);
CREATE INDEX backup_artifacts_scope_idx ON backup_artifacts(scope,created_at DESC);
CREATE TABLE system_runtime_state (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
maintenance boolean NOT NULL DEFAULT false,
maintenance_reason text NOT NULL DEFAULT '',
maintenance_job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO system_runtime_state(singleton) VALUES (true) ON CONFLICT DO NOTHING;
CREATE TABLE update_artifacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product text NOT NULL CHECK (product IN ('s','e','auth')),
platform text NOT NULL,
version text NOT NULL,
channel text NOT NULL CHECK (channel IN ('stable','preview')),
download_url text NOT NULL DEFAULT '',
sha256 text NOT NULL CHECK (sha256 ~ '^[0-9a-f]{64}$'),
signature text NOT NULL DEFAULT '',
min_s_version text NOT NULL DEFAULT '',
min_e_version text NOT NULL DEFAULT '',
state text NOT NULL DEFAULT 'authorized' CHECK (state IN ('authorized','downloaded','verified','staged','applied','rolled_back','failed')),
storage_key text NOT NULL DEFAULT '',
job_id uuid REFERENCES operation_jobs(id) ON DELETE SET NULL,
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(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(product,platform,version,channel)
);
CREATE INDEX update_artifacts_state_idx ON update_artifacts(product,state,updated_at DESC);
@@ -1,124 +0,0 @@
ALTER TABLE users
ADD COLUMN deleted_by uuid REFERENCES users(id) ON DELETE SET NULL,
ADD COLUMN deletion_reason text NOT NULL DEFAULT '',
ADD COLUMN restored_at timestamptz;
ALTER TABLE card_batches
ADD COLUMN membership_plan_id uuid REFERENCES membership_plans(id) ON DELETE SET NULL,
ADD COLUMN edition text NOT NULL DEFAULT '' CHECK (char_length(edition) <= 64),
ADD COLUMN max_uses integer NOT NULL DEFAULT 1 CHECK (max_uses > 0);
ALTER TABLE card_batches DROP CONSTRAINT card_batches_card_type_check;
ALTER TABLE card_batches ADD CONSTRAINT card_batches_card_type_check CHECK (card_type IN ('registration','renewal','membership'));
ALTER TABLE cards
ADD COLUMN label text NOT NULL DEFAULT '' CHECK (char_length(label) <= 128);
CREATE TABLE notification_channel_configs (
channel text PRIMARY KEY CHECK (channel IN ('smtp','telegram')),
enabled boolean NOT NULL DEFAULT false,
settings jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(settings)='object'),
secret_cipher bytea,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO notification_channel_configs(channel) VALUES ('smtp'),('telegram');
CREATE TABLE notification_templates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
template_key text NOT NULL CHECK (template_key ~ '^[a-z0-9][a-z0-9._-]{0,127}$'),
channel text NOT NULL CHECK (channel IN ('email','telegram')),
subject text NOT NULL DEFAULT '' CHECK (char_length(subject) <= 300),
body text NOT NULL CHECK (char_length(body) BETWEEN 1 AND 20000),
enabled boolean NOT NULL DEFAULT true,
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(template_key,channel)
);
CREATE TABLE notification_delivery_attempts (
id bigserial PRIMARY KEY,
delivery_id uuid NOT NULL REFERENCES notification_deliveries(id) ON DELETE CASCADE,
attempt integer NOT NULL CHECK (attempt > 0),
status text NOT NULL CHECK (status IN ('sent','failed','skipped')),
error_code text NOT NULL DEFAULT '',
error_summary text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(delivery_id,attempt)
);
CREATE TABLE telegram_runtime_state (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
update_offset bigint NOT NULL DEFAULT 0,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO telegram_runtime_state(singleton) VALUES(true);
CREATE TABLE user_ip_history (
id bigserial PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
ip_hash text NOT NULL CHECK (char_length(ip_hash)=64),
country text NOT NULL DEFAULT '',
region text NOT NULL DEFAULT '',
carrier text NOT NULL DEFAULT '',
asn bigint,
first_seen_at timestamptz NOT NULL DEFAULT now(),
last_seen_at timestamptz NOT NULL DEFAULT now(),
seen_count bigint NOT NULL DEFAULT 1 CHECK (seen_count > 0),
UNIQUE(user_id,ip_hash)
);
CREATE INDEX user_ip_history_user_time_idx ON user_ip_history(user_id,last_seen_at DESC);
CREATE TABLE security_list_entries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
subject_type text NOT NULL CHECK (subject_type IN ('ip','cidr','device','user')),
mode text NOT NULL CHECK (mode IN ('allow','deny')),
subject_value text NOT NULL CHECK (char_length(subject_value) BETWEEN 1 AND 512),
reason text NOT NULL CHECK (char_length(btrim(reason)) BETWEEN 1 AND 1000),
expires_at timestamptz,
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz,
revoked_by uuid REFERENCES users(id) ON DELETE SET NULL,
UNIQUE(subject_type,mode,subject_value)
);
CREATE INDEX security_list_active_idx ON security_list_entries(subject_type,mode,expires_at) WHERE revoked_at IS NULL;
CREATE TABLE user_appeals (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
risk_event_id uuid REFERENCES risk_events(id) ON DELETE SET NULL,
body text NOT NULL CHECK (char_length(btrim(body)) BETWEEN 1 AND 5000),
status text NOT NULL DEFAULT 'submitted' CHECK (status IN ('submitted','reviewing','accepted','rejected')),
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 TABLE media_request_callbacks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
callback_url text NOT NULL CHECK (callback_url ~ '^https://'),
secret_cipher bytea NOT NULL,
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 media_request_callback_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
callback_id uuid NOT NULL REFERENCES media_request_callbacks(id) ON DELETE CASCADE,
external_event_id text NOT NULL CHECK (char_length(external_event_id) BETWEEN 1 AND 256),
request_id uuid REFERENCES media_requests(id) ON DELETE SET NULL,
payload jsonb NOT NULL CHECK (jsonb_typeof(payload)='object'),
signature_valid boolean NOT NULL,
applied boolean NOT NULL DEFAULT false,
error_summary text NOT NULL DEFAULT '',
received_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(callback_id,external_event_id)
);
-9
View File
@@ -1,9 +0,0 @@
CREATE TABLE system_installation (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
installation_id uuid NOT NULL DEFAULT gen_random_uuid(),
setup_completed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO system_installation(singleton) VALUES(true);
@@ -1,6 +0,0 @@
ALTER TABLE e_address_rules
ALTER COLUMN address_id DROP NOT NULL;
ALTER TABLE e_address_rules
ADD CONSTRAINT e_address_rules_target_check
CHECK (address_id IS NOT NULL OR length(btrim(node_group)) > 0);
@@ -1,17 +0,0 @@
ALTER TABLE playback_decisions
ADD COLUMN device_name text NOT NULL DEFAULT '',
ADD COLUMN client_name text NOT NULL DEFAULT '',
ADD COLUMN client_version text NOT NULL DEFAULT '',
ADD COLUMN remote_ip_hash text NOT NULL DEFAULT '';
ALTER TABLE playback_sessions
ADD COLUMN media_id uuid,
ADD COLUMN source_id uuid,
ADD COLUMN node_id uuid,
ADD COLUMN position_ticks bigint NOT NULL DEFAULT 0 CHECK (position_ticks >= 0),
ADD COLUMN device_name text NOT NULL DEFAULT '',
ADD COLUMN client_name text NOT NULL DEFAULT '',
ADD COLUMN client_version text NOT NULL DEFAULT '';
CREATE INDEX playback_sessions_recent_idx
ON playback_sessions(updated_at DESC, id DESC);
@@ -1,55 +0,0 @@
CREATE TABLE subtitle_sources (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
source_type text NOT NULL DEFAULT 'json_rest_v1' CHECK (source_type IN ('json_rest_v1')),
base_url text NOT NULL CHECK (base_url ~ '^https://'),
api_key_cipher bytea,
enabled boolean NOT NULL DEFAULT true,
languages text[] NOT NULL DEFAULT '{}',
priority integer NOT NULL DEFAULT 100 CHECK (priority BETWEEN 1 AND 10000),
timeout_seconds integer NOT NULL DEFAULT 10 CHECK (timeout_seconds BETWEEN 2 AND 30),
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(),
UNIQUE (name)
);
CREATE INDEX subtitle_sources_enabled_idx
ON subtitle_sources(priority,id) WHERE enabled;
CREATE TABLE subtitle_search_candidates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source_id uuid NOT NULL REFERENCES subtitle_sources(id) ON DELETE CASCADE,
media_item_id uuid NOT NULL,
media_source_id uuid NOT NULL,
external_id text NOT NULL CHECK (char_length(external_id) BETWEEN 1 AND 512),
title text NOT NULL CHECK (char_length(title) BETWEEN 1 AND 512),
language text NOT NULL DEFAULT '' CHECK (char_length(language) <= 64),
codec text NOT NULL CHECK (codec IN ('srt','ass','ssa','vtt','sup','sub','idx')),
release_group text NOT NULL DEFAULT '' CHECK (char_length(release_group) <= 256),
score double precision NOT NULL DEFAULT 0 CHECK (score >= 0 AND score <= 1),
download_path text NOT NULL CHECK (char_length(download_path) BETWEEN 1 AND 2048),
hearing_impaired boolean NOT NULL DEFAULT false,
created_by uuid NOT NULL REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL DEFAULT (now() + interval '1 hour')
);
CREATE INDEX subtitle_search_candidates_actor_idx
ON subtitle_search_candidates(created_by,created_at DESC);
CREATE INDEX subtitle_search_candidates_expiry_idx
ON subtitle_search_candidates(expires_at);
CREATE TABLE subtitle_imports (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
candidate_id uuid NOT NULL REFERENCES subtitle_search_candidates(id) ON DELETE CASCADE,
actor_user_id uuid NOT NULL REFERENCES users(id),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 16 AND 128),
state text NOT NULL DEFAULT 'processing' CHECK (state IN ('processing','succeeded','failed')),
result jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(result)='object'),
error_code text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(actor_user_id,idempotency_key)
);
@@ -1,10 +0,0 @@
CREATE TABLE metadata_provider_credentials (
provider text PRIMARY KEY CHECK (provider IN ('tmdb','bangumi')),
token_cipher bytea,
enabled boolean NOT NULL DEFAULT true,
updated_by uuid NOT NULL REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX metadata_provider_credentials_enabled_idx
ON metadata_provider_credentials(provider) WHERE enabled;
@@ -1,80 +0,0 @@
CREATE TABLE point_rules (
rule_key text PRIMARY KEY CHECK (rule_key ~ '^[a-z0-9][a-z0-9._-]{0,63}$'),
name text NOT NULL CHECK (char_length(btrim(name)) BETWEEN 1 AND 128),
points integer NOT NULL CHECK (points BETWEEN -100000 AND 100000),
enabled boolean NOT NULL DEFAULT true,
description text NOT NULL DEFAULT '' CHECK (char_length(description) <= 1000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO point_rules(rule_key,name,points,description) VALUES
('playback.completed','有效观看',5,'单次观看达到 10 分钟后奖励,每个播放会话仅奖励一次'),
('feedback.accepted','有效反馈',10,'管理员确认媒体问题后奖励');
CREATE TABLE point_accounts (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
balance bigint NOT NULL DEFAULT 0 CHECK (balance >= 0),
lifetime_earned bigint NOT NULL DEFAULT 0 CHECK (lifetime_earned >= 0),
lifetime_spent bigint NOT NULL DEFAULT 0 CHECK (lifetime_spent >= 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE point_ledger (
id bigserial PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
delta integer NOT NULL CHECK (delta <> 0 AND delta BETWEEN -100000 AND 100000),
balance_after bigint NOT NULL CHECK (balance_after >= 0),
reason text NOT NULL CHECK (char_length(btrim(reason)) BETWEEN 1 AND 200),
source_type text NOT NULL DEFAULT 'manual' CHECK (source_type IN ('manual','playback_session','media_feedback','redemption')),
source_id text NOT NULL DEFAULT '' CHECK (char_length(source_id) <= 128),
actor_user_id uuid REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(user_id,source_type,source_id)
);
CREATE INDEX point_ledger_user_time_idx ON point_ledger(user_id,created_at DESC,id DESC);
CREATE TABLE media_feedback (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
media_id uuid,
source_id uuid,
playback_session_id uuid REFERENCES playback_sessions(id) ON DELETE SET NULL,
category text NOT NULL CHECK (category IN ('playback','subtitle','audio','metadata','quality','other')),
title text NOT NULL CHECK (char_length(btrim(title)) BETWEEN 1 AND 200),
detail text NOT NULL CHECK (char_length(btrim(detail)) BETWEEN 1 AND 5000),
status text NOT NULL DEFAULT 'submitted' CHECK (status IN ('submitted','reviewing','resolved','rejected')),
priority text NOT NULL DEFAULT 'normal' CHECK (priority IN ('low','normal','high','urgent')),
resolution text NOT NULL DEFAULT '' CHECK (char_length(resolution) <= 5000),
assigned_to uuid REFERENCES users(id) ON DELETE SET NULL,
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 media_feedback_user_time_idx ON media_feedback(user_id,created_at DESC);
CREATE INDEX media_feedback_queue_idx ON media_feedback(status,priority,updated_at DESC);
CREATE TABLE telegram_bot_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
enabled boolean NOT NULL DEFAULT true,
commands_enabled boolean NOT NULL DEFAULT true,
rate_limit_per_minute integer NOT NULL DEFAULT 12 CHECK (rate_limit_per_minute BETWEEN 1 AND 120),
welcome_message text NOT NULL DEFAULT '欢迎使用站点助手,请先绑定账户。' CHECK (char_length(welcome_message) BETWEEN 1 AND 1000),
maintenance_message text NOT NULL DEFAULT '' CHECK (char_length(maintenance_message) <= 1000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO telegram_bot_settings(singleton) VALUES(true);
CREATE TABLE telegram_command_logs (
id bigserial PRIMARY KEY,
telegram_user_id bigint NOT NULL,
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
command text NOT NULL CHECK (char_length(command) BETWEEN 1 AND 64),
result text NOT NULL CHECK (char_length(result) BETWEEN 1 AND 64),
duration_ms integer NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX telegram_command_logs_rate_idx ON telegram_command_logs(telegram_user_id,created_at DESC);
CREATE INDEX telegram_command_logs_admin_idx ON telegram_command_logs(created_at DESC,id DESC);
-20
View File
@@ -1,20 +0,0 @@
ALTER TABLE point_ledger DROP CONSTRAINT point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check
CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin'));
INSERT INTO point_rules(rule_key,name,points,description) VALUES
('daily_checkin.base','每日签到',5,'每天首次签到获得的积分'),
('daily_checkin.streak_7','连续签到 7 天',10,'连续签到第 7 天的额外积分'),
('daily_checkin.streak_30','连续签到 30 天',30,'连续签到第 30 天的额外积分')
ON CONFLICT(rule_key) DO NOTHING;
CREATE TABLE daily_checkins (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
checkin_date date NOT NULL,
streak integer NOT NULL CHECK(streak > 0),
reward_points integer NOT NULL CHECK(reward_points >= 0),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(user_id,checkin_date)
);
CREATE INDEX daily_checkins_user_time_idx ON daily_checkins(user_id,checkin_date DESC);
@@ -1,34 +0,0 @@
ALTER TABLE membership_plans
ADD COLUMN price_points integer NOT NULL DEFAULT 0 CHECK(price_points >= 0),
ADD COLUMN self_service boolean NOT NULL DEFAULT false;
INSERT INTO membership_plans(code,name,duration_days,max_playbacks,max_devices,max_bitrate_kbps,price_points,self_service,enabled)
VALUES('points-30d','30 天会员',30,1,3,0,100,true,true)
ON CONFLICT(code) DO NOTHING;
ALTER TABLE point_ledger DROP CONSTRAINT point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check
CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin','membership_purchase'));
CREATE TABLE membership_purchases (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
plan_id uuid NOT NULL REFERENCES membership_plans(id),
price_points integer NOT NULL CHECK(price_points >= 0),
membership_id uuid REFERENCES memberships(id),
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX membership_purchases_user_time_idx ON membership_purchases(user_id,created_at DESC);
CREATE TABLE watch_calendar_entries (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title text NOT NULL CHECK(length(btrim(title)) BETWEEN 1 AND 200),
episode_label text NOT NULL DEFAULT '' CHECK(length(episode_label) <= 100),
next_episode_at timestamptz NOT NULL,
notes text NOT NULL DEFAULT '' CHECK(length(notes) <= 1000),
completed boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX watch_calendar_user_time_idx ON watch_calendar_entries(user_id,completed,next_episode_at);
@@ -1,9 +0,0 @@
ALTER TABLE telegram_bot_settings
ADD COLUMN server_name text NOT NULL DEFAULT 'OverCooked' CHECK(char_length(btrim(server_name)) BETWEEN 1 AND 80),
ADD COLUMN icon text NOT NULL DEFAULT '🎬' CHECK(char_length(btrim(icon)) BETWEEN 1 AND 16);
CREATE TABLE telegram_chat_states (
chat_id bigint PRIMARY KEY,
message_id bigint NOT NULL CHECK(message_id > 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
@@ -1,17 +0,0 @@
CREATE TABLE site_brand_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
server_name text NOT NULL DEFAULT 'OverCooked' CHECK (char_length(btrim(server_name)) BETWEEN 1 AND 80),
emby_display_name text NOT NULL DEFAULT 'OverCooked' CHECK (char_length(btrim(emby_display_name)) BETWEEN 1 AND 80),
icon_data_url text NOT NULL DEFAULT '' CHECK (char_length(icon_data_url) <= 700000),
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO site_brand_settings(singleton) VALUES(true);
ALTER TABLE telegram_bot_settings
ADD COLUMN official_chat_id bigint NOT NULL DEFAULT 0,
ADD COLUMN official_chat_title text NOT NULL DEFAULT '' CHECK (char_length(official_chat_title) <= 120);
ALTER TABLE telegram_chat_states
ADD COLUMN message_kind text NOT NULL DEFAULT 'text' CHECK (message_kind IN ('text','photo'));
@@ -1,13 +0,0 @@
ALTER TABLE media_requests
ADD COLUMN provider text NOT NULL DEFAULT '',
ADD COLUMN external_id text NOT NULL DEFAULT '',
ADD COLUMN original_title text NOT NULL DEFAULT '';
ALTER TABLE media_requests
ADD CONSTRAINT media_requests_provider_check CHECK (provider IN ('', 'tmdb', 'bangumi')),
ADD CONSTRAINT media_requests_external_id_length CHECK (length(external_id) <= 512),
ADD CONSTRAINT media_requests_original_title_length CHECK (length(original_title) <= 300);
CREATE INDEX media_requests_external_metadata_idx
ON media_requests(provider, external_id)
WHERE provider <> '' AND external_id <> '';
@@ -1,15 +0,0 @@
CREATE TABLE support_attachments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id uuid REFERENCES tickets(id) ON DELETE CASCADE,
media_feedback_id uuid REFERENCES media_feedback(id) ON DELETE CASCADE,
uploader_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
filename text NOT NULL CHECK (length(filename) BETWEEN 1 AND 255),
mime_type text NOT NULL CHECK (length(mime_type) BETWEEN 1 AND 128),
size_bytes bigint NOT NULL CHECK (size_bytes BETWEEN 1 AND 8388608),
content bytea NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK ((ticket_id IS NOT NULL)::int + (media_feedback_id IS NOT NULL)::int = 1)
);
CREATE INDEX support_attachments_ticket_idx ON support_attachments(ticket_id,created_at,id) WHERE ticket_id IS NOT NULL;
CREATE INDEX support_attachments_feedback_idx ON support_attachments(media_feedback_id,created_at,id) WHERE media_feedback_id IS NOT NULL;
@@ -1,14 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN banner_enabled boolean NOT NULL DEFAULT false,
ADD COLUMN banner_image_data_url text NOT NULL DEFAULT '' CHECK (char_length(banner_image_data_url) <= 2800000),
ADD COLUMN banner_link_url text NOT NULL DEFAULT '' CHECK (char_length(banner_link_url) <= 2000);
CREATE TABLE telegram_password_flows (
telegram_user_id bigint PRIMARY KEY,
stage text NOT NULL CHECK (stage IN ('new_password','confirm_password')),
password_hash text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX telegram_password_flows_expiry_idx ON telegram_password_flows(expires_at);
@@ -1,3 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN login_background_data_url text NOT NULL DEFAULT ''
CHECK (char_length(login_background_data_url) <= 5600000);
@@ -1,9 +0,0 @@
ALTER TABLE site_brand_settings
DROP CONSTRAINT IF EXISTS site_brand_settings_banner_image_data_url_check,
DROP CONSTRAINT IF EXISTS site_brand_settings_login_background_data_url_check;
ALTER TABLE site_brand_settings
ADD CONSTRAINT site_brand_settings_banner_image_data_url_check
CHECK (char_length(banner_image_data_url) <= 28000000),
ADD CONSTRAINT site_brand_settings_login_background_data_url_check
CHECK (char_length(login_background_data_url) <= 28000000);
@@ -1,5 +0,0 @@
ALTER TABLE point_accounts
DROP CONSTRAINT IF EXISTS point_accounts_balance_check;
ALTER TABLE point_ledger
DROP CONSTRAINT IF EXISTS point_ledger_balance_after_check;
@@ -1,54 +0,0 @@
ALTER TABLE site_brand_settings
ADD COLUMN IF NOT EXISTS points_name text NOT NULL DEFAULT '积分'
CHECK (char_length(btrim(points_name)) BETWEEN 1 AND 20);
UPDATE memberships
SET ends_at = timestamptz '9999-12-31 23:59:59+00'
WHERE ends_at > timestamptz '9999-12-31 23:59:59+00';
UPDATE card_batches SET duration_days = 365000 WHERE duration_days > 365000;
UPDATE membership_plans SET duration_days = 365000 WHERE duration_days > 365000;
ALTER TABLE card_batches
ADD CONSTRAINT card_batches_duration_days_limit CHECK (duration_days BETWEEN 1 AND 365000);
ALTER TABLE membership_plans
ADD CONSTRAINT membership_plans_duration_days_limit CHECK (duration_days BETWEEN 1 AND 365000);
ALTER TABLE point_ledger DROP CONSTRAINT IF EXISTS point_ledger_source_type_check;
ALTER TABLE point_ledger
ADD CONSTRAINT point_ledger_source_type_check CHECK (source_type IN ('manual','playback_session','media_feedback','redemption','daily_checkin','membership_purchase','telegram_red_packet'));
CREATE TABLE telegram_red_packets (
id uuid PRIMARY KEY,
chat_id bigint NOT NULL,
creator_user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
creator_telegram_id bigint NOT NULL,
allocation_mode text NOT NULL CHECK (allocation_mode IN ('normal','random')),
claim_mode text NOT NULL CHECK (claim_mode IN ('button','password')),
total_points integer NOT NULL CHECK (total_points BETWEEN 1 AND 100000),
packet_count integer NOT NULL CHECK (packet_count BETWEEN 1 AND 1000),
remaining_points integer NOT NULL CHECK (remaining_points >= 0),
remaining_count integer NOT NULL CHECK (remaining_count >= 0),
password_hash text NOT NULL DEFAULT '',
message text NOT NULL DEFAULT '' CHECK (char_length(message) <= 200),
status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','expired','closed')),
message_id bigint NOT NULL DEFAULT 0,
expires_at timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (remaining_count <= packet_count),
CHECK (remaining_points <= total_points)
);
CREATE INDEX telegram_red_packets_chat_open_idx ON telegram_red_packets(chat_id, expires_at DESC) WHERE status = 'open';
CREATE TABLE telegram_red_packet_claims (
id bigserial PRIMARY KEY,
packet_id uuid NOT NULL REFERENCES telegram_red_packets(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
telegram_user_id bigint NOT NULL,
amount integer NOT NULL CHECK (amount > 0),
claimed_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(packet_id, user_id),
UNIQUE(packet_id, telegram_user_id)
);
@@ -1,17 +0,0 @@
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','plugins.manage','audit.read','logs.read','backups.manage',
'updates.manage','settings.manage','licenses.manage'
));
INSERT INTO user_permissions (user_id, permission, granted_by)
SELECT id, 'plugins.manage', id
FROM users
WHERE role = 'owner'
ON CONFLICT (user_id, permission) DO NOTHING;
ALTER TABLE e_command_requests
ADD COLUMN response_status integer CHECK (response_status BETWEEN 100 AND 599),
ADD COLUMN response_body jsonb;
@@ -1,14 +0,0 @@
-- The plugin control plane is authenticated by E, but existing S transactions
-- require a UUID actor for foreign keys and role checks. This principal has no
-- credential row and is excluded from all user-management views.
INSERT INTO users (id, username, username_normalized, email, email_normalized, role, status)
VALUES (
'00000000-0000-0000-0000-000000000036',
'__plugin_service__',
'__plugin_service__',
'plugin-service@internal.invalid',
'plugin-service@internal.invalid',
'admin',
'active'
)
ON CONFLICT (id) DO NOTHING;
-34
View File
@@ -1,34 +0,0 @@
CREATE TABLE user_groups (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
name_normalized text NOT NULL UNIQUE,
description text NOT NULL DEFAULT '',
created_by uuid REFERENCES users(id),
updated_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (char_length(btrim(name)) BETWEEN 1 AND 80),
CHECK (char_length(description) <= 1000)
);
CREATE TABLE user_group_members (
group_id uuid NOT NULL REFERENCES user_groups(id) ON DELETE CASCADE,
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (group_id, user_id)
);
CREATE INDEX user_group_members_user_idx ON user_group_members(user_id, group_id);
CREATE TABLE user_group_permissions (
group_id uuid NOT NULL REFERENCES user_groups(id) ON DELETE CASCADE,
permission text NOT NULL,
granted_by uuid REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (group_id, permission),
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','plugins.manage','audit.read','logs.read',
'backups.manage','updates.manage','settings.manage','licenses.manage'
))
);
-12
View File
@@ -1,12 +0,0 @@
CREATE TABLE store_settings (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
title text NOT NULL DEFAULT '会员商店',
introduction text NOT NULL DEFAULT '',
notice text NOT NULL DEFAULT '',
enabled boolean NOT NULL DEFAULT true,
allow_point_purchase boolean NOT NULL DEFAULT true,
updated_by text NOT NULL DEFAULT '',
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO store_settings(singleton) VALUES(true) ON CONFLICT (singleton) DO NOTHING;
@@ -1,3 +0,0 @@
ALTER TABLE telegram_bot_settings
ADD COLUMN command_auto_delete_seconds integer NOT NULL DEFAULT 0
CHECK (command_auto_delete_seconds BETWEEN 0 AND 3600);
@@ -1,9 +0,0 @@
ALTER TABLE telegram_bot_settings
ALTER COLUMN command_auto_delete_seconds SET DEFAULT 60;
-- The preceding migration introduced this setting with a disabled default.
-- Enable the requested cleanup for existing installations while retaining 0
-- as the explicit opt-out value in the administration UI.
UPDATE telegram_bot_settings
SET command_auto_delete_seconds = 60
WHERE singleton AND command_auto_delete_seconds = 0;
@@ -1,10 +0,0 @@
-- WebUI refresh-token sessions are account sessions, not playback devices.
-- Retire legacy rows created by the old login path only when they have never
-- been used to reserve or play media, preserving real browser playback data.
UPDATE devices device
SET revoked_at = COALESCE(device.revoked_at, now())
WHERE device.revoked_at IS NULL
AND lower(device.client_name) LIKE '%webui%'
AND NOT EXISTS (
SELECT 1 FROM playback_sessions session WHERE session.device_id = device.id
);
@@ -1,11 +0,0 @@
CREATE TABLE auth_login_failures (
subject_type text NOT NULL CHECK (subject_type IN ('account','ip')),
subject_hash text NOT NULL,
failed_count integer NOT NULL DEFAULT 0 CHECK (failed_count >= 0),
window_started_at timestamptz NOT NULL DEFAULT now(),
locked_until timestamptz,
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (subject_type, subject_hash)
);
CREATE INDEX auth_login_failures_cleanup_idx ON auth_login_failures(updated_at);
-104
View File
@@ -1,104 +0,0 @@
CREATE TABLE IF NOT EXISTS plugin_user_policies (
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
is_disabled boolean NOT NULL DEFAULT false,
is_administrator boolean NOT NULL DEFAULT false,
allow_video_playback boolean NOT NULL DEFAULT true,
allow_audio_playback boolean NOT NULL DEFAULT true,
allow_content_downloading boolean NOT NULL DEFAULT false,
enable_remote_access boolean NOT NULL DEFAULT true,
enabled_library_ids text[] NOT NULL DEFAULT '{}',
blocked_library_ids text[] NOT NULL DEFAULT '{}',
max_concurrent_streams integer NOT NULL DEFAULT 0 CHECK (max_concurrent_streams >= 0),
updated_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE playback_decisions
ADD COLUMN IF NOT EXISTS media_type text NOT NULL DEFAULT '' CHECK (media_type IN ('','video','audio'));
ALTER TABLE playback_sessions
ADD COLUMN IF NOT EXISTS media_type text NOT NULL DEFAULT '' CHECK (media_type IN ('','video','audio'));
CREATE INDEX IF NOT EXISTS playback_sessions_plugin_report_idx ON playback_sessions(created_at DESC,user_id);
CREATE TABLE IF NOT EXISTS user_group_media_policies (
group_id uuid PRIMARY KEY REFERENCES user_groups(id) ON DELETE CASCADE,
enabled_library_ids text[] NOT NULL DEFAULT '{}',
blocked_library_ids text[] NOT NULL DEFAULT '{}',
updated_by uuid REFERENCES users(id),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (cardinality(enabled_library_ids) <= 500),
CHECK (cardinality(blocked_library_ids) <= 500)
);
ALTER TABLE invite_codes
ADD COLUMN IF NOT EXISTS owner_reward_points integer NOT NULL DEFAULT 0 CHECK (owner_reward_points >= 0),
ADD COLUMN IF NOT EXISTS owner_reward_membership_days integer NOT NULL DEFAULT 0 CHECK (owner_reward_membership_days >= 0),
ADD COLUMN IF NOT EXISTS redeemer_reward_points integer NOT NULL DEFAULT 0 CHECK (redeemer_reward_points >= 0),
ADD COLUMN IF NOT EXISTS redeemer_reward_membership_days integer NOT NULL DEFAULT 0 CHECK (redeemer_reward_membership_days >= 0);
CREATE TABLE IF NOT EXISTS invite_redemptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
invite_id uuid NOT NULL REFERENCES invite_codes(id) ON DELETE CASCADE,
redeemed_by_user_id uuid NOT NULL REFERENCES users(id),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 1 AND 256),
owner_reward_points integer NOT NULL DEFAULT 0,
owner_reward_membership_days integer NOT NULL DEFAULT 0,
redeemer_reward_points integer NOT NULL DEFAULT 0,
redeemer_reward_membership_days integer NOT NULL DEFAULT 0,
redeemed_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (invite_id, redeemed_by_user_id),
UNIQUE (invite_id, idempotency_key)
);
CREATE INDEX IF NOT EXISTS invite_redemptions_user_time_idx ON invite_redemptions(redeemed_by_user_id,redeemed_at DESC);
INSERT INTO membership_plans(code,name,duration_days,max_playbacks,max_devices,max_bitrate_kbps,price_points,self_service,enabled)
VALUES('points-7d','7 天会员',7,1,3,0,30,true,true)
ON CONFLICT(code) DO NOTHING;
ALTER TABLE point_ledger DROP CONSTRAINT IF EXISTS point_ledger_source_type_check;
ALTER TABLE point_ledger ADD CONSTRAINT point_ledger_source_type_check CHECK (source_type IN (
'manual','playback_session','media_feedback','redemption','daily_checkin',
'membership_purchase','telegram_red_packet','invite_reward'
));
CREATE TABLE IF NOT EXISTS plugin_telegram_contexts (
token_hash bytea PRIMARY KEY,
plugin_id text NOT NULL,
tenant_id text NOT NULL,
telegram_user_id bigint NOT NULL,
chat_id bigint NOT NULL,
telegram_username text NOT NULL DEFAULT '',
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
CHECK (expires_at > created_at)
);
CREATE INDEX IF NOT EXISTS plugin_telegram_contexts_expiry_idx ON plugin_telegram_contexts(expires_at) WHERE consumed_at IS NULL;
CREATE TABLE IF NOT EXISTS plugin_telegram_sends (
plugin_id text NOT NULL,
tenant_id text NOT NULL,
action text NOT NULL CHECK (action IN ('send_photo','send_document')),
idempotency_key text NOT NULL CHECK (char_length(idempotency_key) BETWEEN 1 AND 256),
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sent')),
message_id bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (plugin_id,tenant_id,action,idempotency_key)
);
CREATE INDEX IF NOT EXISTS plugin_telegram_sends_pending_idx ON plugin_telegram_sends(updated_at) WHERE status='pending';
CREATE TABLE IF NOT EXISTS plugin_audit_records (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
plugin_id text NOT NULL,
plugin_version text NOT NULL,
tenant_id text NOT NULL,
actor_type text NOT NULL,
actor_id text NOT NULL,
action text NOT NULL,
target_type text NOT NULL,
target_id text NOT NULL DEFAULT '',
request_id text NOT NULL DEFAULT '',
trace_id text NOT NULL DEFAULT '',
result text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS plugin_audit_records_plugin_time_idx ON plugin_audit_records(plugin_id,tenant_id,created_at DESC,id DESC);
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff. Show more