CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE TABLE image_folders ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_image_folders_tenant_name UNIQUE (tenant_id, name), CONSTRAINT uq_image_folders_tenant_id UNIQUE (tenant_id, id) ); CREATE INDEX idx_image_folders_tenant ON image_folders (tenant_id); CREATE TABLE image_assets ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, folder_id BIGINT, object_key TEXT NOT NULL UNIQUE, name VARCHAR(255) NOT NULL, size_bytes BIGINT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', created_by BIGINT NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, CONSTRAINT fk_image_assets_folder FOREIGN KEY (tenant_id, folder_id) REFERENCES image_folders(tenant_id, id), CONSTRAINT uq_image_assets_tenant_id UNIQUE (tenant_id, id) ); CREATE INDEX idx_image_assets_tenant_folder ON image_assets (tenant_id, folder_id) WHERE status = 'active' AND deleted_at IS NULL; CREATE INDEX idx_image_assets_tenant_created ON image_assets (tenant_id, created_at DESC) WHERE status = 'active' AND deleted_at IS NULL; CREATE INDEX idx_image_assets_tenant_status ON image_assets (tenant_id, status, created_at DESC); CREATE INDEX idx_image_assets_name_trgm ON image_assets USING GIN (name gin_trgm_ops) WHERE status = 'active' AND deleted_at IS NULL; CREATE TABLE image_asset_references ( id BIGSERIAL PRIMARY KEY, tenant_id BIGINT NOT NULL, image_asset_id BIGINT NOT NULL, article_id BIGINT NOT NULL, ref_scope VARCHAR(20) NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT fk_image_asset_references_asset FOREIGN KEY (tenant_id, image_asset_id) REFERENCES image_assets(tenant_id, id) ON DELETE CASCADE, CONSTRAINT uq_image_asset_reference UNIQUE (image_asset_id, article_id, ref_scope) ); CREATE INDEX idx_image_asset_references_asset ON image_asset_references (tenant_id, image_asset_id); CREATE INDEX idx_image_asset_references_article ON image_asset_references (tenant_id, article_id); CREATE TABLE tenant_image_storage_usage ( tenant_id BIGINT PRIMARY KEY, used_bytes BIGINT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); UPDATE plans SET quota_policy_json = quota_policy_json || CASE plan_code WHEN 'free' THEN '{"image_storage_bytes":104857600}'::jsonb WHEN 'regular' THEN '{"image_storage_bytes":1073741824}'::jsonb WHEN 'premium' THEN '{"image_storage_bytes":2147483648}'::jsonb ELSE '{}'::jsonb END WHERE deleted_at IS NULL;