170 lines
7.3 KiB
SQL
170 lines
7.3 KiB
SQL
-- Idempotent local PostgreSQL patch for production maintenance.
|
|
-- It creates missing application tables and adds indexes used by hot paths.
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
|
|
CREATE TABLE IF NOT EXISTS works (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID,
|
|
title VARCHAR(255),
|
|
type VARCHAR(32) NOT NULL,
|
|
prompt TEXT,
|
|
negative_prompt TEXT,
|
|
params JSONB DEFAULT '{}'::jsonb,
|
|
result_url TEXT,
|
|
thumbnail_url TEXT,
|
|
width INTEGER,
|
|
height INTEGER,
|
|
duration NUMERIC(6, 2),
|
|
is_public BOOLEAN NOT NULL DEFAULT false,
|
|
likes_count INTEGER NOT NULL DEFAULT 0,
|
|
credits_cost INTEGER NOT NULL DEFAULT 0,
|
|
status VARCHAR(32) NOT NULL DEFAULT 'completed',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS orders (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID,
|
|
order_no VARCHAR(64) NOT NULL UNIQUE,
|
|
product_type VARCHAR(32) NOT NULL,
|
|
product_name VARCHAR(255) NOT NULL,
|
|
amount NUMERIC(10, 2) NOT NULL,
|
|
credits_amount INTEGER,
|
|
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
|
payment_method VARCHAR(32),
|
|
paid_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID,
|
|
provider VARCHAR(64) NOT NULL,
|
|
api_url TEXT,
|
|
model_name VARCHAR(128),
|
|
api_key_encrypted TEXT NOT NULL,
|
|
api_key_preview VARCHAR(20),
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS work_likes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID,
|
|
work_id UUID NOT NULL REFERENCES works(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS generation_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
type VARCHAR(16) NOT NULL,
|
|
status VARCHAR(16) NOT NULL DEFAULT 'queued',
|
|
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
result JSONB,
|
|
error TEXT,
|
|
user_id UUID,
|
|
provider VARCHAR(128),
|
|
model_name VARCHAR(255),
|
|
api_url TEXT,
|
|
progress JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
finished_at TIMESTAMPTZ,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS user_id UUID;
|
|
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS provider VARCHAR(128);
|
|
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS model_name VARCHAR(255);
|
|
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS api_url TEXT;
|
|
ALTER TABLE generation_jobs ADD COLUMN IF NOT EXISTS progress JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
|
|
CREATE INDEX IF NOT EXISTS works_user_created_idx ON works (user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS works_public_status_created_idx ON works (is_public, status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS works_public_status_likes_idx ON works (is_public, status, likes_count DESC);
|
|
CREATE INDEX IF NOT EXISTS works_type_created_idx ON works (type, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS works_status_created_idx ON works (status, created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS credit_transactions_user_created_idx ON credit_transactions (user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS credit_transactions_type_created_idx ON credit_transactions (type, created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS orders_user_created_idx ON orders (user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS orders_status_created_idx ON orders (status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS orders_order_no_idx ON orders (order_no);
|
|
|
|
CREATE INDEX IF NOT EXISTS user_api_keys_user_active_idx ON user_api_keys (user_id, is_active);
|
|
CREATE INDEX IF NOT EXISTS user_api_keys_provider_idx ON user_api_keys (provider);
|
|
|
|
CREATE INDEX IF NOT EXISTS work_likes_user_id_idx ON work_likes (user_id);
|
|
CREATE INDEX IF NOT EXISTS work_likes_work_id_idx ON work_likes (work_id);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS work_likes_user_work_uniq ON work_likes (user_id, work_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS announcements_active_window_idx ON announcements (is_active, starts_at, expires_at);
|
|
CREATE INDEX IF NOT EXISTS profiles_email_trgm_idx ON profiles USING GIN (LOWER(email) gin_trgm_ops);
|
|
CREATE INDEX IF NOT EXISTS profiles_nickname_trgm_idx ON profiles USING GIN (LOWER(COALESCE(nickname, '')) gin_trgm_ops);
|
|
CREATE INDEX IF NOT EXISTS profiles_phone_trgm_idx ON profiles USING GIN (LOWER(COALESCE(phone, '')) gin_trgm_ops);
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_status_created_idx ON generation_jobs (status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_status_updated_idx ON generation_jobs (status, updated_at DESC);
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_running_timeout_idx ON generation_jobs (updated_at) WHERE status = 'running';
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_created_idx ON generation_jobs (created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_user_created_idx ON generation_jobs (user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS generation_jobs_provider_model_created_idx ON generation_jobs (type, provider, model_name, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS platform_log_settings (
|
|
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
retention_days INTEGER NOT NULL DEFAULT 30,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
INSERT INTO platform_log_settings (id, retention_days)
|
|
VALUES (1, 30)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS platform_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
type VARCHAR(32) NOT NULL,
|
|
level VARCHAR(16) NOT NULL DEFAULT 'info',
|
|
action VARCHAR(128) NOT NULL,
|
|
message TEXT NOT NULL,
|
|
user_id UUID,
|
|
user_name VARCHAR(255),
|
|
user_email VARCHAR(255),
|
|
target_type VARCHAR(64),
|
|
target_id VARCHAR(255),
|
|
ip_address VARCHAR(64),
|
|
user_agent TEXT,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS platform_logs_type_created_idx ON platform_logs (type, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS platform_logs_level_created_idx ON platform_logs (level, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS platform_logs_user_created_idx ON platform_logs (user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS platform_logs_created_idx ON platform_logs (created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS platform_logs_user_name_idx ON platform_logs (LOWER(COALESCE(user_name, '')));
|
|
CREATE INDEX IF NOT EXISTS platform_logs_user_email_idx ON platform_logs (LOWER(COALESCE(user_email, '')));
|
|
|
|
DROP POLICY IF EXISTS "site_config_write_auth" ON site_config;
|
|
DROP POLICY IF EXISTS "announcements_write_auth" ON announcements;
|
|
DROP POLICY IF EXISTS "site_stats_write_auth" ON site_stats;
|
|
|
|
DROP POLICY IF EXISTS "site_config_admin_write" ON site_config;
|
|
DROP POLICY IF EXISTS "announcements_admin_write" ON announcements;
|
|
|
|
CREATE POLICY "site_config_admin_write" ON site_config FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
|
) WITH CHECK (
|
|
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "announcements_admin_write" ON announcements FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
|
) WITH CHECK (
|
|
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
ANALYZE;
|