初始化

This commit is contained in:
Ran
2026-08-25 17:59:42 +08:00
commit 4b7380dd9b
408 changed files with 327400 additions and 0 deletions
@@ -0,0 +1,11 @@
BEGIN;
ALTER TABLE models DROP CONSTRAINT IF EXISTS models_text_billing_mode_check;
ALTER TABLE models ADD COLUMN IF NOT EXISTS max_output_tokens integer;
UPDATE models SET max_output_tokens = 4096 WHERE model_type = 'text' AND text_billing_mode = 'per_token';
ALTER TABLE models ADD CONSTRAINT models_text_billing_mode_check CHECK (
(model_type = 'text' AND text_billing_mode IN ('per_request', 'per_token') AND (text_billing_mode <> 'per_token' OR max_output_tokens > 0))
OR (model_type <> 'text' AND text_billing_mode IS NULL AND max_output_tokens IS NULL)
);
COMMIT;
@@ -0,0 +1,10 @@
BEGIN;
ALTER TABLE models DROP CONSTRAINT IF EXISTS models_text_billing_mode_check;
ALTER TABLE models DROP COLUMN IF EXISTS max_output_tokens;
ALTER TABLE models ADD CONSTRAINT models_text_billing_mode_check CHECK (
(model_type = 'text' AND text_billing_mode IN ('per_request', 'per_token'))
OR (model_type <> 'text' AND text_billing_mode IS NULL)
);
COMMIT;
@@ -0,0 +1,7 @@
UPDATE episode_storyboards storyboard
SET project_id = episode.project_id, episode_id = NULL
FROM project_episodes episode
JOIN creative_projects project ON project.id = episode.project_id
WHERE storyboard.episode_id = episode.id AND project.project_type = 'video_redraw';
ALTER TABLE project_episodes DROP COLUMN redraw_script;
@@ -0,0 +1,63 @@
ALTER TABLE project_episodes ADD COLUMN redraw_script text;
INSERT INTO project_episodes (
id, project_id, episode_no, name, cover_asset_id, source_video_asset_id,
subtitle_asset_id, audio_source, source_language, status, analysis_message,
redraw_script, created_at, updated_at
)
SELECT
gen_random_uuid(), project.id, 1, '第1集', project.cover_asset_id,
project.source_video_asset_id, project.subtitle_asset_id, project.audio_source,
project.source_language, project.redraw_status, project.analysis_message,
project.redraw_script, project.created_at, project.updated_at
FROM creative_projects project
WHERE project.project_type = 'video_redraw'
AND project.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM project_episodes episode
WHERE episode.project_id = project.id AND episode.deleted_at IS NULL
);
WITH first_episode AS (
SELECT DISTINCT ON (episode.project_id) episode.id, episode.project_id
FROM project_episodes episode
JOIN creative_projects project ON project.id = episode.project_id
WHERE project.project_type = 'video_redraw' AND episode.deleted_at IS NULL
ORDER BY episode.project_id, episode.episode_no, episode.created_at
)
UPDATE project_episodes episode
SET cover_asset_id = COALESCE(episode.cover_asset_id, project.cover_asset_id),
source_video_asset_id = COALESCE(episode.source_video_asset_id, project.source_video_asset_id),
subtitle_asset_id = COALESCE(episode.subtitle_asset_id, project.subtitle_asset_id),
audio_source = project.audio_source,
source_language = COALESCE(episode.source_language, project.source_language),
status = project.redraw_status,
analysis_message = COALESCE(episode.analysis_message, project.analysis_message),
redraw_script = COALESCE(episode.redraw_script, project.redraw_script)
FROM first_episode
JOIN creative_projects project ON project.id = first_episode.project_id
WHERE episode.id = first_episode.id;
WITH first_episode AS (
SELECT DISTINCT ON (episode.project_id) episode.id, episode.project_id
FROM project_episodes episode
JOIN creative_projects project ON project.id = episode.project_id
WHERE project.project_type = 'video_redraw' AND episode.deleted_at IS NULL
ORDER BY episode.project_id, episode.episode_no, episode.created_at
)
UPDATE episode_storyboards storyboard
SET episode_id = first_episode.id, project_id = NULL
FROM first_episode
WHERE storyboard.project_id = first_episode.project_id;
WITH first_episode AS (
SELECT DISTINCT ON (episode.project_id) episode.id, episode.project_id
FROM project_episodes episode
JOIN creative_projects project ON project.id = episode.project_id
WHERE project.project_type = 'video_redraw' AND episode.deleted_at IS NULL
ORDER BY episode.project_id, episode.episode_no, episode.created_at
)
UPDATE generation_tasks task
SET episode_id = first_episode.id
FROM first_episode
WHERE task.project_id = first_episode.project_id AND task.episode_id IS NULL;
@@ -0,0 +1,12 @@
BEGIN;
ALTER TABLE creative_projects
DROP CONSTRAINT IF EXISTS creative_projects_cast_members_check,
DROP CONSTRAINT IF EXISTS creative_projects_play_count_check,
DROP COLUMN IF EXISTS cast_members,
DROP COLUMN IF EXISTS producer,
DROP COLUMN IF EXISTS audience_profile,
DROP COLUMN IF EXISTS play_count,
DROP COLUMN IF EXISTS short_drama_type;
COMMIT;
@@ -0,0 +1,16 @@
BEGIN;
ALTER TABLE creative_projects
ADD COLUMN short_drama_type varchar(100),
ADD COLUMN play_count bigint,
ADD COLUMN audience_profile text,
ADD COLUMN producer varchar(200),
ADD COLUMN cast_members jsonb NOT NULL DEFAULT '[]'::jsonb;
ALTER TABLE creative_projects
ADD CONSTRAINT creative_projects_play_count_check
CHECK (play_count IS NULL OR play_count >= 0),
ADD CONSTRAINT creative_projects_cast_members_check
CHECK (jsonb_typeof(cast_members) = 'array');
COMMIT;
@@ -0,0 +1,11 @@
ALTER TABLE creative_projects
ALTER COLUMN play_count TYPE bigint USING (
CASE
WHEN btrim(play_count) ~ '^[0-9]+$'
AND btrim(play_count)::numeric <= 9223372036854775807
THEN btrim(play_count)::bigint
ELSE NULL
END
),
ADD CONSTRAINT creative_projects_play_count_check
CHECK (play_count IS NULL OR play_count >= 0);
@@ -0,0 +1,3 @@
ALTER TABLE creative_projects
DROP CONSTRAINT IF EXISTS creative_projects_play_count_check,
ALTER COLUMN play_count TYPE varchar(100) USING play_count::text;
@@ -0,0 +1,27 @@
UPDATE creative_projects AS project
SET cast_members = (
SELECT jsonb_agg(
CASE
WHEN jsonb_typeof(member.value->'follower_count') = 'string'
AND (member.value->>'follower_count') ~ '^[0-9]+$'
AND (member.value->>'follower_count')::numeric <= 9223372036854775807
THEN jsonb_set(
member.value,
'{follower_count}',
to_jsonb((member.value->>'follower_count')::bigint)
)
WHEN jsonb_typeof(member.value->'follower_count') = 'string'
THEN jsonb_set(member.value, '{follower_count}', 'null'::jsonb)
ELSE member.value
END
ORDER BY member.ordinality
) AS members
FROM jsonb_array_elements(project.cast_members) WITH ORDINALITY AS member(value, ordinality)
)
WHERE EXISTS (
SELECT 1
FROM jsonb_array_elements(project.cast_members) AS member(value)
WHERE jsonb_typeof(member.value->'follower_count') = 'string'
);
COMMENT ON COLUMN creative_projects.cast_members IS NULL;
@@ -0,0 +1,24 @@
UPDATE creative_projects AS project
SET cast_members = (
SELECT jsonb_agg(
CASE
WHEN jsonb_typeof(member.value->'follower_count') = 'number'
THEN jsonb_set(
member.value,
'{follower_count}',
to_jsonb(member.value->>'follower_count')
)
ELSE member.value
END
ORDER BY member.ordinality
) AS members
FROM jsonb_array_elements(project.cast_members) WITH ORDINALITY AS member(value, ordinality)
)
WHERE EXISTS (
SELECT 1
FROM jsonb_array_elements(project.cast_members) AS member(value)
WHERE jsonb_typeof(member.value->'follower_count') = 'number'
);
COMMENT ON COLUMN creative_projects.cast_members IS
'JSON array of cast members; follower_count is stored as text when present.';
@@ -0,0 +1,4 @@
DELETE FROM prompts
WHERE id = 'f06daef4-027c-4aec-97e2-2ef7eafe57b8'
AND scope = 'system'
AND type = '剧本反推';
@@ -0,0 +1,25 @@
INSERT INTO prompts (id, name, type, content, scope, owner_user_id)
VALUES (
'f06daef4-027c-4aec-97e2-2ef7eafe57b8',
'默认剧本反推提示词',
'剧本反推',
$prompt$请一次性输出完整剧本
1.
2.
3. 使
4. 使
5.
6. JSON{"script":"完整剧本"}
$prompt$,
'system',
NULL
)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
content = EXCLUDED.content,
scope = EXCLUDED.scope,
owner_user_id = EXCLUDED.owner_user_id,
deleted_at = NULL,
updated_at = CURRENT_TIMESTAMP;
@@ -0,0 +1 @@
DROP TABLE IF EXISTS script_analyses;
@@ -0,0 +1,13 @@
CREATE TABLE script_analyses (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES web_users(id) ON DELETE CASCADE,
name citext NOT NULL,
source_content text NOT NULL DEFAULT '',
result_content text NOT NULL DEFAULT '',
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at timestamptz
);
CREATE INDEX idx_script_analyses_user_updated ON script_analyses(user_id, updated_at DESC) WHERE deleted_at IS NULL;
CREATE TRIGGER script_analyses_updated_at BEFORE UPDATE ON script_analyses FOR EACH ROW EXECUTE FUNCTION set_updated_at();
@@ -0,0 +1,8 @@
DELETE FROM user_model_configs WHERE project_type = 'script_analysis';
ALTER TABLE user_model_configs
DROP CONSTRAINT IF EXISTS user_model_configs_project_type_check;
ALTER TABLE user_model_configs
ADD CONSTRAINT user_model_configs_project_type_check
CHECK (project_type IN ('premium_drama', 'video_redraw'));
@@ -0,0 +1,6 @@
ALTER TABLE user_model_configs
DROP CONSTRAINT IF EXISTS user_model_configs_project_type_check;
ALTER TABLE user_model_configs
ADD CONSTRAINT user_model_configs_project_type_check
CHECK (project_type IN ('premium_drama', 'video_redraw', 'script_analysis'));
@@ -0,0 +1,18 @@
DELETE FROM prompts
WHERE id = '2eb481cf-4a8f-4da4-a70e-ea977739c8d1'
AND type = '剧本分析';
DROP INDEX IF EXISTS uq_script_analysis_active_task;
DROP INDEX IF EXISTS idx_generation_tasks_script_analysis;
ALTER TABLE generation_tasks DROP COLUMN IF EXISTS script_analysis_id;
ALTER TABLE generation_tasks DROP CONSTRAINT generation_tasks_task_type_check;
ALTER TABLE generation_tasks
ADD CONSTRAINT generation_tasks_task_type_check
CHECK (task_type IN ('prompt_reverse', 'image_generation', 'video_generation'));
DROP TABLE IF EXISTS script_analysis_characters;
ALTER TABLE script_analyses DROP CONSTRAINT IF EXISTS script_analyses_status_check;
ALTER TABLE script_analyses
DROP COLUMN IF EXISTS analysis_message,
DROP COLUMN IF EXISTS analysis_status,
DROP COLUMN IF EXISTS analysis_result;
@@ -0,0 +1,71 @@
ALTER TABLE script_analyses
ADD COLUMN analysis_result jsonb NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN analysis_status varchar(20) NOT NULL DEFAULT 'idle',
ADD COLUMN analysis_message text;
ALTER TABLE script_analyses
ADD CONSTRAINT script_analyses_status_check
CHECK (analysis_status IN ('idle', 'queued', 'running', 'succeeded', 'failed'));
CREATE TABLE script_analysis_characters (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
script_analysis_id uuid NOT NULL REFERENCES script_analyses(id) ON DELETE CASCADE,
name citext NOT NULL,
faction varchar(20) NOT NULL,
biography text NOT NULL DEFAULT '',
motivation text NOT NULL DEFAULT '',
relationship text NOT NULL DEFAULT '',
sort_order integer NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT script_analysis_characters_faction_check CHECK (faction IN ('正派', '反派', '中立派')),
CONSTRAINT script_analysis_characters_name_unique UNIQUE (script_analysis_id, name)
);
CREATE INDEX idx_script_analysis_characters_analysis
ON script_analysis_characters(script_analysis_id, sort_order);
CREATE TRIGGER script_analysis_characters_updated_at
BEFORE UPDATE ON script_analysis_characters
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
ALTER TABLE generation_tasks
ADD COLUMN script_analysis_id uuid REFERENCES script_analyses(id) ON DELETE CASCADE;
ALTER TABLE generation_tasks
DROP CONSTRAINT generation_tasks_task_type_check;
ALTER TABLE generation_tasks
ADD CONSTRAINT generation_tasks_task_type_check
CHECK (task_type IN ('prompt_reverse', 'image_generation', 'video_generation', 'script_analysis'));
CREATE INDEX idx_generation_tasks_script_analysis
ON generation_tasks(script_analysis_id, created_at DESC)
WHERE script_analysis_id IS NOT NULL;
CREATE UNIQUE INDEX uq_script_analysis_active_task
ON generation_tasks(script_analysis_id)
WHERE script_analysis_id IS NOT NULL
AND status IN ('pending_submission', 'submitting', 'submit_unknown', 'submitted', 'processing', 'result_ready', 'downloading', 'cancel_requested');
INSERT INTO prompts (id, name, type, content, scope, owner_user_id)
VALUES (
'2eb481cf-4a8f-4da4-a70e-ea977739c8d1',
'默认剧本分析提示词',
'剧本分析',
$prompt$你是一名资深短剧策划使
1.
2.
3. faction
4. 稿
5. 1101035$prompt$,
'system',
NULL
)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
content = EXCLUDED.content,
scope = EXCLUDED.scope,
owner_user_id = EXCLUDED.owner_user_id,
deleted_at = NULL,
updated_at = CURRENT_TIMESTAMP;
@@ -0,0 +1,6 @@
ALTER TABLE script_analysis_characters
DROP CONSTRAINT IF EXISTS script_analysis_characters_faction_check;
ALTER TABLE script_analysis_characters
ADD CONSTRAINT script_analysis_characters_faction_check
CHECK (faction IN (U&'\6B63\6D3E', U&'\53CD\6D3E', U&'\4E2D\7ACB\6D3E'));
@@ -0,0 +1,6 @@
ALTER TABLE script_analysis_characters
DROP CONSTRAINT IF EXISTS script_analysis_characters_faction_check;
ALTER TABLE script_analysis_characters
ADD CONSTRAINT script_analysis_characters_faction_check
CHECK (faction IN (U&'\6B63\6D3E', U&'\53CD\6D3E', U&'\4E2D\7ACB\6D3E'));
@@ -0,0 +1,2 @@
ALTER TABLE generation_tasks
DROP COLUMN IF EXISTS result_data;
@@ -0,0 +1,2 @@
ALTER TABLE generation_tasks
ADD COLUMN IF NOT EXISTS result_data jsonb NOT NULL DEFAULT '{}'::jsonb;
@@ -0,0 +1,51 @@
UPDATE prompts
SET content = $prompt$你是一名资深短剧策划使
$prompt$ || content || $prompt$
JSON Markdown
{
"tags": {
"drama_tags": ["标签"],
"applicable_scene_tags": ["适用场景标签"]
},
"selling_point": {
"summary": "一句话总结",
"highlights": [
{"type": "爽点|痛点|卖点|看点", "content": "具体内容"}
]
},
"story_structure": {
"synopsis": "短剧简介",
"story_outline": ["按故事阶段组织的大纲"],
"emotion_outline": [
{"stage": "情绪阶段", "emotion": "主导情绪", "description": "情绪变化"}
],
"episode_outlines": [
{"episode_no": 1, "title": "集标题", "outline": "本集大纲", "emotion": "本集情绪"}
],
"characters": [
{"name": "人物名", "faction": "正派|反派|中立派", "biography": "人物小传", "motivation": "核心动机", "relationship": "关键人物关系"}
]
},
"opening_hook": {
"first_scene": "开篇第一个画面或场景",
"first_line": "第一句台词",
"attention_methods": ["悬念前置|激烈冲突|视觉奇观|其他明确手法"],
"analysis": "为什么能或不能抓住观众",
"optimization": "必要时给出优化建议"
},
"episode_hooks": [
{"episode_no": 1, "hook_type": "钩子类型", "hook": "本集结尾或关键卡点钩子", "basis": "对应的原文依据或信息不足说明"}
],
"episode_10_paywall_hooks": [
{"hook_type": "钩子类型", "content": "付费卡点候选", "appeal": "促使继续付费观看的原因"}
]
}
episode_hooks episode_no 11010episode_10_paywall_hooks 35$prompt$,
updated_at = CURRENT_TIMESTAMP
WHERE id = '2eb481cf-4a8f-4da4-a70e-ea977739c8d1'
AND type = '剧本分析'
AND scope = 'system';
@@ -0,0 +1,13 @@
UPDATE prompts
SET content = $prompt$你是一名资深短剧策划使
1.
2.
3.
4. 稿
5. 1101035$prompt$,
updated_at = CURRENT_TIMESTAMP
WHERE id = '2eb481cf-4a8f-4da4-a70e-ea977739c8d1'
AND type = '剧本分析'
AND scope = 'system';
@@ -0,0 +1,10 @@
UPDATE prompts
SET content = regexp_replace(
content,
'^你是一名资深短剧策划、编剧和商业化内容分析师。请严格依据用户提供的剧本原文进行结构化分析,不得把推测写成原文事实,不得编造原文不存在的关键人物、情节或台词。信息不足时使用空字符串或空数组。\n\n分析目标:\n',
''
),
updated_at = CURRENT_TIMESTAMP
WHERE id = '2eb481cf-4a8f-4da4-a70e-ea977739c8d1'
AND type = '剧本分析'
AND scope = 'system';
@@ -0,0 +1,10 @@
UPDATE prompts
SET content = $prompt$你是一名资深短剧策划使
$prompt$ || content,
updated_at = CURRENT_TIMESTAMP
WHERE id = '2eb481cf-4a8f-4da4-a70e-ea977739c8d1'
AND type = '剧本分析'
AND scope = 'system'
AND content NOT LIKE '你是一名资深短剧策划、编剧和商业化内容分析师。%';
@@ -0,0 +1,3 @@
-- 回滚图片生成模板结构及其参考图关系。
DROP TABLE IF EXISTS product_image_references;
DROP TABLE IF EXISTS product_image_templates;
@@ -0,0 +1,62 @@
-- 图片生成模板结构,保存用户的生成配置与参考图关系。
CREATE TABLE product_image_templates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES web_users(id) ON DELETE CASCADE,
name varchar(120) NOT NULL,
prompt text NOT NULL DEFAULT '',
model_id uuid REFERENCES models(id),
aspect_ratio varchar(12) NOT NULL DEFAULT '1:1',
resolution varchar(12) NOT NULL DEFAULT '1K',
image_count smallint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at timestamptz,
CONSTRAINT chk_product_image_template_ratio CHECK (aspect_ratio IN ('1:1', '3:4', '4:3', '9:16', '16:9')),
CONSTRAINT chk_product_image_template_resolution CHECK (resolution IN ('1K', '2K', '4K')),
CONSTRAINT chk_product_image_template_count CHECK (image_count BETWEEN 1 AND 4)
);
CREATE INDEX idx_product_image_templates_user_updated
ON product_image_templates(user_id, updated_at DESC)
WHERE deleted_at IS NULL;
-- 图片生成参考图关系,使用顺序字段稳定传递多张参考图。
CREATE TABLE product_image_references (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
template_id uuid NOT NULL REFERENCES product_image_templates(id) ON DELETE CASCADE,
media_asset_id uuid NOT NULL REFERENCES media_assets(id),
sort_order smallint NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_product_image_reference_media UNIQUE (template_id, media_asset_id),
CONSTRAINT uq_product_image_reference_order UNIQUE (template_id, sort_order),
CONSTRAINT chk_product_image_reference_order CHECK (sort_order BETWEEN 1 AND 4)
);
CREATE INDEX idx_product_image_references_template
ON product_image_references(template_id, sort_order);
COMMENT ON TABLE product_image_templates IS '用户保存的图片生成生成模板';
COMMENT ON COLUMN product_image_templates.id IS '图片生成模板主键';
COMMENT ON COLUMN product_image_templates.user_id IS '模板所属用户';
COMMENT ON COLUMN product_image_templates.name IS '模板名称';
COMMENT ON COLUMN product_image_templates.prompt IS '图片生成生成提示词';
COMMENT ON COLUMN product_image_templates.model_id IS '模板选用的图片模型';
COMMENT ON COLUMN product_image_templates.aspect_ratio IS '生成图片宽高比';
COMMENT ON COLUMN product_image_templates.resolution IS '生成图片分辨率档位';
COMMENT ON COLUMN product_image_templates.image_count IS '单次生成图片张数';
COMMENT ON COLUMN product_image_templates.created_at IS '模板创建时间';
COMMENT ON COLUMN product_image_templates.updated_at IS '模板最近更新时间';
COMMENT ON COLUMN product_image_templates.deleted_at IS '模板软删除时间';
COMMENT ON CONSTRAINT chk_product_image_template_ratio ON product_image_templates IS '限制图片生成支持的宽高比';
COMMENT ON CONSTRAINT chk_product_image_template_resolution ON product_image_templates IS '限制图片生成支持的分辨率档位';
COMMENT ON CONSTRAINT chk_product_image_template_count ON product_image_templates IS '限制单次生成一至四张图片';
COMMENT ON TABLE product_image_references IS '图片生成模板引用的参考图片';
COMMENT ON COLUMN product_image_references.id IS '参考图关系主键';
COMMENT ON COLUMN product_image_references.template_id IS '所属图片生成模板';
COMMENT ON COLUMN product_image_references.media_asset_id IS '引用的媒体文件';
COMMENT ON COLUMN product_image_references.sort_order IS '参考图传递顺序';
COMMENT ON COLUMN product_image_references.created_at IS '参考图添加时间';
COMMENT ON CONSTRAINT uq_product_image_reference_media ON product_image_references IS '同一模板不能重复引用同一媒体';
COMMENT ON CONSTRAINT uq_product_image_reference_order ON product_image_references IS '同一模板的参考图顺序不可重复';
COMMENT ON CONSTRAINT chk_product_image_reference_order ON product_image_references IS '每个模板最多保存四张参考图';
@@ -0,0 +1,11 @@
-- 回滚图片生成模板分辨率的小写约束变更。
ALTER TABLE product_image_templates
DROP CONSTRAINT IF EXISTS chk_product_image_template_resolution;
UPDATE product_image_templates
SET resolution = upper(resolution);
ALTER TABLE product_image_templates
ALTER COLUMN resolution SET DEFAULT '1K',
ADD CONSTRAINT chk_product_image_template_resolution
CHECK (resolution IN ('1K', '2K', '4K'));
@@ -0,0 +1,13 @@
-- 将图片生成模板分辨率统一为上游 GPT-Image-2 要求的小写档位。
ALTER TABLE product_image_templates
DROP CONSTRAINT IF EXISTS chk_product_image_template_resolution;
UPDATE product_image_templates
SET resolution = lower(resolution);
ALTER TABLE product_image_templates
ALTER COLUMN resolution SET DEFAULT '1k',
ADD CONSTRAINT chk_product_image_template_resolution
CHECK (resolution IN ('1k', '2k', '4k'));
COMMENT ON COLUMN product_image_templates.resolution IS '生成图片分辨率档位,使用上游要求的 1k、2k 或 4k 小写值';
@@ -0,0 +1,3 @@
-- 回退图片生成新模板的默认画幅。
ALTER TABLE product_image_templates
ALTER COLUMN aspect_ratio SET DEFAULT '1:1';
@@ -0,0 +1,3 @@
-- 将图片生成新模板的默认画幅调整为竖屏比例。
ALTER TABLE product_image_templates
ALTER COLUMN aspect_ratio SET DEFAULT '9:16';
@@ -0,0 +1,5 @@
-- 模板配置及其参考图已被明确移除,历史数据无法自动还原。
DO $$
BEGIN
RAISE EXCEPTION '图片生成模板数据已删除,000055 不支持自动回滚';
END $$;
@@ -0,0 +1,20 @@
-- 将图片生成历史迁移为用户级生图记录,并删除不再使用的模板结构。
CREATE TEMP TABLE legacy_product_image_reference_media ON COMMIT DROP AS
SELECT DISTINCT media_asset_id
FROM product_image_references;
UPDATE generation_tasks
SET input_data = (input_data - 'product_image_template_id') || jsonb_build_object(
'product_image', true,
'display_prompt', COALESCE(input_data->>'display_prompt', input_data->>'prompt', '')
)
WHERE input_data->>'product_image_template_id' IS NOT NULL;
DROP TABLE product_image_references;
DROP TABLE product_image_templates;
DELETE FROM channel_asset_cache
WHERE media_asset_id IN (SELECT media_asset_id FROM legacy_product_image_reference_media);
DELETE FROM media_assets
WHERE id IN (SELECT media_asset_id FROM legacy_product_image_reference_media);
@@ -0,0 +1,5 @@
-- 本迁移包含已授权的不可逆数据清理,不提供自动恢复路径。
DO $$
BEGIN
RAISE EXCEPTION 'WEB 历史数据清理不可自动回滚,请从备份恢复';
END $$;
@@ -0,0 +1,68 @@
-- 统一媒体存储为 COS,并清理 WEB 历史业务数据;管理员配置和用户登录身份保留。
BEGIN;
-- 先解除即将删除媒体对象上的业务引用,避免外键阻止清理。
UPDATE creative_projects
SET cover_asset_id = NULL, source_video_asset_id = NULL, subtitle_asset_id = NULL;
UPDATE project_episodes
SET cover_asset_id = NULL, source_video_asset_id = NULL, subtitle_asset_id = NULL;
UPDATE episode_storyboards
SET active_output_id = NULL, thumbnail_asset_id = NULL;
UPDATE project_assets
SET image_asset_id = NULL, audio_asset_id = NULL;
-- 删除 WEB 任务、项目、媒体和用户个性化数据;不触碰管理员配置表。
DELETE FROM user_prompt_preferences;
DELETE FROM user_model_configs;
DELETE FROM channel_asset_cache;
DELETE FROM generation_outputs;
DELETE FROM generation_tasks;
DO $$
BEGIN
IF to_regclass('public.canvas_projects') IS NOT NULL THEN
EXECUTE 'DELETE FROM public.canvas_projects';
END IF;
IF to_regclass('public.recharge_orders') IS NOT NULL THEN
EXECUTE 'DELETE FROM public.recharge_orders';
END IF;
END $$;
DELETE FROM script_analysis_characters;
DELETE FROM script_analyses;
DELETE FROM point_ledger_allocations;
DELETE FROM point_grants;
DELETE FROM point_ledger;
DELETE FROM episode_continuity_contexts;
DELETE FROM episode_sources;
DELETE FROM drama_parse_tasks;
DELETE FROM drama_parse_batches;
DELETE FROM episode_storyboards;
DELETE FROM project_model_configs;
DELETE FROM project_assets;
DELETE FROM project_episodes;
DELETE FROM drama_import_sessions;
DELETE FROM creative_projects;
DELETE FROM web_refresh_tokens;
DELETE FROM media_assets;
-- 系统提示词属于 ADMIN 配置,用户自定义提示词属于 WEB 数据。
DELETE FROM prompts WHERE scope = 'user';
-- 只保留用户登录所需身份和密码,其余 WEB 状态恢复为新用户状态。
UPDATE web_users
SET point_balance = 0,
daily_limit = 9999,
enabled = TRUE,
avatar_url = NULL,
avatar_key = NULL,
session_version = session_version + 1,
last_online_at = NULL,
deleted_at = NULL,
updated_at = CURRENT_TIMESTAMP;
-- 媒体资产的新存储提供方只允许 COS。
ALTER TABLE media_assets DROP CONSTRAINT media_assets_storage_provider_check;
ALTER TABLE media_assets ALTER COLUMN storage_provider SET DEFAULT 'cos';
ALTER TABLE media_assets
ADD CONSTRAINT media_assets_storage_provider_check CHECK (storage_provider = 'cos');
COMMIT;
@@ -0,0 +1,14 @@
-- 回滚永久积分结构;旧到期日期已无法恢复,现有批次统一设为无限期。
DROP INDEX IF EXISTS public.idx_point_grants_user_available;
ALTER TABLE public.point_grants
ADD COLUMN expires_at timestamp with time zone NOT NULL DEFAULT 'infinity'::timestamptz;
ALTER TABLE public.point_grants
ALTER COLUMN expires_at DROP DEFAULT;
CREATE INDEX idx_point_grants_user_expiry
ON public.point_grants(user_id, expires_at, created_at, id)
WHERE available_amount > 0;
COMMENT ON INDEX public.idx_point_grants_user_expiry IS '加速按用户和到期时间锁定可用积分批次';
@@ -0,0 +1,11 @@
-- 积分改为永久有效,移除批次过期字段并保留按创建顺序扣减所需索引。
DROP INDEX IF EXISTS public.idx_point_grants_user_expiry;
ALTER TABLE public.point_grants
DROP COLUMN expires_at;
CREATE INDEX idx_point_grants_user_available
ON public.point_grants(user_id, created_at, id)
WHERE available_amount > 0;
COMMENT ON INDEX public.idx_point_grants_user_available IS '加速按用户和创建顺序锁定可用积分批次';
@@ -0,0 +1,26 @@
-- 回滚提交未知状态约束;已转换的任务状态不会反向推断。
ALTER TABLE drama_parse_tasks DROP CONSTRAINT drama_parse_tasks_status_check;
ALTER TABLE drama_parse_tasks
ADD CONSTRAINT drama_parse_tasks_status_check
CHECK (status IN ('queued', 'running', 'retry_wait', 'submit_unknown', 'succeeded', 'failed', 'cancel_requested', 'cancelled'));
ALTER TABLE generation_tasks DROP CONSTRAINT generation_tasks_status_check;
ALTER TABLE generation_tasks
ADD CONSTRAINT generation_tasks_status_check
CHECK (status IN ('pending_submission', 'submitting', 'submit_unknown', 'submitted', 'processing', 'result_ready', 'downloading', 'succeeded', 'failed', 'cancel_requested', 'cancelled'));
DROP INDEX idx_drama_parse_tasks_recovery;
CREATE INDEX idx_drama_parse_tasks_recovery
ON drama_parse_tasks(status, lease_until)
WHERE status IN ('queued', 'running', 'retry_wait', 'submit_unknown');
DROP INDEX uq_drama_episode_active_parse;
CREATE UNIQUE INDEX uq_drama_episode_active_parse
ON drama_parse_tasks(episode_id)
WHERE status IN ('queued', 'running', 'retry_wait', 'submit_unknown', 'cancel_requested');
DROP INDEX uq_script_analysis_active_task;
CREATE UNIQUE INDEX uq_script_analysis_active_task
ON generation_tasks(script_analysis_id)
WHERE script_analysis_id IS NOT NULL
AND status IN ('pending_submission', 'submitting', 'submit_unknown', 'submitted', 'processing', 'result_ready', 'downloading', 'cancel_requested');
@@ -0,0 +1,44 @@
-- 移除无法自动结束的提交未知状态,并将遗留任务恢复到可重试队列。
UPDATE drama_parse_tasks
SET status = 'retry_wait',
error_code = NULL,
error_message = '连接超时,正在自动重试',
lease_owner = NULL,
lease_until = NULL
WHERE status = 'submit_unknown';
UPDATE generation_tasks
SET status = 'pending_submission',
next_submit_at = CURRENT_TIMESTAMP,
slot_released_at = CURRENT_TIMESTAMP,
error_code = NULL,
error_message = '连接超时,正在自动重试',
lease_owner = NULL,
lease_until = NULL
WHERE status = 'submit_unknown';
ALTER TABLE drama_parse_tasks DROP CONSTRAINT drama_parse_tasks_status_check;
ALTER TABLE drama_parse_tasks
ADD CONSTRAINT drama_parse_tasks_status_check
CHECK (status IN ('queued', 'running', 'retry_wait', 'succeeded', 'failed', 'cancel_requested', 'cancelled'));
ALTER TABLE generation_tasks DROP CONSTRAINT generation_tasks_status_check;
ALTER TABLE generation_tasks
ADD CONSTRAINT generation_tasks_status_check
CHECK (status IN ('pending_submission', 'submitting', 'submitted', 'processing', 'result_ready', 'downloading', 'succeeded', 'failed', 'cancel_requested', 'cancelled'));
DROP INDEX idx_drama_parse_tasks_recovery;
CREATE INDEX idx_drama_parse_tasks_recovery
ON drama_parse_tasks(status, lease_until)
WHERE status IN ('queued', 'running', 'retry_wait');
DROP INDEX uq_drama_episode_active_parse;
CREATE UNIQUE INDEX uq_drama_episode_active_parse
ON drama_parse_tasks(episode_id)
WHERE status IN ('queued', 'running', 'retry_wait', 'cancel_requested');
DROP INDEX uq_script_analysis_active_task;
CREATE UNIQUE INDEX uq_script_analysis_active_task
ON generation_tasks(script_analysis_id)
WHERE script_analysis_id IS NOT NULL
AND status IN ('pending_submission', 'submitting', 'submitted', 'processing', 'result_ready', 'downloading', 'cancel_requested');
@@ -0,0 +1,2 @@
-- 回滚:移除 redemption_codes 表的明文兑换码字段。
ALTER TABLE public.redemption_codes DROP COLUMN IF EXISTS code;
@@ -0,0 +1,4 @@
-- 在 redemption_codes 表中增加明文兑换码字段,使管理后台可持续查看完整兑换码。
ALTER TABLE public.redemption_codes ADD COLUMN code character varying(40);
COMMENT ON COLUMN public.redemption_codes.code IS '完整明文兑换码,仅对新建记录写入,历史记录为空';
@@ -0,0 +1,14 @@
-- 回滚图片生成积分流水备注,仅处理仍可关联到独立图片生成任务的记录。
UPDATE public.point_ledger AS ledger
SET remark = CASE ledger.remark
WHEN '图片生成' THEN '图片生成生成'
WHEN '图片生成入队失败返还' THEN '图片生成生成入队失败返还'
END
WHERE ledger.remark IN ('图片生成', '图片生成入队失败返还')
AND EXISTS (
SELECT 1
FROM public.generation_tasks AS task
WHERE task.id::text = ledger.business_id
AND task.task_type = 'image_generation'
AND task.input_data->>'product_image' = 'true'
);
@@ -0,0 +1,7 @@
-- 统一图片生成积分流水的业务备注,修正历史记录中的重复文案。
UPDATE public.point_ledger
SET remark = CASE remark
WHEN '图片生成生成' THEN '图片生成'
WHEN '图片生成生成入队失败返还' THEN '图片生成入队失败返还'
END
WHERE remark IN ('图片生成生成', '图片生成生成入队失败返还');
@@ -0,0 +1,2 @@
-- 回滚分镜 image_prompt 列;删除列会丢失已保存的分镜图提示词。
ALTER TABLE public.episode_storyboards DROP COLUMN IF EXISTS image_prompt;
@@ -0,0 +1,2 @@
-- 为分镜增加 image_prompt 列,用于保存分镜图生成提示词(与剧本解析生成的分镜内容提示词相互独立)。
ALTER TABLE public.episode_storyboards ADD COLUMN IF NOT EXISTS image_prompt text;