Files
2026-06-19 18:45:55 +08:00

839 lines
33 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import StorageMain from "../storage/main";
import Files from "../file/main";
const versions = [
{
version: 0,
up: async (db: DB) => {
// await db.execute(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)`);
// console.log('db.insert', await db.insert(`INSERT INTO users (name, email) VALUES (?, ?)`,['Alice', 'alice@example.com']));
// console.log('db.select', await db.select(`SELECT * FROM users`));
// console.log('db.first', await db.first(`SELECT * FROM users`));
},
},
{
version: 1,
up: async (db: DB) => {
await db.execute(`CREATE TABLE IF NOT EXISTS data_sound_tts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
serverName TEXT,
serverTitle TEXT,
serverVersion TEXT,
text TEXT,
param TEXT,
status TEXT,
statusMsg TEXT,
jobResult TEXT,
startTime INTEGER,
endTime INTEGER,
resultWav TEXT
)`);
await db.execute(`CREATE TABLE IF NOT EXISTS data_sound_clone (
id INTEGER PRIMARY KEY AUTOINCREMENT,
serverName TEXT,
serverTitle TEXT,
serverVersion TEXT,
promptName TEXT,
promptWav TEXT,
promptText TEXT,
text TEXT,
param TEXT,
status TEXT,
statusMsg TEXT,
jobResult TEXT,
startTime INTEGER,
endTime INTEGER,
resultWav TEXT
)`);
await db.execute(`CREATE TABLE IF NOT EXISTS data_video_template (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
video TEXT
)`);
await db.execute(`CREATE TABLE IF NOT EXISTS data_video_gen (
id INTEGER PRIMARY KEY AUTOINCREMENT,
serverName TEXT,
serverTitle TEXT,
serverVersion TEXT,
videoTemplateId INTEGER,
videoTemplateName TEXT,
soundType TEXT,
soundTtsId INTEGER,
soundTtsText TEXT,
soundCloneId INTEGER,
soundCloneText TEXT,
param TEXT,
status TEXT,
statusMsg TEXT,
jobResult TEXT,
startTime INTEGER,
endTime INTEGER,
resultMp4 TEXT
)`);
},
},
{
version: 2,
up: async (db: DB) => {
await db.execute(`ALTER TABLE data_sound_tts ADD COLUMN result TEXT`);
await db.execute(`ALTER TABLE data_sound_clone ADD COLUMN result TEXT`);
await db.execute(`ALTER TABLE data_video_gen ADD COLUMN result TEXT`);
},
},
{
version: 3,
up: async (db: DB) => {
await db.execute(`ALTER TABLE data_video_gen ADD COLUMN soundCustomFile TEXT`);
},
},
{
version: 4,
up: async (db: DB) => {
await db.execute(`CREATE TABLE IF NOT EXISTS data_task (
id INTEGER PRIMARY KEY AUTOINCREMENT,
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
updatedAt INTEGER DEFAULT (strftime('%s', 'now')),
biz TEXT,
status TEXT,
statusMsg TEXT,
startTime INTEGER,
endTime INTEGER,
serverName TEXT,
serverTitle TEXT,
serverVersion TEXT,
param TEXT,
jobResult TEXT,
modelConfig TEXT,
result TEXT
)`);
},
},
{
version: 5,
up: async (db: DB) => {
// await db.execute(`DELETE FROM data_task where 1=1`);
// SoundClone
let records = await db.select(`SELECT * FROM data_sound_clone`);
for (const r of records) {
const values = [
"SoundClone",
r.status,
r.statusMsg,
r.startTime,
r.endTime,
r.serverName,
r.serverTitle,
r.serverVersion,
r.param,
r.jobResult,
JSON.stringify({
promptName: r.promptName,
promptWav: r.promptWav,
promptText: r.promptText,
text: r.text,
}),
JSON.stringify({
url: r.resultWav,
}),
];
await db.insert(
`INSERT INTO data_task
(biz, status, statusMsg, startTime, endTime, serverName, serverTitle, serverVersion, param, jobResult, modelConfig, result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
values
);
}
// SoundTts
records = await db.select(`SELECT * FROM data_sound_tts`);
for (const r of records) {
const values = [
"SoundTts",
r.status,
r.statusMsg,
r.startTime,
r.endTime,
r.serverName,
r.serverTitle,
r.serverVersion,
r.param,
r.jobResult,
JSON.stringify({
text: r.text,
}),
JSON.stringify({
url: r.resultWav,
}),
];
await db.insert(
`INSERT INTO data_task
(biz, status, statusMsg, startTime, endTime, serverName, serverTitle, serverVersion, param, jobResult, modelConfig, result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
values
);
}
// VideoGen
records = await db.select(`SELECT * FROM data_video_gen`);
for (const r of records) {
const values = [
"VideoGen",
r.status,
r.statusMsg,
r.startTime,
r.endTime,
r.serverName,
r.serverTitle,
r.serverVersion,
r.param,
r.jobResult,
JSON.stringify({
videoTemplateId: r.videoTemplateId,
videoTemplateName: r.videoTemplateName,
soundType: r.soundType,
soundTtsId: r.soundTtsId,
soundTtsText: r.soundTtsText,
soundCloneId: r.soundCloneId,
soundCloneText: r.soundCloneText,
soundCustomFile: r.soundCustomFile,
}),
JSON.stringify({
url: r.resultMp4,
}),
];
await db.insert(
`INSERT INTO data_task
(biz, status, statusMsg, startTime, endTime, serverName, serverTitle, serverVersion, param, jobResult, modelConfig, result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
values
);
}
},
},
{
version: 6,
up: async (db: DB) => {
await db.execute(`CREATE TABLE IF NOT EXISTS data_storage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
createdAt INTEGER DEFAULT (strftime('%s', 'now')),
updatedAt INTEGER DEFAULT (strftime('%s', 'now')),
biz TEXT,
title TEXT,
sort INTEGER,
content TEXT
)`);
},
},
{
version: 7,
up: async (db: DB) => {
const records = await StorageMain.get("soundClonePrompt", "records", []);
for (const r of records) {
const values = [
"SoundPrompt",
r.name,
JSON.stringify({
url: r.promptWav,
promptText: r.promptText,
}),
];
await db.insert(
`INSERT INTO data_storage (biz, title, content)
VALUES (?, ?, ?)`,
values
);
}
},
},
{
version: 8,
up: async (db: DB) => {
await db.execute(`ALTER TABLE data_task ADD COLUMN title TEXT`);
const records = await db.select(`SELECT * FROM data_task`);
for (const r of records) {
let modelConfig: any = {};
try {
modelConfig = JSON.parse(r.modelConfig);
} catch (e) {
modelConfig = {};
}
let title = "";
if (r.biz === "SoundTts" || r.biz === "SoundClone") {
title = Files.textToName(modelConfig.text);
} else if (r.biz === "VideoGen") {
title = Files.textToName([modelConfig.videoTemplateName, modelConfig.soundTtsText].join("_"));
} else if (r.biz === "VideoGenFlow") {
title = Files.textToName([modelConfig.videoTemplateName, modelConfig.text].join("_"));
}
await db.execute(`UPDATE data_task SET title = ? WHERE id = ?`, [title, r.id]);
}
},
},
{
version: 9,
up: async (db: DB) => {
const records = await db.select(`SELECT * FROM data_task where biz in ('SoundTts', 'SoundClone')`);
for (const r of records) {
const modelConfigOld = JSON.parse(r.modelConfig);
const paramOld = JSON.parse(r.param);
const modelConfig: any = {
type: r.biz,
ttsParam: r.biz === "SoundTts" ? paramOld : undefined,
cloneParam: r.biz === "SoundClone" ? paramOld : undefined,
...modelConfigOld,
};
const values = [
"SoundGenerate",
r.title,
r.status,
r.statusMsg,
r.startTime,
r.endTime,
r.serverName,
r.serverTitle,
r.serverVersion,
JSON.stringify({}),
r.jobResult,
JSON.stringify(modelConfig),
r.result,
];
await db.insert(
`INSERT INTO data_task
(biz, title, status, statusMsg, startTime, endTime, serverName, serverTitle, serverVersion, param, jobResult, modelConfig, result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
values
);
await db.execute(`DELETE FROM data_task WHERE id = ?`, [r.id]);
}
},
},
{
version: 10,
up: async (db: DB) => {
await db.execute(`ALTER TABLE data_task
ADD COLUMN type INTEGER DEFAULT 1`);
},
},
{
version: 11,
up: async (db: DB) => {
await db.execute(`ALTER TABLE data_video_template
ADD COLUMN info TEXT`);
},
},
{
version: 12,
up: async (db: DB) => {
// 平台账号表
await db.execute(`CREATE TABLE IF NOT EXISTS platform_accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL,
nickname TEXT,
uid TEXT,
avatar TEXT,
cookies TEXT,
tokens TEXT,
loginStatus TEXT DEFAULT 'pending',
expiresAt INTEGER,
createdAt INTEGER,
updatedAt INTEGER
)`);
// 发布任务表
await db.execute(`CREATE TABLE IF NOT EXISTS publish_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
videoPath TEXT,
coverPath TEXT,
platforms TEXT,
publishConfig TEXT,
scheduledTime INTEGER,
status TEXT DEFAULT 'pending',
createdAt INTEGER,
updatedAt INTEGER
)`);
// 发布历史表
await db.execute(`CREATE TABLE IF NOT EXISTS publish_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
taskId INTEGER,
platform TEXT,
accountId INTEGER,
videoUrl TEXT,
videoId TEXT,
status TEXT,
errorMessage TEXT,
publishedAt INTEGER,
viewCount INTEGER DEFAULT 0,
likeCount INTEGER DEFAULT 0
)`);
// 创建索引
await db.execute(`CREATE INDEX IF NOT EXISTS idx_platform_accounts_platform ON platform_accounts(platform)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_publish_tasks_status ON publish_tasks(status)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_publish_history_taskId ON publish_history(taskId)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_publish_history_platform ON publish_history(platform)`);
},
},
{
version: 13,
up: async (db: DB) => {
// 封面模板表
await db.execute(`CREATE TABLE IF NOT EXISTS cover_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
thumbnail_path TEXT,
config TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)`);
// 创建索引以提升查询性能
await db.execute(`CREATE INDEX IF NOT EXISTS idx_cover_templates_created_at ON cover_templates(created_at DESC)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_cover_templates_name ON cover_templates(name)`);
},
},
{
version: 14,
up: async (db: DB) => {
// 关键词分组表 - 用于字幕特效管理
await db.execute(`CREATE TABLE IF NOT EXISTS keyword_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
keywords TEXT NOT NULL,
color TEXT,
effectId TEXT,
styleOverride TEXT,
effectConfig TEXT,
styleConfig TEXT,
createdAt INTEGER,
updatedAt INTEGER
)`);
// 创建索引以提升查询性能
await db.execute(`CREATE INDEX IF NOT EXISTS idx_keyword_groups_name ON keyword_groups(name)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_keyword_groups_createdAt ON keyword_groups(createdAt DESC)`);
},
},
{
version: 15,
up: async (db: DB) => {
// 字幕样式表 - 用于保存字幕样式配置
await db.execute(`CREATE TABLE IF NOT EXISTS subtitle_styles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
preview TEXT,
fontName TEXT NOT NULL,
fontSize INTEGER,
fontColor TEXT,
outlineColor TEXT,
outlineWidth INTEGER,
shadowOffset INTEGER,
backgroundColor TEXT,
backgroundOpacity REAL,
position TEXT,
alignment TEXT,
createdAt INTEGER,
updatedAt INTEGER
)`);
// 创建索引以提升查询性能
await db.execute(`CREATE INDEX IF NOT EXISTS idx_subtitle_styles_name ON subtitle_styles(name)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_subtitle_styles_createdAt ON subtitle_styles(createdAt DESC)`);
},
},
{
version: 16,
up: async (db: DB) => {
// 为 cover_templates 表添加 is_system 字段,标识系统默认模板
await db.execute(`
ALTER TABLE cover_templates
ADD COLUMN is_system INTEGER DEFAULT 0
`);
// 为 subtitle_styles 表添加 is_system 字段,标识系统默认样式
await db.execute(`
ALTER TABLE subtitle_styles
ADD COLUMN is_system INTEGER DEFAULT 0
`);
console.log('[Migration v16] 已添加 is_system 字段到 cover_templates 和 subtitle_styles 表');
},
},
{
version: 17,
up: async (db: DB) => {
// 为 subtitle_styles 表添加 shadowColor 和 shadowBlur 字段
try {
await db.execute(`
ALTER TABLE subtitle_styles
ADD COLUMN shadowColor TEXT DEFAULT '#000000'
`);
console.log('[Migration v17] 已添加 shadowColor 字段到 subtitle_styles 表');
} catch (error: any) {
if (!error.message?.includes('duplicate column')) {
throw error;
}
}
try {
await db.execute(`
ALTER TABLE subtitle_styles
ADD COLUMN shadowBlur INTEGER DEFAULT 4
`);
console.log('[Migration v17] 已添加 shadowBlur 字段到 subtitle_styles 表');
} catch (error: any) {
if (!error.message?.includes('duplicate column')) {
throw error;
}
}
},
},
{
version: 18,
up: async (db: DB) => {
console.log('[Migration v18] 开始处理封面模板系统迁移');
try {
// 第一步:删除旧的系统模板(system-cover-01/02/03/04
const oldSystemTemplates = [
'system-cover-01',
'system-cover-02',
'system-cover-03',
'system-cover-04'
];
for (const templateId of oldSystemTemplates) {
try {
await db.execute(
`DELETE FROM cover_templates WHERE id = ?`,
[templateId]
);
console.log(`[Migration v18] ✅ 已删除旧系统模板: ${templateId}`);
} catch (error) {
// 如果模板不存在,忽略错误(新用户的情况)
console.log(`[Migration v18] ℹ️ 旧系统模板不存在(预期行为): ${templateId}`);
}
}
// 第二步:将4个自定义模板标记为新的系统模板(仅对现有模板操作)
const newSystemTemplates = [
'485183be-6eed-4eae-b74a-8591f08b69fa', // 黄白实描
'8f17c467-c69c-45b2-8011-94a6c3071d16', // 斜黄白
'6404718a-1b1c-4148-9807-ad3c57a53e0c', // 蓝底白字
'705fa010-5bf3-4041-b624-0fd364bea5ad' // 大四字报
];
for (const templateId of newSystemTemplates) {
try {
const result = await db.execute(
`UPDATE cover_templates SET is_system = 1 WHERE id = ?`,
[templateId]
);
console.log(`[Migration v18] ✅ 已标记为系统模板: ${templateId}`);
} catch (error) {
// 如果模板不存在,忽略(新用户的情况)
console.log(`[Migration v18] ️ 模板不存在,跳过标记: ${templateId}`);
}
}
console.log('[Migration v18] ✅ 封面模板系统迁移完成(仅对现有模板生效)');
console.log('[Migration v18] 💡 提示:新用户请运行 \"ipAgent:exportCoverTemplatesToConfig\" 来导出这4个模板到配置文件');
} catch (error: any) {
console.error('[Migration v18] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 19,
up: async (db: DB) => {
console.log('[Migration v19] 开始音效系统初始化');
try {
// 第一步:创建音效库表
await db.execute(`CREATE TABLE IF NOT EXISTS sound_effects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
category TEXT,
type TEXT,
audio_source TEXT,
volume REAL DEFAULT 0.7,
duration REAL,
metadata TEXT,
created_at INTEGER,
updated_at INTEGER,
is_system INTEGER DEFAULT 0
)`);
console.log('[Migration v19] ✅ 创建 sound_effects 表');
// 第二步:创建用户自定义音效表
await db.execute(`CREATE TABLE IF NOT EXISTS user_sound_effects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
file_path TEXT NOT NULL,
duration REAL,
file_size INTEGER,
created_at INTEGER,
updated_at INTEGER
)`);
console.log('[Migration v19] ✅ 创建 user_sound_effects 表');
// 第三步:检查并添加 keyword_groups 新字段
try {
await db.execute(`ALTER TABLE keyword_groups ADD COLUMN sound_effect_id TEXT`);
console.log('[Migration v19] ✅ 为 keyword_groups 添加 sound_effect_id 列');
} catch (error: any) {
if (error.message?.includes('duplicate')) {
console.log('[Migration v19] ️ sound_effect_id 列已存在,跳过');
} else {
throw error;
}
}
try {
await db.execute(`ALTER TABLE keyword_groups ADD COLUMN sound_effect_config TEXT`);
console.log('[Migration v19] ✅ 为 keyword_groups 添加 sound_effect_config 列');
} catch (error: any) {
if (error.message?.includes('duplicate')) {
console.log('[Migration v19] ️ sound_effect_config 列已存在,跳过');
} else {
throw error;
}
}
console.log('[Migration v19] ✅ 音效系统初始化完成');
} catch (error: any) {
console.error('[Migration v19] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 20,
up: async (db: DB) => {
console.log('[Migration v20] 开始贴纸系统初始化');
try {
// 添加 stickerId 列
try {
await db.execute(`ALTER TABLE keyword_groups ADD COLUMN stickerId TEXT`);
console.log('[Migration v20] ✅ 为 keyword_groups 添加 stickerId 列');
} catch (error: any) {
if (error.message?.includes('duplicate')) {
console.log('[Migration v20] ️ stickerId 列已存在,跳过');
} else {
throw error;
}
}
// 添加 stickerConfig 列
try {
await db.execute(`ALTER TABLE keyword_groups ADD COLUMN stickerConfig TEXT`);
console.log('[Migration v20] ✅ 为 keyword_groups 添加 stickerConfig 列');
} catch (error: any) {
if (error.message?.includes('duplicate')) {
console.log('[Migration v20] ️ stickerConfig 列已存在,跳过');
} else {
throw error;
}
}
// 添加 effectDuration 列
try {
await db.execute(`ALTER TABLE keyword_groups ADD COLUMN effectDuration REAL`);
console.log('[Migration v20] ✅ 为 keyword_groups 添加 effectDuration 列');
} catch (error: any) {
if (error.message?.includes('duplicate')) {
console.log('[Migration v20] ️ effectDuration 列已存在,跳过');
} else {
throw error;
}
}
console.log('[Migration v20] ✅ 贴纸系统初始化完成');
} catch (error: any) {
console.error('[Migration v20] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 21,
up: async (db: DB) => {
console.log('[Migration v21] 开始更新平台账号昵称');
try {
// 更新所有平台账号的昵称为"用户"
await db.execute(`
UPDATE platform_accounts
SET nickname = '用户'
WHERE nickname IN ('抖音用户', '快手用户', '视频号用户', '小红书用户')
`);
console.log('[Migration v21] ✅ 已更新所有平台账号昵称为"用户"');
} catch (error: any) {
console.error('[Migration v21] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 22,
up: async (db: DB) => {
console.log('[Migration v22] 开始创建用户认证系统表');
try {
// 创建 users 表 - 用户基本信息
await db.execute(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
avatar TEXT,
role TEXT DEFAULT 'user',
status TEXT DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_login_at DATETIME
)`);
console.log('[Migration v22] ✅ 创建 users 表');
// 创建 user_subscriptions 表 - 用户订阅时长管理
await db.execute(`CREATE TABLE IF NOT EXISTS user_subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER UNIQUE NOT NULL,
subscription_type TEXT DEFAULT 'trial',
expires_at DATETIME NOT NULL,
trial_started_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)`);
console.log('[Migration v22] ✅ 创建 user_subscriptions 表');
// 创建 user_time_logs 表 - 时长变更日志
await db.execute(`CREATE TABLE IF NOT EXISTS user_time_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
operator_id INTEGER,
action TEXT NOT NULL,
days_added INTEGER,
previous_expires_at DATETIME,
new_expires_at DATETIME,
reason TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (operator_id) REFERENCES users(id)
)`);
console.log('[Migration v22] ✅ 创建 user_time_logs 表');
// 创建 password_resets 表 - 密码重置记录(管理员手动重置)
await db.execute(`CREATE TABLE IF NOT EXISTS password_resets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
operator_id INTEGER,
old_password_hash TEXT,
reset_at DATETIME DEFAULT CURRENT_TIMESTAMP,
reason TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (operator_id) REFERENCES users(id)
)`);
console.log('[Migration v22] ✅ 创建 password_resets 表');
console.log('[Migration v22] ✅ 用户认证系统表创建完成');
} catch (error: any) {
console.error('[Migration v22] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 23,
up: async (db: DB) => {
console.log('[Migration v23] 开始创建字幕模板表和添加 readonly 列');
try {
// 创建 subtitle_templates 表 - 用于保存字幕模板
await db.execute(`CREATE TABLE IF NOT EXISTS subtitle_templates (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
config TEXT,
is_system INTEGER DEFAULT 0,
readonly INTEGER DEFAULT 0,
created_at INTEGER,
updated_at INTEGER
)`);
console.log('[Migration v23] ✅ 创建 subtitle_templates 表');
// 为 cover_templates 表添加 readonly 列(如果不存在)
try {
await db.execute(`
ALTER TABLE cover_templates
ADD COLUMN readonly INTEGER DEFAULT 0
`);
console.log('[Migration v23] ✅ 为 cover_templates 表添加 readonly 列');
} catch (error: any) {
if (error.message && error.message.includes('duplicate column name')) {
console.log('[Migration v23] ️ cover_templates 表已有 readonly 列');
} else {
throw error;
}
}
// 为 subtitle_templates 表创建索引
await db.execute(`CREATE INDEX IF NOT EXISTS idx_subtitle_templates_name ON subtitle_templates(name)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_subtitle_templates_is_system ON subtitle_templates(is_system)`);
console.log('[Migration v23] ✅ 创建 subtitle_templates 表的索引');
console.log('[Migration v23] ✅ 字幕模板表创建完成');
} catch (error: any) {
console.error('[Migration v23] ❌ 迁移失败:', error);
throw error;
}
},
},
{
version: 24,
up: async (db: DB) => {
console.log('[Migration v24] 开始创建素材库表');
await db.execute(`CREATE TABLE IF NOT EXISTS data_video_material (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
path TEXT NOT NULL,
type TEXT NOT NULL,
info TEXT,
createdAt INTEGER,
updatedAt INTEGER
)`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_video_material_updatedAt ON data_video_material(updatedAt DESC)`);
console.log('[Migration v24] 素材库表创建完成');
},
},
];
export default {
versions,
};