feat: implement QuestionBank CRUD with pagination and template query

- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
@@ -0,0 +1,91 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddKnowledgeBaseEnhancements1737800000000 implements MigrationInterface {
name = 'AddKnowledgeBaseEnhancements1737800000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// Create knowledge base group table
await queryRunner.query(`
CREATE TABLE "knowledge_groups" (
"id" varchar PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"description" varchar,
"color" varchar NOT NULL DEFAULT '#3B82F6',
"user_id" varchar NOT NULL,
"created_at" datetime NOT NULL DEFAULT (datetime('now')),
"updated_at" datetime NOT NULL DEFAULT (datetime('now'))
)
`);
// Create document group related tables
await queryRunner.query(`
CREATE TABLE "knowledge_base_groups" (
"knowledge_base_id" varchar NOT NULL,
"group_id" varchar NOT NULL,
"created_at" datetime NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY ("knowledge_base_id", "group_id"),
FOREIGN KEY ("knowledge_base_id") REFERENCES "knowledge_base" ("id") ON DELETE CASCADE,
FOREIGN KEY ("group_id") REFERENCES "knowledge_groups" ("id") ON DELETE CASCADE
)
`);
// Create search history table
await queryRunner.query(`
CREATE TABLE "search_history" (
"id" varchar PRIMARY KEY NOT NULL,
"user_id" varchar NOT NULL,
"title" varchar NOT NULL,
"selected_groups" text,
"created_at" datetime NOT NULL DEFAULT (datetime('now')),
"updated_at" datetime NOT NULL DEFAULT (datetime('now'))
)
`);
// Create conversation message table
await queryRunner.query(`
CREATE TABLE "chat_messages" (
"id" varchar PRIMARY KEY NOT NULL,
"search_history_id" varchar NOT NULL,
"role" varchar NOT NULL CHECK ("role" IN ('user', 'assistant')),
"content" text NOT NULL,
"sources" text,
"created_at" datetime NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY ("search_history_id") REFERENCES "search_history" ("id") ON DELETE CASCADE
)
`);
// Add pdf_path field to knowledge_base table
await queryRunner.query(`
ALTER TABLE "knowledge_base" ADD COLUMN "pdf_path" varchar
`);
// Create index
await queryRunner.query(
`CREATE INDEX "IDX_knowledge_groups_user_id" ON "knowledge_groups" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_search_history_user_id" ON "search_history" ("user_id")`,
);
await queryRunner.query(
`CREATE INDEX "IDX_chat_messages_search_history_id" ON "chat_messages" ("search_history_id")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Delete index
await queryRunner.query(`DROP INDEX "IDX_chat_messages_search_history_id"`);
await queryRunner.query(`DROP INDEX "IDX_search_history_user_id"`);
await queryRunner.query(`DROP INDEX "IDX_knowledge_groups_user_id"`);
// Delete pdf_path field
await queryRunner.query(
`ALTER TABLE "knowledge_base" DROP COLUMN "pdf_path"`,
);
// Delete table
await queryRunner.query(`DROP TABLE "chat_messages"`);
await queryRunner.query(`DROP TABLE "search_history"`);
await queryRunner.query(`DROP TABLE "knowledge_base_groups"`);
await queryRunner.query(`DROP TABLE "knowledge_groups"`);
}
}
@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class RemoveSupportsVisionColumn1739260000000 implements MigrationInterface {
name = 'RemoveSupportsVisionColumn1739260000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// Remove supportsVision column from model_configs table
// This column is no longer needed as we now use ModelType.VISION instead
await queryRunner.query(`
ALTER TABLE "model_configs" DROP COLUMN "supportsVision"
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Restore supportsVision column in case of rollback
await queryRunner.query(`
ALTER TABLE "model_configs" ADD COLUMN "supportsVision" boolean NOT NULL DEFAULT 0
`);
}
}
@@ -0,0 +1,91 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddDefaultTenant1772329237979 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// 1. Insert "Default" tenant if it doesn't exist.
// We use a predefined UUID or let the DB generate it.
// Assuming Postgres/MySQL compatible uuid generation isn't strictly standard here,
// we'll insert and get the ID back, or use a workaround for SQLite if it's SQLite.
// Actually, since this is a TypeORM setup, we can use standard SQL.
// This is a bit tricky to write purely in SQL that works across all DBs (SQLite/Postgres/MySQL)
// without knowing the exact DB. AuraK seems to use SQLite locally by default.
// First, check if there's any record in the tenant table.
const tenants = await queryRunner.query(
`SELECT id FROM "tenant" WHERE "name" = 'Default'`,
);
let defaultTenantId;
if (tenants && tenants.length > 0) {
defaultTenantId = tenants[0].id;
} else {
// Create it with a JS generated UUID to be database agnostic
const crypto = require('crypto');
defaultTenantId = crypto.randomUUID();
const now = new Date().toISOString();
await queryRunner.query(
`INSERT INTO "tenant" (id, name, description, "createdAt", "updatedAt") VALUES (?, ?, ?, ?, ?)`,
[
defaultTenantId,
'Default',
'Default tenant created by migration',
now,
now,
],
);
// Create tenant settings
const settingsId = crypto.randomUUID();
await queryRunner.query(
`INSERT INTO "tenant_setting" (id, "tenantId", "createdAt", "updatedAt") VALUES (?, ?, ?, ?)`,
[settingsId, defaultTenantId, now, now],
);
}
// 2. Assign the Default tenant to all relevant existing records that have no tenantId
const tablesToUpdate = [
'user',
'knowledge_base',
'knowledge_group',
'search_history',
'note',
'model_config',
];
for (const table of tablesToUpdate) {
// Check if table exists first (some might be missing if DB is fresh)
try {
await queryRunner.query(
`UPDATE "${table}" SET "tenantId" = ? WHERE "tenantId" IS NULL`,
[defaultTenantId],
);
} catch (e) {
console.warn(
`Could not update table ${table}, it might not exist or the tenantId column might not exist yet.`,
);
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
// We don't necessarily want to delete the tenant data in a down migration
// as it would orphan all the records or require setting them to NULL.
// But for completeness, we can set them back to NULL.
const tablesToUpdate = [
'user',
'knowledge_base',
'knowledge_group',
'search_history',
'note',
'model_config',
];
for (const table of tablesToUpdate) {
try {
await queryRunner.query(`UPDATE "${table}" SET "tenantId" = NULL`);
} catch (e) {
// Ignore
}
}
}
}
@@ -0,0 +1,57 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddTenantModule1772334811108 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// 1. Ensure 'tenants' table exists before we insert into it.
// This assumes the schema definition migrations have run or TypeORM synchronize handled the tables.
// We will insert a system default tenant.
await queryRunner.query(`
INSERT INTO "tenants" ("id", "name", "description", "isActive", "created_at", "updated_at")
SELECT '00000000-0000-0000-0000-000000000000', 'Default Tenant', 'System Default Organization', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
WHERE NOT EXISTS (SELECT 1 FROM "tenants" WHERE "id" = '00000000-0000-0000-0000-000000000000');
`);
// 2. Link existing users to the default tenant
await queryRunner.query(
`UPDATE "users" SET "tenant_id" = '00000000-0000-0000-0000-000000000000' WHERE "tenant_id" IS NULL;`,
);
// 3. Link existing knowledge bases to the default tenant
await queryRunner.query(
`UPDATE "knowledge_bases" SET "tenant_id" = '00000000-0000-0000-0000-000000000000' WHERE "tenant_id" IS NULL;`,
);
// 4. Link existing knowledge groups to the default tenant
await queryRunner.query(
`UPDATE "knowledge_groups" SET "tenant_id" = '00000000-0000-0000-0000-000000000000' WHERE "tenant_id" IS NULL;`,
);
// 5. Link existing search histories to the default tenant
await queryRunner.query(
`UPDATE "search_history" SET "tenant_id" = '00000000-0000-0000-0000-000000000000' WHERE "tenant_id" IS NULL;`,
);
// 6. Link existing notes to the default tenant
await queryRunner.query(
`UPDATE "notes" SET "tenant_id" = '00000000-0000-0000-0000-000000000000' WHERE "tenant_id" IS NULL;`,
);
// 7. Make the existing admin users SUPER_ADMIN
await queryRunner.query(
`UPDATE "users" SET "role" = 'SUPER_ADMIN' WHERE "isAdmin" = 1;`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Reverse operations if needed. Note: We do not delete the Default tenant here because it might be in active use.
// But we could nullify the links if we were strictly rolling back to a state without tenant links.
/*
await queryRunner.query(`UPDATE "users" SET "tenant_id" = NULL WHERE "tenant_id" = '00000000-0000-0000-0000-000000000000';`);
await queryRunner.query(`UPDATE "knowledge_bases" SET "tenant_id" = NULL WHERE "tenant_id" = '00000000-0000-0000-0000-000000000000';`);
await queryRunner.query(`UPDATE "knowledge_groups" SET "tenant_id" = NULL WHERE "tenant_id" = '00000000-0000-0000-0000-000000000000';`);
await queryRunner.query(`UPDATE "search_history" SET "tenant_id" = NULL WHERE "tenant_id" = '00000000-0000-0000-0000-000000000000';`);
await queryRunner.query(`UPDATE "notes" SET "tenant_id" = NULL WHERE "tenant_id" = '00000000-0000-0000-0000-000000000000';`);
*/
}
}
@@ -0,0 +1,18 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddParentIdToKnowledgeGroups1772340000000 implements MigrationInterface {
name = 'AddParentIdToKnowledgeGroups1772340000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// Add parent_id column to knowledge_groups table
await queryRunner.query(
`ALTER TABLE "knowledge_groups" ADD COLUMN "parent_id" text REFERENCES "knowledge_groups"("id") ON DELETE SET NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "knowledge_groups" DROP COLUMN "parent_id"`,
);
}
}
@@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddAssessmentTablesManual1773198650000 implements MigrationInterface {
name = 'AddAssessmentTablesManual1773198650000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "assessment_sessions" ("id" varchar PRIMARY KEY NOT NULL, "user_id" varchar NOT NULL, "knowledge_base_id" varchar NOT NULL, "thread_id" varchar, "status" varchar CHECK( "status" IN ('IN_PROGRESS','COMPLETED') ) NOT NULL DEFAULT ('IN_PROGRESS'), "final_score" float, "final_report" text, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`,
);
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "assessment_questions" ("id" varchar PRIMARY KEY NOT NULL, "session_id" varchar NOT NULL, "question_text" text NOT NULL, "key_points" text, "difficulty" varchar, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`,
);
await queryRunner.query(
`CREATE TABLE IF NOT EXISTS "assessment_answers" ("id" varchar PRIMARY KEY NOT NULL, "question_id" varchar NOT NULL, "user_answer" text NOT NULL, "score" float, "feedback" text, "is_follow_up" boolean NOT NULL DEFAULT (0), "created_at" datetime NOT NULL DEFAULT (datetime('now')), "updated_at" datetime NOT NULL DEFAULT (datetime('now')))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "assessment_answers"`);
await queryRunner.query(`DROP TABLE "assessment_questions"`);
await queryRunner.query(`DROP TABLE "assessment_sessions"`);
}
}
@@ -0,0 +1,35 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddFeishuBotKnowledgeFields1773200000000 implements MigrationInterface {
name = 'AddFeishuBotKnowledgeFields1773200000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// 添加知识库ID字段
await queryRunner.query(`
ALTER TABLE feishu_bots
ADD COLUMN knowledge_base_id VARCHAR(36) NULL;
`);
// 添加知识组ID字段
await queryRunner.query(`
ALTER TABLE feishu_bots
ADD COLUMN knowledge_group_id VARCHAR(36) NULL;
`);
// 添加外键约束(可选,如果需要引用完整性)
// 注意:这里假设 knowledge_bases 和 knowledge_groups 表存在
// 如果表不存在,可以先创建或移除外键约束
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE feishu_bots
DROP COLUMN knowledge_base_id;
`);
await queryRunner.query(`
ALTER TABLE feishu_bots
DROP COLUMN knowledge_group_id;
`);
}
}
@@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateFeishuAssessmentSessionTable1773200000001 implements MigrationInterface {
name = 'CreateFeishuAssessmentSessionTable1773200000001';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE feishu_assessment_sessions (
id VARCHAR(36) PRIMARY KEY,
bot_id VARCHAR(36) NOT NULL,
open_id VARCHAR(255) NOT NULL,
assessment_session_id VARCHAR(36) NOT NULL,
status ENUM('active', 'completed', 'cancelled') DEFAULT 'active',
current_question_index INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_bot_open (bot_id, open_id),
INDEX idx_assessment_session (assessment_session_id),
CONSTRAINT fk_feishu_assessment_bot
FOREIGN KEY (bot_id)
REFERENCES feishu_bots(id)
ON DELETE CASCADE
);
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
DROP TABLE feishu_assessment_sessions;
`);
}
}
@@ -0,0 +1,47 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class DropTenantFromNotes1773210000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Log tables to help debug
const tables = await queryRunner.getTables();
console.log('Database tables found:', tables.map(t => t.name).join(', '));
const noteTables = ['note', 'notes'];
const noteCategoryTables = ['note_category', 'note_categories'];
const tenantColumns = ['tenant_id', 'tenantId', 'tenantid'];
// 1. Drop from notes tables
for (const table of noteTables) {
for (const col of tenantColumns) {
try {
await queryRunner.query(`ALTER TABLE "${table}" DROP COLUMN "${col}"`);
console.log(`Successfully dropped ${col} from ${table}`);
} catch (e) {
// Ignore - column or table might not exist
}
}
}
// 2. Drop from note_categories tables
for (const table of noteCategoryTables) {
for (const col of tenantColumns) {
try {
await queryRunner.query(`ALTER TABLE "${table}" DROP COLUMN "${col}"`);
console.log(`Successfully dropped ${col} from ${table}`);
} catch (e) {
// Ignore
}
}
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
// To reverse, we would add the column back.
try {
await queryRunner.query('ALTER TABLE "notes" ADD COLUMN "tenant_id" varchar');
await queryRunner.query('ALTER TABLE "note_categories" ADD COLUMN "tenant_id" varchar');
} catch (e) {
// Ignore
}
}
}
@@ -0,0 +1,76 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddTemplateExtensions1773210000002 implements MigrationInterface {
name = 'AddTemplateExtensions1773210000002';
public async up(queryRunner: QueryRunner): Promise<void> {
// Add linked_group_ids column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'linked_group_ids',
type: 'simple-json',
isNullable: true,
}),
);
// Add weight_config column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'weight_config',
type: 'simple-json',
isNullable: true,
}),
);
// Add difficulty_config column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'difficulty_config',
type: 'simple-json',
isNullable: true,
}),
);
// Add question_count_min column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'question_count_min',
type: 'int',
default: 8,
}),
);
// Add question_count_max column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'question_count_max',
type: 'int',
default: 10,
}),
);
// Add passing_score column
await queryRunner.addColumn(
'assessment_templates',
new TableColumn({
name: 'passing_score',
type: 'int',
default: 90,
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('assessment_templates', 'linked_group_ids');
await queryRunner.dropColumn('assessment_templates', 'weight_config');
await queryRunner.dropColumn('assessment_templates', 'difficulty_config');
await queryRunner.dropColumn('assessment_templates', 'question_count_min');
await queryRunner.dropColumn('assessment_templates', 'question_count_max');
await queryRunner.dropColumn('assessment_templates', 'passing_score');
}
}
@@ -0,0 +1,79 @@
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateAssessmentCertificateTable1773210000003 implements MigrationInterface {
name = 'CreateAssessmentCertificateTable1773210000003';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'assessment_certificates',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
isGenerated: true,
},
{
name: 'user_id',
type: 'uuid',
isNullable: false,
},
{
name: 'session_id',
type: 'uuid',
isNullable: false,
},
{
name: 'template_id',
type: 'uuid',
isNullable: false,
},
{
name: 'level',
type: 'varchar',
length: '50',
isNullable: false,
},
{
name: 'total_score',
type: 'float',
isNullable: false,
},
{
name: 'qr_code',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'dimension_scores',
type: 'simple-json',
isNullable: true,
},
{
name: 'radar_data',
type: 'simple-json',
isNullable: true,
},
{
name: 'passed',
type: 'boolean',
default: false,
},
{
name: 'issued_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('assessment_certificates');
}
}
@@ -0,0 +1,223 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
export class CreateQuestionBankTables1773220000000 implements MigrationInterface {
name = 'CreateQuestionBankTables1773220000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'question_banks',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
isGenerated: true,
},
{
name: 'tenant_id',
type: 'uuid',
isNullable: true,
},
{
name: 'template_id',
type: 'uuid',
isNullable: true,
},
{
name: 'name',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'description',
type: 'text',
isNullable: true,
},
{
name: 'status',
type: 'enum',
enum: ['DRAFT', 'PENDING_REVIEW', 'PUBLISHED', 'REJECTED'],
default: "'DRAFT'",
},
{
name: 'created_by',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'reviewed_by',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'reviewed_at',
type: 'timestamp',
isNullable: true,
},
{
name: 'review_comment',
type: 'text',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true,
);
await queryRunner.createIndex('question_banks', new TableIndex({
name: 'IDX_QUESTION_BANK_TEMPLATE',
columnNames: ['template_id'],
isUnique: true,
}));
await queryRunner.createForeignKey('question_banks', new TableForeignKey({
columnNames: ['template_id'],
referencedColumnNames: ['id'],
referencedTableName: 'assessment_templates',
onDelete: 'SET NULL',
}));
await queryRunner.createForeignKey('question_banks', new TableForeignKey({
columnNames: ['tenant_id'],
referencedColumnNames: ['id'],
referencedTableName: 'tenants',
onDelete: 'CASCADE',
}));
await queryRunner.createTable(
new Table({
name: 'question_bank_items',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
isGenerated: true,
},
{
name: 'bank_id',
type: 'uuid',
isNullable: false,
},
{
name: 'question_text',
type: 'text',
isNullable: false,
},
{
name: 'question_type',
type: 'enum',
enum: ['SHORT_ANSWER', 'MULTIPLE_CHOICE', 'TRUE_FALSE'],
default: "'SHORT_ANSWER'",
},
{
name: 'options',
type: 'simple-json',
isNullable: true,
},
{
name: 'correct_answer',
type: 'text',
isNullable: true,
},
{
name: 'key_points',
type: 'simple-json',
isNullable: false,
},
{
name: 'difficulty',
type: 'enum',
enum: ['STANDARD', 'ADVANCED', 'SPECIALIST'],
default: "'STANDARD'",
},
{
name: 'dimension',
type: 'enum',
enum: ['PROMPT', 'LLM', 'IDE', 'DEV_PATTERN', 'WORK_CAPABILITY'],
default: "'PROMPT'",
},
{
name: 'basis',
type: 'text',
isNullable: true,
},
{
name: 'created_by',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'status',
type: 'enum',
enum: ['PENDING_REVIEW', 'PUBLISHED'],
default: "'PENDING_REVIEW'",
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
],
}),
true,
);
await queryRunner.createForeignKey('question_bank_items', new TableForeignKey({
columnNames: ['bank_id'],
referencedColumnNames: ['id'],
referencedTableName: 'question_banks',
onDelete: 'CASCADE',
}));
}
public async down(queryRunner: QueryRunner): Promise<void> {
const questionBankItemsTable = await queryRunner.getTable('question_bank_items');
if (questionBankItemsTable) {
const foreignKey = questionBankItemsTable.foreignKeys.find(
(fk) => fk.columnNames.indexOf('bank_id') !== -1,
);
if (foreignKey) {
await queryRunner.dropForeignKey('question_bank_items', foreignKey);
}
await queryRunner.dropTable('question_bank_items');
}
const questionBanksTable = await queryRunner.getTable('question_banks');
if (questionBanksTable) {
const foreignKeys = questionBanksTable.foreignKeys;
for (const fk of foreignKeys) {
await queryRunner.dropForeignKey('question_banks', fk);
}
const index = questionBanksTable.indices.find(
(idx) => idx.name === 'IDX_QUESTION_BANK_TEMPLATE',
);
if (index) {
await queryRunner.dropIndex('question_banks', index);
}
await queryRunner.dropTable('question_banks');
}
}
}
@@ -0,0 +1,3 @@
-- Add language column to user_settings table
ALTER TABLE user_settings
ADD COLUMN language TEXT DEFAULT 'zh';
@@ -0,0 +1,29 @@
-- cleanup-settings-tables.sql
-- Drop unnecessary columns from settings tables to align with the refined architecture.
-- 1. Prune user_settings table
-- Keeps only id, userId, and language.
ALTER TABLE user_settings DROP COLUMN selectedLLMId;
ALTER TABLE user_settings DROP COLUMN selectedEmbeddingId;
ALTER TABLE user_settings DROP COLUMN selectedRerankId;
ALTER TABLE user_settings DROP COLUMN temperature;
ALTER TABLE user_settings DROP COLUMN maxTokens;
ALTER TABLE user_settings DROP COLUMN enableRerank;
ALTER TABLE user_settings DROP COLUMN topK;
ALTER TABLE user_settings DROP COLUMN similarityThreshold;
ALTER TABLE user_settings DROP COLUMN enableFullTextSearch;
ALTER TABLE user_settings DROP COLUMN defaultVisionModelId;
ALTER TABLE user_settings DROP COLUMN coachKbId;
ALTER TABLE user_settings DROP COLUMN created_at;
ALTER TABLE user_settings DROP COLUMN updated_at;
ALTER TABLE user_settings DROP COLUMN rerankSimilarityThreshold;
ALTER TABLE user_settings DROP COLUMN hybridVectorWeight;
ALTER TABLE user_settings DROP COLUMN isGlobal;
ALTER TABLE user_settings DROP COLUMN enableQueryExpansion;
ALTER TABLE user_settings DROP COLUMN enableHyDE;
ALTER TABLE user_settings DROP COLUMN chunkSize;
ALTER TABLE user_settings DROP COLUMN chunkOverlap;
-- 2. Prune tenant_settings table
-- Language is now strictly a user-level setting.
ALTER TABLE tenant_settings DROP COLUMN language;
@@ -0,0 +1,8 @@
-- restore-timestamps.sql
-- Restore created_at and updated_at columns to user_settings table.
ALTER TABLE user_settings ADD COLUMN created_at datetime;
ALTER TABLE user_settings ADD COLUMN updated_at datetime;
UPDATE user_settings SET created_at = datetime('now') WHERE created_at IS NULL;
UPDATE user_settings SET updated_at = datetime('now') WHERE updated_at IS NULL;