This commit is contained in:
38
scripts/check_cst_schema.ts
Normal file
38
scripts/check_cst_schema.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { config } from "dotenv"
|
||||
import mysql from "mysql2/promise"
|
||||
|
||||
async function main() {
|
||||
config()
|
||||
const url = process.env.DATABASE_URL
|
||||
if (!url) {
|
||||
console.error("Missing DATABASE_URL")
|
||||
process.exit(1)
|
||||
return
|
||||
}
|
||||
const conn = await mysql.createConnection(url)
|
||||
try {
|
||||
const [rows] = await conn.query("SHOW COLUMNS FROM class_subject_teachers")
|
||||
const [keys] = await conn.query("SHOW KEYS FROM class_subject_teachers")
|
||||
let migrations: Array<{ id: number | string; hash: string; created_at: number | string }> | null = null
|
||||
try {
|
||||
const [m] = await conn.query("SELECT id, hash, created_at FROM __drizzle_migrations ORDER BY id DESC LIMIT 5")
|
||||
migrations = m as Array<{ id: number | string; hash: string; created_at: number | string }>
|
||||
} catch (error: unknown) {
|
||||
console.error(error)
|
||||
}
|
||||
const columns = rows as Array<{ Field: string; Type: string; Null: string; Key: string }>
|
||||
const indexes = keys as Array<{ Key_name: string; Column_name: string }>
|
||||
console.log(columns.map((r) => `${r.Field}:${r.Type}:${r.Null}:${r.Key}`).join("\n"))
|
||||
console.log(indexes.map((r) => `${r.Key_name}:${r.Column_name}`).join("\n"))
|
||||
if (migrations) {
|
||||
console.log(migrations.map((r) => `${r.id}:${r.hash}:${r.created_at}`).join("\n"))
|
||||
}
|
||||
} finally {
|
||||
await conn.end()
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
58
scripts/migrate_cst_subjectid.ts
Normal file
58
scripts/migrate_cst_subjectid.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { config } from "dotenv"
|
||||
import { db } from "@/shared/db"
|
||||
import { sql } from "drizzle-orm"
|
||||
|
||||
async function main() {
|
||||
config()
|
||||
// 1) add subject_id column if not exists (nullable first)
|
||||
await db.execute(sql`ALTER TABLE class_subject_teachers ADD COLUMN IF NOT EXISTS subject_id VARCHAR(128) NULL;`)
|
||||
|
||||
// 2) backfill subject_id from subjects.name matching existing 'subject' column
|
||||
// This assumes existing data uses subjects.name 值;若不匹配,将在 NOT NULL 约束处失败
|
||||
await db.execute(sql`
|
||||
UPDATE class_subject_teachers cst
|
||||
JOIN subjects s ON (
|
||||
s.name = cst.subject
|
||||
OR s.code = CASE cst.subject
|
||||
WHEN '语文' THEN 'CHINESE'
|
||||
WHEN '数学' THEN 'MATH'
|
||||
WHEN '英语' THEN 'ENG'
|
||||
WHEN '美术' THEN 'ART'
|
||||
WHEN '体育' THEN 'PE'
|
||||
WHEN '科学' THEN 'SCI'
|
||||
WHEN '社会' THEN 'SOC'
|
||||
WHEN '音乐' THEN 'MUSIC'
|
||||
ELSE NULL
|
||||
END
|
||||
)
|
||||
SET cst.subject_id = s.id
|
||||
WHERE cst.subject_id IS NULL
|
||||
`)
|
||||
|
||||
// 3) enforce NOT NULL
|
||||
await db.execute(sql`ALTER TABLE class_subject_teachers MODIFY COLUMN subject_id VARCHAR(128) NOT NULL;`)
|
||||
|
||||
// 4) drop old PK and create new PK (class_id, subject_id)
|
||||
try { await db.execute(sql`ALTER TABLE class_subject_teachers DROP PRIMARY KEY;`) } catch {}
|
||||
await db.execute(sql`ALTER TABLE class_subject_teachers ADD PRIMARY KEY (class_id, subject_id);`)
|
||||
|
||||
// 5) drop old subject column if exists
|
||||
await db.execute(sql`ALTER TABLE class_subject_teachers DROP COLUMN IF EXISTS subject;`)
|
||||
|
||||
// 6) add index and FK
|
||||
try { await db.execute(sql`CREATE INDEX class_subject_teachers_subject_id_idx ON class_subject_teachers (subject_id);`) } catch {}
|
||||
try { await db.execute(sql`ALTER TABLE class_subject_teachers DROP FOREIGN KEY cst_s_fk;`) } catch {}
|
||||
await db.execute(sql`
|
||||
ALTER TABLE class_subject_teachers
|
||||
ADD CONSTRAINT cst_s_fk
|
||||
FOREIGN KEY (subject_id) REFERENCES subjects(id)
|
||||
ON DELETE CASCADE
|
||||
`)
|
||||
|
||||
console.log("Migration completed: class_subject_teachers now uses subject_id mapping.")
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { hash } from "bcryptjs";
|
||||
|
||||
/**
|
||||
* Enterprise-Grade Seed Script for Next_Edu
|
||||
@@ -77,27 +78,31 @@ async function seed() {
|
||||
]);
|
||||
|
||||
// Users
|
||||
const passwordHash = await hash("123456", 10);
|
||||
const usersData = [
|
||||
{
|
||||
id: "user_admin",
|
||||
name: "Admin User",
|
||||
email: "admin@next-edu.com",
|
||||
role: "admin", // Legacy field
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Admin"
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Admin",
|
||||
password: passwordHash
|
||||
},
|
||||
{
|
||||
id: "user_teacher_math",
|
||||
name: "Mr. Math",
|
||||
email: "math@next-edu.com",
|
||||
role: "teacher",
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Math"
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Math",
|
||||
password: passwordHash
|
||||
},
|
||||
{
|
||||
id: "user_student_1",
|
||||
name: "Alice Student",
|
||||
email: "alice@next-edu.com",
|
||||
role: "student",
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alice"
|
||||
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alice",
|
||||
password: passwordHash
|
||||
}
|
||||
];
|
||||
|
||||
@@ -122,6 +127,7 @@ async function seed() {
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
role: "student",
|
||||
image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${studentId}`,
|
||||
password: passwordHash,
|
||||
});
|
||||
await db.insert(usersToRoles).values({ userId: studentId, roleId: roleMap.student });
|
||||
}
|
||||
@@ -136,13 +142,14 @@ async function seed() {
|
||||
|
||||
// --- Seeding Subjects ---
|
||||
await db.insert(subjects).values([
|
||||
{ id: createId(), name: "Mathematics", code: "MATH", order: 1 },
|
||||
{ id: createId(), name: "Physics", code: "PHYS", order: 2 },
|
||||
{ id: createId(), name: "Chemistry", code: "CHEM", order: 3 },
|
||||
{ id: createId(), name: "English", code: "ENG", order: 4 },
|
||||
{ id: createId(), name: "History", code: "HIST", order: 5 },
|
||||
{ id: createId(), name: "Geography", code: "GEO", order: 6 },
|
||||
{ id: createId(), name: "Biology", code: "BIO", order: 7 },
|
||||
{ id: createId(), name: "语文", code: "CHINESE", order: 1 },
|
||||
{ id: createId(), name: "数学", code: "MATH", order: 2 },
|
||||
{ id: createId(), name: "英语", code: "ENG", order: 3 },
|
||||
{ id: createId(), name: "美术", code: "ART", order: 4 },
|
||||
{ id: createId(), name: "体育", code: "PE", order: 5 },
|
||||
{ id: createId(), name: "科学", code: "SCI", order: 6 },
|
||||
{ id: createId(), name: "社会", code: "SOC", order: 7 },
|
||||
{ id: createId(), name: "音乐", code: "MUSIC", order: 8 },
|
||||
])
|
||||
|
||||
await db.insert(grades).values([
|
||||
|
||||
Reference in New Issue
Block a user