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:
@@ -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;
|
||||
Reference in New Issue
Block a user