Files
NextEdu/scripts/add-ai-provider-visibility.js
SpecialX 9783be58c0 feat(scripts): add diagnostic, seed, and test scripts
- 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
2026-06-24 12:01:54 +08:00

119 lines
3.9 KiB
JavaScript
Raw 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.
/**
* 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);
});