Files
usa/server/db.js
2026-03-03 14:49:02 +08:00

279 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* SQLite 封装:使用 sql.js纯 JS/WebAssembly无需 node-gyp
* 对外接口与 better-sqlite3 兼容db.prepare().get/all/run、db.exec
*/
const path = require('path')
const fs = require('fs')
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data.db')
let _db = null
function getDb() {
if (!_db) throw new Error('DB not initialized. Call initDb() first.')
return _db
}
function wrapDatabase(nativeDb, persist) {
return {
prepare(sql) {
return {
get(...args) {
const stmt = nativeDb.prepare(sql)
stmt.bind(args.length ? args : null)
const row = stmt.step() ? stmt.getAsObject() : undefined
stmt.free()
return row
},
all(...args) {
const stmt = nativeDb.prepare(sql)
stmt.bind(args.length ? args : null)
const rows = []
while (stmt.step()) rows.push(stmt.getAsObject())
stmt.free()
return rows
},
run(...args) {
const stmt = nativeDb.prepare(sql)
stmt.bind(args.length ? args : null)
while (stmt.step());
stmt.free()
persist()
},
}
},
exec(sql) {
const statements = sql.split(';').map((s) => s.trim()).filter(Boolean)
statements.forEach((s) => nativeDb.run(s))
persist()
},
pragma(str) {
nativeDb.run('PRAGMA ' + str)
},
}
}
function runMigrations(db) {
const exec = (sql) => db.exec(sql)
const prepare = (sql) => db.prepare(sql)
exec(`
CREATE TABLE IF NOT EXISTS situation (
id INTEGER PRIMARY KEY CHECK (id = 1),
data TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS force_summary (
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
total_assets INTEGER NOT NULL,
personnel INTEGER NOT NULL,
naval_ships INTEGER NOT NULL,
aircraft INTEGER NOT NULL,
ground_units INTEGER NOT NULL,
uav INTEGER NOT NULL,
missile_consumed INTEGER NOT NULL,
missile_stock INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS power_index (
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
overall INTEGER NOT NULL,
military_strength INTEGER NOT NULL,
economic_power INTEGER NOT NULL,
geopolitical_influence INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS force_asset (
id TEXT PRIMARY KEY,
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
name TEXT NOT NULL,
type TEXT NOT NULL,
count INTEGER NOT NULL,
status TEXT NOT NULL CHECK (status IN ('active', 'standby', 'alert')),
lat REAL,
lng REAL
);
CREATE TABLE IF NOT EXISTS key_location (
id INTEGER PRIMARY KEY AUTOINCREMENT,
side TEXT NOT NULL CHECK (side IN ('us', 'iran')),
name TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL,
type TEXT,
region TEXT
);
CREATE TABLE IF NOT EXISTS combat_losses (
side TEXT PRIMARY KEY CHECK (side IN ('us', 'iran')),
bases_destroyed INTEGER NOT NULL,
bases_damaged INTEGER NOT NULL,
personnel_killed INTEGER NOT NULL,
personnel_wounded INTEGER NOT NULL,
aircraft INTEGER NOT NULL,
warships INTEGER NOT NULL,
armor INTEGER NOT NULL,
vehicles INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS wall_street_trend (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time TEXT NOT NULL,
value INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS retaliation_current (
id INTEGER PRIMARY KEY CHECK (id = 1),
value INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS retaliation_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time TEXT NOT NULL,
value INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS situation_update (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
category TEXT NOT NULL,
summary TEXT NOT NULL,
severity TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS gdelt_events (
event_id TEXT PRIMARY KEY,
event_time TEXT NOT NULL,
title TEXT NOT NULL,
lat REAL NOT NULL,
lng REAL NOT NULL,
impact_score INTEGER NOT NULL,
url TEXT,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS conflict_stats (
id INTEGER PRIMARY KEY CHECK (id = 1),
total_events INTEGER NOT NULL DEFAULT 0,
high_impact_events INTEGER NOT NULL DEFAULT 0,
estimated_casualties INTEGER NOT NULL DEFAULT 0,
estimated_strike_count INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS news_content (
id TEXT PRIMARY KEY,
content_hash TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
summary TEXT NOT NULL,
url TEXT NOT NULL DEFAULT '',
source TEXT NOT NULL DEFAULT '',
published_at TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'other',
severity TEXT NOT NULL DEFAULT 'medium',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
`)
try { exec('CREATE INDEX IF NOT EXISTS idx_news_content_hash ON news_content(content_hash)') } catch (_) {}
try { exec('CREATE INDEX IF NOT EXISTS idx_news_content_published ON news_content(published_at DESC)') } catch (_) {}
try {
const cols = prepare('PRAGMA table_info(key_location)').all()
const names = cols.map((c) => c.name)
if (!names.includes('type')) exec('ALTER TABLE key_location ADD COLUMN type TEXT')
if (!names.includes('region')) exec('ALTER TABLE key_location ADD COLUMN region TEXT')
if (!names.includes('status')) exec('ALTER TABLE key_location ADD COLUMN status TEXT DEFAULT "operational"')
if (!names.includes('damage_level')) exec('ALTER TABLE key_location ADD COLUMN damage_level INTEGER')
} catch (_) {}
try {
const lossCols = prepare('PRAGMA table_info(combat_losses)').all()
const lossNames = lossCols.map((c) => c.name)
if (!lossNames.includes('civilian_killed')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('civilian_wounded')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('updated_at')) exec('ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))')
if (!lossNames.includes('drones')) exec('ALTER TABLE combat_losses ADD COLUMN drones INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('missiles')) exec('ALTER TABLE combat_losses ADD COLUMN missiles INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('helicopters')) exec('ALTER TABLE combat_losses ADD COLUMN helicopters INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('submarines')) exec('ALTER TABLE combat_losses ADD COLUMN submarines INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('tanks')) exec('ALTER TABLE combat_losses ADD COLUMN tanks INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('carriers')) {
exec('ALTER TABLE combat_losses ADD COLUMN carriers INTEGER NOT NULL DEFAULT 0')
exec('UPDATE combat_losses SET carriers = tanks')
}
if (!lossNames.includes('civilian_ships')) exec('ALTER TABLE combat_losses ADD COLUMN civilian_ships INTEGER NOT NULL DEFAULT 0')
if (!lossNames.includes('airport_port')) exec('ALTER TABLE combat_losses ADD COLUMN airport_port INTEGER NOT NULL DEFAULT 0')
} catch (_) {}
const addUpdatedAt = (table) => {
try {
const cols = prepare(`PRAGMA table_info(${table})`).all()
if (!cols.some((c) => c.name === 'updated_at')) {
exec(`ALTER TABLE ${table} ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))`)
}
} catch (_) {}
}
;['force_summary', 'power_index', 'force_asset', 'key_location', 'retaliation_current'].forEach(addUpdatedAt)
try {
exec(`
CREATE TABLE IF NOT EXISTS visits (
ip TEXT PRIMARY KEY,
last_seen TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS visitor_count (
id INTEGER PRIMARY KEY CHECK (id = 1),
total INTEGER NOT NULL DEFAULT 0
);
INSERT OR IGNORE INTO visitor_count (id, total) VALUES (1, 0);
`)
} catch (_) {}
try {
exec(`
CREATE TABLE IF NOT EXISTS feedback (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
ip TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`)
} catch (_) {}
try {
exec(`
CREATE TABLE IF NOT EXISTS share_count (
id INTEGER PRIMARY KEY CHECK (id = 1),
total INTEGER NOT NULL DEFAULT 0
);
INSERT OR IGNORE INTO share_count (id, total) VALUES (1, 0);
`)
} catch (_) {}
}
async function initDb() {
const initSqlJs = require('sql.js')
const SQL = await initSqlJs()
let data = new Uint8Array(0)
if (fs.existsSync(dbPath)) {
data = new Uint8Array(fs.readFileSync(dbPath))
}
const nativeDb = new SQL.Database(data)
function persist() {
try {
const buf = nativeDb.export()
fs.writeFileSync(dbPath, Buffer.from(buf))
} catch (e) {
console.error('[db] persist error:', e.message)
}
}
nativeDb.run('PRAGMA journal_mode = WAL')
const wrapped = wrapDatabase(nativeDb, persist)
runMigrations(wrapped)
_db = wrapped
return _db
}
const proxy = {
prepare(sql) {
return getDb().prepare(sql)
},
exec(sql) {
return getDb().exec(sql)
},
pragma(str) {
getDb().pragma(str)
},
}
module.exports = proxy
module.exports.initDb = initDb
module.exports.getDb = getDb