增加浏览器离线通知功能
This commit is contained in:
@@ -37,27 +37,86 @@ if (migrationFiles.length === 0) {
|
||||
|
||||
console.log(`📄 Found ${migrationFiles.length} migration file(s):`, migrationFiles.join(', '));
|
||||
|
||||
const MIGRATION_BASELINE_CUTOFF = '008_web_push_notifications.sql';
|
||||
|
||||
function splitSqlStatements(schemaSql) {
|
||||
const withoutLineComments = schemaSql
|
||||
.split('\n')
|
||||
.filter((line) => !line.trim().startsWith('--'))
|
||||
.join('\n');
|
||||
|
||||
return withoutLineComments
|
||||
.split(';')
|
||||
.map((statement) => statement.trim())
|
||||
.filter((statement) => statement.length > 0);
|
||||
}
|
||||
|
||||
async function tableExists(tableName) {
|
||||
const result = await sql.query(
|
||||
"SELECT to_regclass($1) AS table_name",
|
||||
[`public.${tableName}`]
|
||||
);
|
||||
return Boolean(result.rows?.[0]?.table_name);
|
||||
}
|
||||
|
||||
async function ensureMigrationTable() {
|
||||
await sql.query(`
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
filename TEXT PRIMARY KEY,
|
||||
applied_at BIGINT NOT NULL
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
async function getAppliedMigrations() {
|
||||
const result = await sql.query('SELECT filename FROM schema_migrations');
|
||||
return new Set((result.rows || []).map((row) => row.filename));
|
||||
}
|
||||
|
||||
async function markMigrationApplied(filename) {
|
||||
await sql.query(
|
||||
'INSERT INTO schema_migrations (filename, applied_at) VALUES ($1, $2) ON CONFLICT (filename) DO NOTHING',
|
||||
[filename, Date.now()]
|
||||
);
|
||||
}
|
||||
|
||||
async function seedExistingMigrationBaseline(hadExistingSchema) {
|
||||
const applied = await getAppliedMigrations();
|
||||
if (!hadExistingSchema || applied.size > 0) return;
|
||||
|
||||
for (const file of migrationFiles) {
|
||||
if (file.localeCompare(MIGRATION_BASELINE_CUTOFF) < 0) {
|
||||
await markMigrationApplied(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
// 执行所有迁移脚本
|
||||
console.log('🔧 Running database migrations...');
|
||||
const hadExistingSchema = await tableExists('users');
|
||||
await ensureMigrationTable();
|
||||
await seedExistingMigrationBaseline(hadExistingSchema);
|
||||
|
||||
for (const migrationFile of migrationFiles) {
|
||||
const applied = await getAppliedMigrations();
|
||||
if (applied.has(migrationFile)) {
|
||||
console.log(` ⏭️ ${migrationFile} already applied`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sqlPath = path.join(migrationsDir, migrationFile);
|
||||
console.log(` ⏳ Executing ${migrationFile}...`);
|
||||
|
||||
const schemaSql = fs.readFileSync(sqlPath, 'utf8');
|
||||
|
||||
// 将 SQL 脚本按语句分割并逐个执行
|
||||
const statements = schemaSql
|
||||
.split(';')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0);
|
||||
const statements = splitSqlStatements(schemaSql);
|
||||
|
||||
for (const statement of statements) {
|
||||
await sql.query(statement);
|
||||
}
|
||||
|
||||
await markMigrationApplied(migrationFile);
|
||||
console.log(` ✅ ${migrationFile} executed successfully`);
|
||||
}
|
||||
|
||||
|
||||
+75
-10
@@ -4,6 +4,7 @@ const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const MIGRATIONS_DIR = path.join(__dirname, '../migrations');
|
||||
const MIGRATION_BASELINE_CUTOFF = '008_web_push_notifications.sql';
|
||||
|
||||
function hashPassword(password) {
|
||||
return crypto.createHash('sha256').update(password).digest('hex');
|
||||
@@ -46,24 +47,88 @@ function isIgnorableMigrationError(error) {
|
||||
);
|
||||
}
|
||||
|
||||
function runMigrations(db) {
|
||||
const migrationFiles = getMigrationFiles();
|
||||
function splitSqlStatements(sql) {
|
||||
const withoutLineComments = sql
|
||||
.split('\n')
|
||||
.filter((line) => !line.trim().startsWith('--'))
|
||||
.join('\n');
|
||||
|
||||
return withoutLineComments
|
||||
.split(';')
|
||||
.map((statement) => statement.trim())
|
||||
.filter((statement) => statement.length > 0);
|
||||
}
|
||||
|
||||
function tableExists(db, tableName) {
|
||||
const row = db
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
|
||||
.get(tableName);
|
||||
return Boolean(row);
|
||||
}
|
||||
|
||||
function ensureMigrationTable(db) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
filename TEXT PRIMARY KEY,
|
||||
applied_at INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
function getAppliedMigrations(db) {
|
||||
return new Set(
|
||||
db.prepare('SELECT filename FROM schema_migrations').all().map((row) => row.filename)
|
||||
);
|
||||
}
|
||||
|
||||
function markMigrationApplied(db, filename) {
|
||||
db.prepare(
|
||||
'INSERT OR IGNORE INTO schema_migrations (filename, applied_at) VALUES (?, ?)'
|
||||
).run(filename, Date.now());
|
||||
}
|
||||
|
||||
function seedExistingMigrationBaseline(db, migrationFiles, hadExistingSchema) {
|
||||
const applied = getAppliedMigrations(db);
|
||||
if (!hadExistingSchema || applied.size > 0) return;
|
||||
|
||||
for (const file of migrationFiles) {
|
||||
if (file.localeCompare(MIGRATION_BASELINE_CUTOFF) < 0) {
|
||||
markMigrationApplied(db, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runMigrations(db) {
|
||||
const migrationFiles = getMigrationFiles();
|
||||
const hadExistingSchema = tableExists(db, 'users');
|
||||
ensureMigrationTable(db);
|
||||
seedExistingMigrationBaseline(db, migrationFiles, hadExistingSchema);
|
||||
|
||||
for (const file of migrationFiles) {
|
||||
const applied = getAppliedMigrations(db);
|
||||
if (applied.has(file)) {
|
||||
console.log(`⏭️ Migration already applied: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const migrationPath = path.join(MIGRATIONS_DIR, file);
|
||||
const sql = fs.readFileSync(migrationPath, 'utf8');
|
||||
const statements = splitSqlStatements(sql);
|
||||
|
||||
console.log(`▶️ Applying migration: ${file}`);
|
||||
try {
|
||||
db.exec(sql);
|
||||
console.log(`✅ Migration applied: ${file}`);
|
||||
} catch (error) {
|
||||
if (isIgnorableMigrationError(error)) {
|
||||
console.log(`⏭️ Migration skipped: ${file}`);
|
||||
continue;
|
||||
for (const statement of statements) {
|
||||
try {
|
||||
db.exec(statement);
|
||||
} catch (error) {
|
||||
if (isIgnorableMigrationError(error)) {
|
||||
console.log(`⏭️ Statement skipped in ${file}: ${error.message}`);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
markMigrationApplied(db, file);
|
||||
console.log(`✅ Migration applied: ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user