初始提交:全国记者站管理系统

This commit is contained in:
selfrelease
2026-08-01 23:09:49 +08:00
commit 45fbba0308
96 changed files with 21514 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env node
/**
* 数据库迁移运行器
* 按顺序执行 migrations/ 目录下的所有 .sql 文件
* 记录已执行的迁移到 schema_migrations 表(幂等性保障)
*
* 用法:
* node migrations/_runner.js # 执行所有待执行迁移
* node migrations/_runner.js --status # 查看迁移状态
* node migrations/_runner.js --reset # 重置数据库(慎用)
*/
import { DatabaseSync } from 'node:sqlite'
import { readFileSync, readdirSync, mkdirSync, unlinkSync, existsSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const root = join(__dirname, '..')
const dataDir = join(root, 'data')
mkdirSync(dataDir, { recursive: true })
const dbPath = join(dataDir, 'reporter-station.db')
/** 初始化数据库连接(确保 schema_migrations 表存在) */
function initDb() {
const db = new DatabaseSync(dbPath)
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS schema_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
);
`)
return db
}
const args = process.argv.slice(2)
if (args.includes('--status')) {
const db = initDb()
const applied = db.prepare('SELECT name, applied_at FROM schema_migrations ORDER BY id').all()
const allFiles = readdirSync(__dirname)
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
.sort()
console.log('\n迁移状态:')
for (const f of allFiles) {
const name = f.replace('.sql', '')
const row = applied.find(a => a.name === name)
if (row) {
console.log(` [x] ${name} (applied at ${row.applied_at})`)
} else {
console.log(` [ ] ${name}`)
}
}
db.close()
process.exit(0)
}
if (args.includes('--seed')) {
const db = initDb()
const seedFile = '002_seed_data.sql'
const sql = readFileSync(join(__dirname, seedFile), 'utf-8')
try {
db.exec(sql)
console.log(`已重新执行 seed 数据(${seedFile}`)
} catch (err) {
console.error(`seed 失败: ${err.message}`)
process.exit(1)
}
db.close()
process.exit(0)
}
if (args.includes('--reset')) {
for (const ext of ['', '-wal', '-shm']) {
const f = dbPath + ext
if (existsSync(f)) { unlinkSync(f); console.log(`已删除: ${f}`) }
}
console.log('数据库已重置,重新启动服务将自动执行迁移。')
process.exit(0)
}
// 默认:执行待执行的迁移
const db = initDb()
const applied = new Set(db.prepare('SELECT name FROM schema_migrations').all().map(r => r.name))
const allFiles = readdirSync(__dirname)
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
.sort()
let appliedCount = 0
for (const file of allFiles) {
const name = file.replace('.sql', '')
if (applied.has(name)) {
console.log(` 跳过 (已执行): ${name}`)
continue
}
const sql = readFileSync(join(__dirname, file), 'utf-8')
try {
db.exec('BEGIN')
db.exec(sql)
db.prepare('INSERT INTO schema_migrations (name) VALUES (?)').run(name)
db.exec('COMMIT')
console.log(` [x] ${name}`)
appliedCount++
} catch (err) {
db.exec('ROLLBACK')
console.error(` [!] ${name} 执行失败: ${err.message}`)
process.exit(1)
}
}
db.close()
if (appliedCount === 0) {
console.log('\n没有待执行的迁移,数据库已是最新。')
} else {
console.log(`\n成功执行 ${appliedCount} 个迁移。`)
}