fix: 恢复所有迁移文件以修复校验和错误
恢复了以下迁移文件到原始状态: - 004_add_redeem_code_notes.sql - 005_schema_parity.sql - 006_fix_invalid_subscription_expires_at.sql - 007_add_user_allowed_groups.sql - 008_seed_default_group.sql - 009_fix_usage_logs_cache_columns.sql - 010_add_usage_logs_aggregated_indexes.sql - 011_remove_duplicate_unique_indexes.sql - 012_add_user_subscription_soft_delete.sql - 013_log_orphan_allowed_groups.sql - 014_drop_legacy_allowed_groups.sql - 015_fix_settings_unique_constraint.sql - 016_soft_delete_partial_unique_indexes.sql - 018_user_attributes.sql - 019_migrate_wechat_to_attributes.sql - 024_add_gemini_tier_id.sql 数据库迁移文件不应在应用后修改,即使只是注释。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
-- 为 redeem_codes 表添加备注字段
|
||||
|
||||
ALTER TABLE redeem_codes
|
||||
ADD COLUMN IF NOT EXISTS notes TEXT DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN redeem_codes.notes IS '备注说明(管理员调整时的原因说明)';
|
||||
-- 为 redeem_codes 表添加备注字段
|
||||
|
||||
ALTER TABLE redeem_codes
|
||||
ADD COLUMN IF NOT EXISTS notes TEXT DEFAULT NULL;
|
||||
|
||||
COMMENT ON COLUMN redeem_codes.notes IS '备注说明(管理员调整时的原因说明)';
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
-- Align SQL migrations with current GORM persistence models.
|
||||
-- This file is designed to be safe on both fresh installs and existing databases.
|
||||
|
||||
-- users: add fields added after initial migration
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS username VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS notes TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- api_keys: allow longer keys (GORM model uses size:128)
|
||||
ALTER TABLE api_keys ALTER COLUMN key TYPE VARCHAR(128);
|
||||
|
||||
-- accounts: scheduling and rate-limit fields used by repository queries
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS schedulable BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limited_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limit_reset_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS overload_until TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_start TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_end TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_status VARCHAR(20);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_schedulable ON accounts(schedulable);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limited_at ON accounts(rate_limited_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limit_reset_at ON accounts(rate_limit_reset_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_overload_until ON accounts(overload_until);
|
||||
|
||||
-- redeem_codes: subscription redeem fields
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL;
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS validity_days INT NOT NULL DEFAULT 30;
|
||||
CREATE INDEX IF NOT EXISTS idx_redeem_codes_group_id ON redeem_codes(group_id);
|
||||
|
||||
-- usage_logs: billing type used by filters and stats
|
||||
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_type SMALLINT NOT NULL DEFAULT 0;
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_billing_type ON usage_logs(billing_type);
|
||||
|
||||
-- settings: key-value store
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Align SQL migrations with current GORM persistence models.
|
||||
-- This file is designed to be safe on both fresh installs and existing databases.
|
||||
|
||||
-- users: add fields added after initial migration
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS username VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS notes TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- api_keys: allow longer keys (GORM model uses size:128)
|
||||
ALTER TABLE api_keys ALTER COLUMN key TYPE VARCHAR(128);
|
||||
|
||||
-- accounts: scheduling and rate-limit fields used by repository queries
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS schedulable BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limited_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limit_reset_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS overload_until TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_start TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_end TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_status VARCHAR(20);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_schedulable ON accounts(schedulable);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limited_at ON accounts(rate_limited_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limit_reset_at ON accounts(rate_limit_reset_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_overload_until ON accounts(overload_until);
|
||||
|
||||
-- redeem_codes: subscription redeem fields
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL;
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS validity_days INT NOT NULL DEFAULT 30;
|
||||
CREATE INDEX IF NOT EXISTS idx_redeem_codes_group_id ON redeem_codes(group_id);
|
||||
|
||||
-- usage_logs: billing type used by filters and stats
|
||||
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_type SMALLINT NOT NULL DEFAULT 0;
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_billing_type ON usage_logs(billing_type);
|
||||
|
||||
-- settings: key-value store
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
-- Fix legacy subscription records with invalid expires_at (year > 2099).
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.user_subscriptions') IS NOT NULL THEN
|
||||
UPDATE user_subscriptions
|
||||
SET expires_at = TIMESTAMPTZ '2099-12-31 23:59:59+00'
|
||||
WHERE expires_at > TIMESTAMPTZ '2099-12-31 23:59:59+00';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Fix legacy subscription records with invalid expires_at (year > 2099).
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.user_subscriptions') IS NOT NULL THEN
|
||||
UPDATE user_subscriptions
|
||||
SET expires_at = TIMESTAMPTZ '2099-12-31 23:59:59+00'
|
||||
WHERE expires_at > TIMESTAMPTZ '2099-12-31 23:59:59+00';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
-- Add user_allowed_groups join table to replace users.allowed_groups (BIGINT[]).
|
||||
-- Phase 1: create table + backfill from the legacy array column.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_allowed_groups (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (user_id, group_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_allowed_groups_group_id ON user_allowed_groups(group_id);
|
||||
|
||||
-- Backfill from the legacy users.allowed_groups array.
|
||||
INSERT INTO user_allowed_groups (user_id, group_id)
|
||||
SELECT u.id, x.group_id
|
||||
FROM users u
|
||||
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
|
||||
JOIN groups g ON g.id = x.group_id
|
||||
WHERE u.allowed_groups IS NOT NULL
|
||||
ON CONFLICT DO NOTHING;
|
||||
-- Add user_allowed_groups join table to replace users.allowed_groups (BIGINT[]).
|
||||
-- Phase 1: create table + backfill from the legacy array column.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_allowed_groups (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (user_id, group_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_allowed_groups_group_id ON user_allowed_groups(group_id);
|
||||
|
||||
-- Backfill from the legacy users.allowed_groups array.
|
||||
INSERT INTO user_allowed_groups (user_id, group_id)
|
||||
SELECT u.id, x.group_id
|
||||
FROM users u
|
||||
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
|
||||
JOIN groups g ON g.id = x.group_id
|
||||
WHERE u.allowed_groups IS NOT NULL
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- Seed a default group for fresh installs.
|
||||
INSERT INTO groups (name, description, created_at, updated_at)
|
||||
SELECT 'default', 'Default group', NOW(), NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM groups);
|
||||
-- Seed a default group for fresh installs.
|
||||
INSERT INTO groups (name, description, created_at, updated_at)
|
||||
SELECT 'default', 'Default group', NOW(), NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM groups);
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
-- Ensure usage_logs cache token columns use the underscored names expected by code.
|
||||
-- Backfill from legacy column names if they exist.
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD COLUMN IF NOT EXISTS cache_creation_5m_tokens INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD COLUMN IF NOT EXISTS cache_creation_1h_tokens INT NOT NULL DEFAULT 0;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'usage_logs'
|
||||
AND column_name = 'cache_creation5m_tokens'
|
||||
) THEN
|
||||
UPDATE usage_logs
|
||||
SET cache_creation_5m_tokens = cache_creation5m_tokens
|
||||
WHERE cache_creation_5m_tokens = 0
|
||||
AND cache_creation5m_tokens <> 0;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'usage_logs'
|
||||
AND column_name = 'cache_creation1h_tokens'
|
||||
) THEN
|
||||
UPDATE usage_logs
|
||||
SET cache_creation_1h_tokens = cache_creation1h_tokens
|
||||
WHERE cache_creation_1h_tokens = 0
|
||||
AND cache_creation1h_tokens <> 0;
|
||||
END IF;
|
||||
END $$;
|
||||
-- Ensure usage_logs cache token columns use the underscored names expected by code.
|
||||
-- Backfill from legacy column names if they exist.
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD COLUMN IF NOT EXISTS cache_creation_5m_tokens INT NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE usage_logs
|
||||
ADD COLUMN IF NOT EXISTS cache_creation_1h_tokens INT NOT NULL DEFAULT 0;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'usage_logs'
|
||||
AND column_name = 'cache_creation5m_tokens'
|
||||
) THEN
|
||||
UPDATE usage_logs
|
||||
SET cache_creation_5m_tokens = cache_creation5m_tokens
|
||||
WHERE cache_creation_5m_tokens = 0
|
||||
AND cache_creation5m_tokens <> 0;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'usage_logs'
|
||||
AND column_name = 'cache_creation1h_tokens'
|
||||
) THEN
|
||||
UPDATE usage_logs
|
||||
SET cache_creation_1h_tokens = cache_creation1h_tokens
|
||||
WHERE cache_creation_1h_tokens = 0
|
||||
AND cache_creation1h_tokens <> 0;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- 为聚合查询补充复合索引
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_account_created_at ON usage_logs(account_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_api_key_created_at ON usage_logs(api_key_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_model_created_at ON usage_logs(model, created_at);
|
||||
-- 为聚合查询补充复合索引
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_account_created_at ON usage_logs(account_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_api_key_created_at ON usage_logs(api_key_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_model_created_at ON usage_logs(model, created_at);
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
-- 011_remove_duplicate_unique_indexes.sql
|
||||
-- 移除重复的唯一索引
|
||||
-- 这些字段在 ent schema 的 Fields() 中已声明 .Unique(),
|
||||
-- 因此在 Indexes() 中再次声明 index.Fields("x").Unique() 会创建重复索引。
|
||||
-- 本迁移脚本清理这些冗余索引。
|
||||
|
||||
-- 重复索引命名约定(由 Ent 自动生成/历史迁移遗留):
|
||||
-- - 字段级 Unique() 创建的索引名: <table>_<field>_key
|
||||
-- - Indexes() 中的 Unique() 创建的索引名: <table>_<field>
|
||||
-- - 初始化迁移中的非唯一索引: idx_<table>_<field>
|
||||
|
||||
-- 仅当索引存在时才删除(幂等操作)
|
||||
|
||||
-- api_keys 表: key 字段
|
||||
DROP INDEX IF EXISTS apikey_key;
|
||||
DROP INDEX IF EXISTS api_keys_key;
|
||||
DROP INDEX IF EXISTS idx_api_keys_key;
|
||||
|
||||
-- users 表: email 字段
|
||||
DROP INDEX IF EXISTS user_email;
|
||||
DROP INDEX IF EXISTS users_email;
|
||||
DROP INDEX IF EXISTS idx_users_email;
|
||||
|
||||
-- settings 表: key 字段
|
||||
DROP INDEX IF EXISTS settings_key;
|
||||
DROP INDEX IF EXISTS idx_settings_key;
|
||||
|
||||
-- redeem_codes 表: code 字段
|
||||
DROP INDEX IF EXISTS redeemcode_code;
|
||||
DROP INDEX IF EXISTS redeem_codes_code;
|
||||
DROP INDEX IF EXISTS idx_redeem_codes_code;
|
||||
|
||||
-- groups 表: name 字段
|
||||
DROP INDEX IF EXISTS group_name;
|
||||
DROP INDEX IF EXISTS groups_name;
|
||||
DROP INDEX IF EXISTS idx_groups_name;
|
||||
|
||||
-- 注意: 每个字段的唯一约束仍由字段级 Unique() 创建的约束保留,
|
||||
-- 如 api_keys_key_key、users_email_key 等。
|
||||
-- 011_remove_duplicate_unique_indexes.sql
|
||||
-- 移除重复的唯一索引
|
||||
-- 这些字段在 ent schema 的 Fields() 中已声明 .Unique(),
|
||||
-- 因此在 Indexes() 中再次声明 index.Fields("x").Unique() 会创建重复索引。
|
||||
-- 本迁移脚本清理这些冗余索引。
|
||||
|
||||
-- 重复索引命名约定(由 Ent 自动生成/历史迁移遗留):
|
||||
-- - 字段级 Unique() 创建的索引名: <table>_<field>_key
|
||||
-- - Indexes() 中的 Unique() 创建的索引名: <table>_<field>
|
||||
-- - 初始化迁移中的非唯一索引: idx_<table>_<field>
|
||||
|
||||
-- 仅当索引存在时才删除(幂等操作)
|
||||
|
||||
-- api_keys 表: key 字段
|
||||
DROP INDEX IF EXISTS apikey_key;
|
||||
DROP INDEX IF EXISTS api_keys_key;
|
||||
DROP INDEX IF EXISTS idx_api_keys_key;
|
||||
|
||||
-- users 表: email 字段
|
||||
DROP INDEX IF EXISTS user_email;
|
||||
DROP INDEX IF EXISTS users_email;
|
||||
DROP INDEX IF EXISTS idx_users_email;
|
||||
|
||||
-- settings 表: key 字段
|
||||
DROP INDEX IF EXISTS settings_key;
|
||||
DROP INDEX IF EXISTS idx_settings_key;
|
||||
|
||||
-- redeem_codes 表: code 字段
|
||||
DROP INDEX IF EXISTS redeemcode_code;
|
||||
DROP INDEX IF EXISTS redeem_codes_code;
|
||||
DROP INDEX IF EXISTS idx_redeem_codes_code;
|
||||
|
||||
-- groups 表: name 字段
|
||||
DROP INDEX IF EXISTS group_name;
|
||||
DROP INDEX IF EXISTS groups_name;
|
||||
DROP INDEX IF EXISTS idx_groups_name;
|
||||
|
||||
-- 注意: 每个字段的唯一约束仍由字段级 Unique() 创建的约束保留,
|
||||
-- 如 api_keys_key_key、users_email_key 等。
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
-- 012: 为 user_subscriptions 表添加软删除支持
|
||||
-- 任务:fix-medium-data-hygiene 1.1
|
||||
|
||||
-- 添加 deleted_at 字段
|
||||
ALTER TABLE user_subscriptions
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
|
||||
|
||||
-- 添加 deleted_at 索引以优化软删除查询
|
||||
CREATE INDEX IF NOT EXISTS usersubscription_deleted_at
|
||||
ON user_subscriptions (deleted_at);
|
||||
|
||||
-- 注释:与其他使用软删除的实体保持一致
|
||||
COMMENT ON COLUMN user_subscriptions.deleted_at IS '软删除时间戳,NULL 表示未删除';
|
||||
-- 012: 为 user_subscriptions 表添加软删除支持
|
||||
-- 任务:fix-medium-data-hygiene 1.1
|
||||
|
||||
-- 添加 deleted_at 字段
|
||||
ALTER TABLE user_subscriptions
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
|
||||
|
||||
-- 添加 deleted_at 索引以优化软删除查询
|
||||
CREATE INDEX IF NOT EXISTS usersubscription_deleted_at
|
||||
ON user_subscriptions (deleted_at);
|
||||
|
||||
-- 注释:与其他使用软删除的实体保持一致
|
||||
COMMENT ON COLUMN user_subscriptions.deleted_at IS '软删除时间戳,NULL 表示未删除';
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
-- 013: 记录 users.allowed_groups 中的孤立 group_id
|
||||
-- 任务:fix-medium-data-hygiene 3.1
|
||||
--
|
||||
-- 目的:在删除 legacy allowed_groups 列前,记录所有引用了不存在 group 的孤立记录
|
||||
-- 这些记录可用于审计或后续数据修复
|
||||
|
||||
-- 创建审计表存储孤立的 allowed_groups 记录
|
||||
CREATE TABLE IF NOT EXISTS orphan_allowed_groups_audit (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
group_id BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (user_id, group_id)
|
||||
);
|
||||
|
||||
-- 记录孤立的 group_id(存在于 users.allowed_groups 但不存在于 groups 表)
|
||||
INSERT INTO orphan_allowed_groups_audit (user_id, group_id)
|
||||
SELECT u.id, x.group_id
|
||||
FROM users u
|
||||
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
|
||||
LEFT JOIN groups g ON g.id = x.group_id
|
||||
WHERE u.allowed_groups IS NOT NULL
|
||||
AND g.id IS NULL
|
||||
ON CONFLICT (user_id, group_id) DO NOTHING;
|
||||
|
||||
-- 添加索引便于查询
|
||||
CREATE INDEX IF NOT EXISTS idx_orphan_allowed_groups_audit_user_id
|
||||
ON orphan_allowed_groups_audit(user_id);
|
||||
|
||||
-- 记录迁移完成信息
|
||||
COMMENT ON TABLE orphan_allowed_groups_audit IS
|
||||
'审计表:记录 users.allowed_groups 中引用的不存在的 group_id,用于数据清理前的审计';
|
||||
-- 013: 记录 users.allowed_groups 中的孤立 group_id
|
||||
-- 任务:fix-medium-data-hygiene 3.1
|
||||
--
|
||||
-- 目的:在删除 legacy allowed_groups 列前,记录所有引用了不存在 group 的孤立记录
|
||||
-- 这些记录可用于审计或后续数据修复
|
||||
|
||||
-- 创建审计表存储孤立的 allowed_groups 记录
|
||||
CREATE TABLE IF NOT EXISTS orphan_allowed_groups_audit (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
group_id BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (user_id, group_id)
|
||||
);
|
||||
|
||||
-- 记录孤立的 group_id(存在于 users.allowed_groups 但不存在于 groups 表)
|
||||
INSERT INTO orphan_allowed_groups_audit (user_id, group_id)
|
||||
SELECT u.id, x.group_id
|
||||
FROM users u
|
||||
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
|
||||
LEFT JOIN groups g ON g.id = x.group_id
|
||||
WHERE u.allowed_groups IS NOT NULL
|
||||
AND g.id IS NULL
|
||||
ON CONFLICT (user_id, group_id) DO NOTHING;
|
||||
|
||||
-- 添加索引便于查询
|
||||
CREATE INDEX IF NOT EXISTS idx_orphan_allowed_groups_audit_user_id
|
||||
ON orphan_allowed_groups_audit(user_id);
|
||||
|
||||
-- 记录迁移完成信息
|
||||
COMMENT ON TABLE orphan_allowed_groups_audit IS
|
||||
'审计表:记录 users.allowed_groups 中引用的不存在的 group_id,用于数据清理前的审计';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
-- 014: 删除 legacy users.allowed_groups 列
|
||||
-- 任务:fix-medium-data-hygiene 3.3
|
||||
--
|
||||
-- 前置条件:
|
||||
-- - 迁移 007 已将数据回填到 user_allowed_groups 联接表
|
||||
-- - 迁移 013 已记录所有孤立的 group_id 到审计表
|
||||
-- - 应用代码已停止写入该列(3.2 完成)
|
||||
--
|
||||
-- 该列现已废弃,所有读写操作均使用 user_allowed_groups 联接表。
|
||||
|
||||
-- 删除 allowed_groups 列
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS allowed_groups;
|
||||
|
||||
-- 添加注释记录删除原因
|
||||
COMMENT ON TABLE users IS '用户表。注:原 allowed_groups BIGINT[] 列已迁移至 user_allowed_groups 联接表';
|
||||
-- 014: 删除 legacy users.allowed_groups 列
|
||||
-- 任务:fix-medium-data-hygiene 3.3
|
||||
--
|
||||
-- 前置条件:
|
||||
-- - 迁移 007 已将数据回填到 user_allowed_groups 联接表
|
||||
-- - 迁移 013 已记录所有孤立的 group_id 到审计表
|
||||
-- - 应用代码已停止写入该列(3.2 完成)
|
||||
--
|
||||
-- 该列现已废弃,所有读写操作均使用 user_allowed_groups 联接表。
|
||||
|
||||
-- 删除 allowed_groups 列
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS allowed_groups;
|
||||
|
||||
-- 添加注释记录删除原因
|
||||
COMMENT ON TABLE users IS '用户表。注:原 allowed_groups BIGINT[] 列已迁移至 user_allowed_groups 联接表';
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
-- 015_fix_settings_unique_constraint.sql
|
||||
-- 修复 settings 表 key 字段缺失的唯一约束
|
||||
-- 此约束是 ON CONFLICT ("key") DO UPDATE 语句所必需的
|
||||
|
||||
-- 检查并添加唯一约束(如果不存在)
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查是否已存在唯一约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'settings'::regclass
|
||||
AND contype = 'u'
|
||||
AND conname = 'settings_key_key'
|
||||
) THEN
|
||||
-- 添加唯一约束
|
||||
ALTER TABLE settings ADD CONSTRAINT settings_key_key UNIQUE (key);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
-- 015_fix_settings_unique_constraint.sql
|
||||
-- 修复 settings 表 key 字段缺失的唯一约束
|
||||
-- 此约束是 ON CONFLICT ("key") DO UPDATE 语句所必需的
|
||||
|
||||
-- 检查并添加唯一约束(如果不存在)
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查是否已存在唯一约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'settings'::regclass
|
||||
AND contype = 'u'
|
||||
AND conname = 'settings_key_key'
|
||||
) THEN
|
||||
-- 添加唯一约束
|
||||
ALTER TABLE settings ADD CONSTRAINT settings_key_key UNIQUE (key);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
-- 016_soft_delete_partial_unique_indexes.sql
|
||||
-- 修复软删除 + 唯一约束冲突问题
|
||||
-- 将普通唯一约束替换为部分唯一索引(WHERE deleted_at IS NULL)
|
||||
-- 这样软删除的记录不会占用唯一约束位置,允许删后重建同名/同邮箱/同订阅关系
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. users 表: email 字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束(可能的命名方式)
|
||||
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_email_key;
|
||||
DROP INDEX IF EXISTS users_email_key;
|
||||
DROP INDEX IF EXISTS user_email_key;
|
||||
|
||||
-- 创建部分唯一索引:只对未删除的记录建立唯一约束
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_email_unique_active
|
||||
ON users(email)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. groups 表: name 字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束
|
||||
ALTER TABLE groups DROP CONSTRAINT IF EXISTS groups_name_key;
|
||||
DROP INDEX IF EXISTS groups_name_key;
|
||||
DROP INDEX IF EXISTS group_name_key;
|
||||
|
||||
-- 创建部分唯一索引
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS groups_name_unique_active
|
||||
ON groups(name)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. user_subscriptions 表: (user_id, group_id) 组合字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束/索引
|
||||
ALTER TABLE user_subscriptions DROP CONSTRAINT IF EXISTS user_subscriptions_user_id_group_id_key;
|
||||
DROP INDEX IF EXISTS user_subscriptions_user_id_group_id_key;
|
||||
DROP INDEX IF EXISTS usersubscription_user_id_group_id;
|
||||
|
||||
-- 创建部分唯一索引
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS user_subscriptions_user_group_unique_active
|
||||
ON user_subscriptions(user_id, group_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 注意: api_keys 表的 key 字段保留普通唯一约束
|
||||
-- API Key 即使软删除后也不应该重复使用(安全考虑)
|
||||
-- ============================================================================
|
||||
-- 016_soft_delete_partial_unique_indexes.sql
|
||||
-- 修复软删除 + 唯一约束冲突问题
|
||||
-- 将普通唯一约束替换为部分唯一索引(WHERE deleted_at IS NULL)
|
||||
-- 这样软删除的记录不会占用唯一约束位置,允许删后重建同名/同邮箱/同订阅关系
|
||||
|
||||
-- ============================================================================
|
||||
-- 1. users 表: email 字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束(可能的命名方式)
|
||||
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_email_key;
|
||||
DROP INDEX IF EXISTS users_email_key;
|
||||
DROP INDEX IF EXISTS user_email_key;
|
||||
|
||||
-- 创建部分唯一索引:只对未删除的记录建立唯一约束
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_email_unique_active
|
||||
ON users(email)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 2. groups 表: name 字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束
|
||||
ALTER TABLE groups DROP CONSTRAINT IF EXISTS groups_name_key;
|
||||
DROP INDEX IF EXISTS groups_name_key;
|
||||
DROP INDEX IF EXISTS group_name_key;
|
||||
|
||||
-- 创建部分唯一索引
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS groups_name_unique_active
|
||||
ON groups(name)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 3. user_subscriptions 表: (user_id, group_id) 组合字段
|
||||
-- ============================================================================
|
||||
|
||||
-- 删除旧的唯一约束/索引
|
||||
ALTER TABLE user_subscriptions DROP CONSTRAINT IF EXISTS user_subscriptions_user_id_group_id_key;
|
||||
DROP INDEX IF EXISTS user_subscriptions_user_id_group_id_key;
|
||||
DROP INDEX IF EXISTS usersubscription_user_id_group_id;
|
||||
|
||||
-- 创建部分唯一索引
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS user_subscriptions_user_group_unique_active
|
||||
ON user_subscriptions(user_id, group_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- 注意: api_keys 表的 key 字段保留普通唯一约束
|
||||
-- API Key 即使软删除后也不应该重复使用(安全考虑)
|
||||
-- ============================================================================
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
-- Add user attribute definitions and values tables for custom user attributes.
|
||||
|
||||
-- User Attribute Definitions table (with soft delete support)
|
||||
CREATE TABLE IF NOT EXISTS user_attribute_definitions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
type VARCHAR(20) NOT NULL,
|
||||
options JSONB DEFAULT '[]'::jsonb,
|
||||
required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
validation JSONB DEFAULT '{}'::jsonb,
|
||||
placeholder VARCHAR(255) DEFAULT '',
|
||||
display_order INT NOT NULL DEFAULT 0,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Partial unique index for key (only for non-deleted records)
|
||||
-- Allows reusing keys after soft delete
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_attribute_definitions_key_unique
|
||||
ON user_attribute_definitions(key) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_enabled
|
||||
ON user_attribute_definitions(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_display_order
|
||||
ON user_attribute_definitions(display_order);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_deleted_at
|
||||
ON user_attribute_definitions(deleted_at);
|
||||
|
||||
-- User Attribute Values table (hard delete only, no deleted_at)
|
||||
CREATE TABLE IF NOT EXISTS user_attribute_values (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
attribute_id BIGINT NOT NULL REFERENCES user_attribute_definitions(id) ON DELETE CASCADE,
|
||||
value TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE(user_id, attribute_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_values_user_id
|
||||
ON user_attribute_values(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_values_attribute_id
|
||||
ON user_attribute_values(attribute_id);
|
||||
-- Add user attribute definitions and values tables for custom user attributes.
|
||||
|
||||
-- User Attribute Definitions table (with soft delete support)
|
||||
CREATE TABLE IF NOT EXISTS user_attribute_definitions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
type VARCHAR(20) NOT NULL,
|
||||
options JSONB DEFAULT '[]'::jsonb,
|
||||
required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
validation JSONB DEFAULT '{}'::jsonb,
|
||||
placeholder VARCHAR(255) DEFAULT '',
|
||||
display_order INT NOT NULL DEFAULT 0,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Partial unique index for key (only for non-deleted records)
|
||||
-- Allows reusing keys after soft delete
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_attribute_definitions_key_unique
|
||||
ON user_attribute_definitions(key) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_enabled
|
||||
ON user_attribute_definitions(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_display_order
|
||||
ON user_attribute_definitions(display_order);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_definitions_deleted_at
|
||||
ON user_attribute_definitions(deleted_at);
|
||||
|
||||
-- User Attribute Values table (hard delete only, no deleted_at)
|
||||
CREATE TABLE IF NOT EXISTS user_attribute_values (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
attribute_id BIGINT NOT NULL REFERENCES user_attribute_definitions(id) ON DELETE CASCADE,
|
||||
value TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE(user_id, attribute_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_values_user_id
|
||||
ON user_attribute_values(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_attribute_values_attribute_id
|
||||
ON user_attribute_values(attribute_id);
|
||||
|
||||
@@ -1,83 +1,83 @@
|
||||
-- Migration: Move wechat field from users table to user_attribute_values
|
||||
-- This migration:
|
||||
-- 1. Creates a "wechat" attribute definition
|
||||
-- 2. Migrates existing wechat data to user_attribute_values
|
||||
-- 3. Does NOT drop the wechat column (for rollback safety, can be done in a later migration)
|
||||
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- Step 1: Insert wechat attribute definition if not exists
|
||||
INSERT INTO user_attribute_definitions (key, name, description, type, options, required, validation, placeholder, display_order, enabled, created_at, updated_at)
|
||||
SELECT 'wechat', '微信', '用户微信号', 'text', '[]'::jsonb, false, '{}'::jsonb, '请输入微信号', 0, true, NOW(), NOW()
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL
|
||||
);
|
||||
|
||||
-- Step 2: Migrate existing wechat values to user_attribute_values
|
||||
-- Only migrate non-empty values
|
||||
INSERT INTO user_attribute_values (user_id, attribute_id, value, created_at, updated_at)
|
||||
SELECT
|
||||
u.id,
|
||||
(SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL LIMIT 1),
|
||||
u.wechat,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM users u
|
||||
WHERE u.wechat IS NOT NULL
|
||||
AND u.wechat != ''
|
||||
AND u.deleted_at IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_attribute_values uav
|
||||
WHERE uav.user_id = u.id
|
||||
AND uav.attribute_id = (SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL LIMIT 1)
|
||||
);
|
||||
|
||||
-- Step 3: Update display_order to ensure wechat appears first
|
||||
UPDATE user_attribute_definitions
|
||||
SET display_order = -1
|
||||
WHERE key = 'wechat' AND deleted_at IS NULL;
|
||||
|
||||
-- Reorder all attributes starting from 0
|
||||
WITH ordered AS (
|
||||
SELECT id, ROW_NUMBER() OVER (ORDER BY display_order, id) - 1 as new_order
|
||||
FROM user_attribute_definitions
|
||||
WHERE deleted_at IS NULL
|
||||
)
|
||||
UPDATE user_attribute_definitions
|
||||
SET display_order = ordered.new_order
|
||||
FROM ordered
|
||||
WHERE user_attribute_definitions.id = ordered.id;
|
||||
|
||||
-- Step 4: Drop the redundant wechat column from users table
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS wechat;
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- Restore wechat column
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat VARCHAR(100) DEFAULT '';
|
||||
|
||||
-- Copy attribute values back to users.wechat column
|
||||
UPDATE users u
|
||||
SET wechat = uav.value
|
||||
FROM user_attribute_values uav
|
||||
JOIN user_attribute_definitions uad ON uav.attribute_id = uad.id
|
||||
WHERE uav.user_id = u.id
|
||||
AND uad.key = 'wechat'
|
||||
AND uad.deleted_at IS NULL;
|
||||
|
||||
-- Delete migrated attribute values
|
||||
DELETE FROM user_attribute_values
|
||||
WHERE attribute_id IN (
|
||||
SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL
|
||||
);
|
||||
|
||||
-- Soft-delete the wechat attribute definition
|
||||
UPDATE user_attribute_definitions
|
||||
SET deleted_at = NOW()
|
||||
WHERE key = 'wechat' AND deleted_at IS NULL;
|
||||
|
||||
-- +goose StatementEnd
|
||||
-- Migration: Move wechat field from users table to user_attribute_values
|
||||
-- This migration:
|
||||
-- 1. Creates a "wechat" attribute definition
|
||||
-- 2. Migrates existing wechat data to user_attribute_values
|
||||
-- 3. Does NOT drop the wechat column (for rollback safety, can be done in a later migration)
|
||||
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- Step 1: Insert wechat attribute definition if not exists
|
||||
INSERT INTO user_attribute_definitions (key, name, description, type, options, required, validation, placeholder, display_order, enabled, created_at, updated_at)
|
||||
SELECT 'wechat', '微信', '用户微信号', 'text', '[]'::jsonb, false, '{}'::jsonb, '请输入微信号', 0, true, NOW(), NOW()
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL
|
||||
);
|
||||
|
||||
-- Step 2: Migrate existing wechat values to user_attribute_values
|
||||
-- Only migrate non-empty values
|
||||
INSERT INTO user_attribute_values (user_id, attribute_id, value, created_at, updated_at)
|
||||
SELECT
|
||||
u.id,
|
||||
(SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL LIMIT 1),
|
||||
u.wechat,
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM users u
|
||||
WHERE u.wechat IS NOT NULL
|
||||
AND u.wechat != ''
|
||||
AND u.deleted_at IS NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_attribute_values uav
|
||||
WHERE uav.user_id = u.id
|
||||
AND uav.attribute_id = (SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL LIMIT 1)
|
||||
);
|
||||
|
||||
-- Step 3: Update display_order to ensure wechat appears first
|
||||
UPDATE user_attribute_definitions
|
||||
SET display_order = -1
|
||||
WHERE key = 'wechat' AND deleted_at IS NULL;
|
||||
|
||||
-- Reorder all attributes starting from 0
|
||||
WITH ordered AS (
|
||||
SELECT id, ROW_NUMBER() OVER (ORDER BY display_order, id) - 1 as new_order
|
||||
FROM user_attribute_definitions
|
||||
WHERE deleted_at IS NULL
|
||||
)
|
||||
UPDATE user_attribute_definitions
|
||||
SET display_order = ordered.new_order
|
||||
FROM ordered
|
||||
WHERE user_attribute_definitions.id = ordered.id;
|
||||
|
||||
-- Step 4: Drop the redundant wechat column from users table
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS wechat;
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- Restore wechat column
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat VARCHAR(100) DEFAULT '';
|
||||
|
||||
-- Copy attribute values back to users.wechat column
|
||||
UPDATE users u
|
||||
SET wechat = uav.value
|
||||
FROM user_attribute_values uav
|
||||
JOIN user_attribute_definitions uad ON uav.attribute_id = uad.id
|
||||
WHERE uav.user_id = u.id
|
||||
AND uad.key = 'wechat'
|
||||
AND uad.deleted_at IS NULL;
|
||||
|
||||
-- Delete migrated attribute values
|
||||
DELETE FROM user_attribute_values
|
||||
WHERE attribute_id IN (
|
||||
SELECT id FROM user_attribute_definitions WHERE key = 'wechat' AND deleted_at IS NULL
|
||||
);
|
||||
|
||||
-- Soft-delete the wechat attribute definition
|
||||
UPDATE user_attribute_definitions
|
||||
SET deleted_at = NOW()
|
||||
WHERE key = 'wechat' AND deleted_at IS NULL;
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- 为 Gemini Code Assist OAuth 账号添加默认 tier_id
|
||||
-- 包括显式标记为 code_assist 的账号,以及 legacy 账号(oauth_type 为空但 project_id 存在)
|
||||
UPDATE accounts
|
||||
SET credentials = jsonb_set(
|
||||
credentials,
|
||||
'{tier_id}',
|
||||
'"LEGACY"',
|
||||
true
|
||||
)
|
||||
WHERE platform = 'gemini'
|
||||
AND type = 'oauth'
|
||||
AND jsonb_typeof(credentials) = 'object'
|
||||
AND credentials->>'tier_id' IS NULL
|
||||
AND (
|
||||
credentials->>'oauth_type' = 'code_assist'
|
||||
OR (credentials->>'oauth_type' IS NULL AND credentials->>'project_id' IS NOT NULL)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- 回滚:删除 tier_id 字段
|
||||
UPDATE accounts
|
||||
SET credentials = credentials - 'tier_id'
|
||||
WHERE platform = 'gemini'
|
||||
AND type = 'oauth'
|
||||
AND credentials ? 'tier_id';
|
||||
-- +goose StatementEnd
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- 为 Gemini Code Assist OAuth 账号添加默认 tier_id
|
||||
-- 包括显式标记为 code_assist 的账号,以及 legacy 账号(oauth_type 为空但 project_id 存在)
|
||||
UPDATE accounts
|
||||
SET credentials = jsonb_set(
|
||||
credentials,
|
||||
'{tier_id}',
|
||||
'"LEGACY"',
|
||||
true
|
||||
)
|
||||
WHERE platform = 'gemini'
|
||||
AND type = 'oauth'
|
||||
AND jsonb_typeof(credentials) = 'object'
|
||||
AND credentials->>'tier_id' IS NULL
|
||||
AND (
|
||||
credentials->>'oauth_type' = 'code_assist'
|
||||
OR (credentials->>'oauth_type' IS NULL AND credentials->>'project_id' IS NOT NULL)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
-- 回滚:删除 tier_id 字段
|
||||
UPDATE accounts
|
||||
SET credentials = credentials - 'tier_id'
|
||||
WHERE platform = 'gemini'
|
||||
AND type = 'oauth'
|
||||
AND credentials ? 'tier_id';
|
||||
-- +goose StatementEnd
|
||||
|
||||
Reference in New Issue
Block a user