- Add add-ai-provider-visibility and add-missing-columns migration scripts - Add clear-error-book, seed-error-book, diagnose-error-book scripts - Add diagnose-tables and create-missing-tables scripts - Add test-failing-modules and test-teacher-pages test scripts
119 lines
3.9 KiB
JavaScript
119 lines
3.9 KiB
JavaScript
/**
|
||
* V3: 为 ai_providers 表添加 visibility 列(public/private)
|
||
*
|
||
* 背景:
|
||
* - 管理员可发布 public provider,全员可用
|
||
* - 普通用户(teacher/student 等)可创建 private provider,仅本人可见
|
||
*
|
||
* 缺失列清单:
|
||
* - ai_providers.visibility (可见性:public | private,默认 private)
|
||
* - 索引 ai_provider_visibility_idx (visibility)
|
||
* - 索引 ai_provider_created_by_idx (created_by)
|
||
*
|
||
* 注意:默认值设为 'private',保证历史记录不会意外对全员公开。
|
||
* 管理员可在 UI 中将需要共享的 provider 改为 public。
|
||
*/
|
||
require("dotenv/config");
|
||
const mysql = require("mysql2/promise");
|
||
|
||
const ALTER_OPERATIONS = [
|
||
{
|
||
table: "ai_providers",
|
||
description: "添加 visibility 列(public/private 可见性)",
|
||
sql: [
|
||
"ALTER TABLE `ai_providers` ADD COLUMN `visibility` ENUM('public','private') NOT NULL DEFAULT 'private' AFTER `is_default`",
|
||
"ALTER TABLE `ai_providers` ADD INDEX `ai_provider_visibility_idx` (`visibility`)",
|
||
"ALTER TABLE `ai_providers` ADD INDEX `ai_provider_created_by_idx` (`created_by`)",
|
||
],
|
||
},
|
||
];
|
||
|
||
async function columnExists(conn, table, column) {
|
||
const [rows] = await conn.execute(
|
||
`SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?`,
|
||
[table, column],
|
||
);
|
||
return rows.length > 0;
|
||
}
|
||
|
||
async function indexExists(conn, table, indexName) {
|
||
const [rows] = await conn.execute(
|
||
`SELECT INDEX_NAME FROM information_schema.STATISTICS
|
||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?
|
||
LIMIT 1`,
|
||
[table, indexName],
|
||
);
|
||
return rows.length > 0;
|
||
}
|
||
|
||
async function main() {
|
||
const conn = await mysql.createConnection({ uri: process.env.DATABASE_URL });
|
||
console.log("✅ 已连接数据库\n");
|
||
|
||
for (const op of ALTER_OPERATIONS) {
|
||
console.log(`${"=".repeat(60)}`);
|
||
console.log(`表: ${op.table} — ${op.description}`);
|
||
console.log("=".repeat(60));
|
||
|
||
for (const sql of op.sql) {
|
||
try {
|
||
const isColumn = sql.includes("ADD COLUMN");
|
||
const isIndex = sql.includes("ADD INDEX");
|
||
|
||
if (isColumn) {
|
||
const match = sql.match(/ADD COLUMN `(\w+)`/);
|
||
if (match && await columnExists(conn, op.table, match[1])) {
|
||
console.log(` ⏭️ 跳过已存在的列: ${match[1]}`);
|
||
continue;
|
||
}
|
||
} else if (isIndex) {
|
||
const match = sql.match(/ADD INDEX `(\w+)`/);
|
||
if (match && await indexExists(conn, op.table, match[1])) {
|
||
console.log(` ⏭️ 跳过已存在的索引: ${match[1]}`);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
await conn.execute(sql);
|
||
console.log(` ✅ 执行成功: ${sql.slice(0, 100)}...`);
|
||
} catch (err) {
|
||
if (err.code === "ER_DUP_FIELDNAME" || err.code === "ER_DUP_KEYNAME" || err.errno === 1060 || err.errno === 1061) {
|
||
console.log(` ⏭️ 已存在,跳过: ${err.message}`);
|
||
} else {
|
||
console.error(` ❌ 执行失败: ${err.message}`);
|
||
console.error(` SQL: ${sql}`);
|
||
}
|
||
}
|
||
}
|
||
console.log("");
|
||
}
|
||
|
||
// 验证最终列结构
|
||
console.log(`${"=".repeat(60)}`);
|
||
console.log("最终验证 — 检查 visibility 列是否已添加");
|
||
console.log("=".repeat(60));
|
||
|
||
const checks = [
|
||
["ai_providers", "visibility"],
|
||
];
|
||
|
||
let allOk = true;
|
||
for (const [table, col] of checks) {
|
||
const exists = await columnExists(conn, table, col);
|
||
const status = exists ? "✅" : "❌";
|
||
if (!exists) allOk = false;
|
||
console.log(` ${status} ${table}.${col}`);
|
||
}
|
||
|
||
console.log(allOk ? "\n✅ visibility 列已就绪" : "\n❌ 仍有缺失列,请检查错误");
|
||
|
||
await conn.end();
|
||
process.exitCode = allOk ? 0 : 1;
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error("致命错误:", err);
|
||
process.exit(1);
|
||
});
|