Merge exams grading into homework
Some checks failed
CI / build-and-test (push) Failing after 3m34s
CI / deploy (push) Has been skipped

Redirect /teacher/exams/grading* to /teacher/homework/submissions; remove exam grading UI/actions/data-access; add homework student workflow and update design docs.
This commit is contained in:
SpecialX
2025-12-31 11:59:03 +08:00
parent f8e39f518d
commit 13e91e628d
36 changed files with 4491 additions and 452 deletions

View File

@@ -0,0 +1,90 @@
require("dotenv/config");
const fs = require("node:fs");
const crypto = require("node:crypto");
const path = require("node:path");
const mysql = require("mysql2/promise");
const JOURNAL = {
"0000_aberrant_cobalt_man": 1766460456274,
"0001_flawless_texas_twister": 1767004087964,
"0002_equal_wolfpack": 1767145757594,
};
function sha256Hex(input) {
return crypto.createHash("sha256").update(input).digest("hex");
}
async function main() {
const url = process.env.DATABASE_URL;
if (!url) {
throw new Error("DATABASE_URL is not set");
}
const conn = await mysql.createConnection(url);
await conn.query(
"CREATE TABLE IF NOT EXISTS `__drizzle_migrations` (id serial primary key, hash text not null, created_at bigint)"
);
const [existing] = await conn.query(
"SELECT id, hash, created_at FROM `__drizzle_migrations` ORDER BY created_at DESC LIMIT 1"
);
if (Array.isArray(existing) && existing.length > 0) {
console.log("✅ __drizzle_migrations already has entries. Skip baselining.");
await conn.end();
return;
}
const [[accountsRow]] = await conn.query(
"SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='accounts'"
);
const accountsExists = Number(accountsRow?.cnt ?? 0) > 0;
if (!accountsExists) {
console.log(" No existing tables detected (accounts missing). Skip baselining.");
await conn.end();
return;
}
const [[structureRow]] = await conn.query(
"SELECT COUNT(*) AS cnt FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='exams' AND column_name='structure'"
);
const examsStructureExists = Number(structureRow?.cnt ?? 0) > 0;
const [[homeworkRow]] = await conn.query(
"SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='homework_assignments'"
);
const homeworkExists = Number(homeworkRow?.cnt ?? 0) > 0;
const baselineTags = [];
baselineTags.push("0000_aberrant_cobalt_man");
if (examsStructureExists) baselineTags.push("0001_flawless_texas_twister");
if (homeworkExists) baselineTags.push("0002_equal_wolfpack");
const drizzleDir = path.resolve(__dirname, "..", "..", "drizzle");
for (const tag of baselineTags) {
const sqlPath = path.join(drizzleDir, `${tag}.sql`);
if (!fs.existsSync(sqlPath)) {
throw new Error(`Missing migration file: ${sqlPath}`);
}
const sqlText = fs.readFileSync(sqlPath).toString();
const hash = sha256Hex(sqlText);
const createdAt = JOURNAL[tag];
if (typeof createdAt !== "number") {
throw new Error(`Missing journal timestamp for: ${tag}`);
}
await conn.query(
"INSERT INTO `__drizzle_migrations` (`hash`, `created_at`) VALUES (?, ?)",
[hash, createdAt]
);
}
console.log(`✅ Baselined __drizzle_migrations: ${baselineTags.join(", ")}`);
await conn.end();
}
main().catch((err) => {
console.error("❌ Baseline failed:", err);
process.exit(1);
});