Files
OverCooked-Installer/s-migrations/000017_notifications_security_lifecycle.up.sql
T

125 lines
5.4 KiB
SQL

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)
);