197 lines
6.6 KiB
JavaScript
197 lines
6.6 KiB
JavaScript
const Database = require('better-sqlite3')
|
||
const path = require('path')
|
||
|
||
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data.db')
|
||
const db = new Database(dbPath)
|
||
|
||
// 启用外键
|
||
db.pragma('journal_mode = WAL')
|
||
|
||
// 建表
|
||
db.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'))
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_news_content_hash ON news_content(content_hash);
|
||
CREATE INDEX IF NOT EXISTS idx_news_content_published ON news_content(published_at DESC);
|
||
`)
|
||
|
||
// 迁移:为已有 key_location 表添加 type、region、status、damage_level 列
|
||
try {
|
||
const cols = db.prepare('PRAGMA table_info(key_location)').all()
|
||
const names = cols.map((c) => c.name)
|
||
if (!names.includes('type')) db.exec('ALTER TABLE key_location ADD COLUMN type TEXT')
|
||
if (!names.includes('region')) db.exec('ALTER TABLE key_location ADD COLUMN region TEXT')
|
||
if (!names.includes('status')) db.exec('ALTER TABLE key_location ADD COLUMN status TEXT DEFAULT "operational"')
|
||
if (!names.includes('damage_level')) db.exec('ALTER TABLE key_location ADD COLUMN damage_level INTEGER')
|
||
} catch (_) {}
|
||
// 迁移:combat_losses 添加平民伤亡、updated_at
|
||
try {
|
||
const lossCols = db.prepare('PRAGMA table_info(combat_losses)').all()
|
||
const lossNames = lossCols.map((c) => c.name)
|
||
if (!lossNames.includes('civilian_killed')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_killed INTEGER NOT NULL DEFAULT 0')
|
||
if (!lossNames.includes('civilian_wounded')) db.exec('ALTER TABLE combat_losses ADD COLUMN civilian_wounded INTEGER NOT NULL DEFAULT 0')
|
||
if (!lossNames.includes('updated_at')) db.exec('ALTER TABLE combat_losses ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))')
|
||
if (!lossNames.includes('drones')) db.exec('ALTER TABLE combat_losses ADD COLUMN drones INTEGER NOT NULL DEFAULT 0')
|
||
if (!lossNames.includes('missiles')) db.exec('ALTER TABLE combat_losses ADD COLUMN missiles INTEGER NOT NULL DEFAULT 0')
|
||
if (!lossNames.includes('helicopters')) db.exec('ALTER TABLE combat_losses ADD COLUMN helicopters INTEGER NOT NULL DEFAULT 0')
|
||
if (!lossNames.includes('submarines')) db.exec('ALTER TABLE combat_losses ADD COLUMN submarines INTEGER NOT NULL DEFAULT 0')
|
||
} catch (_) {}
|
||
|
||
// 迁移:所有表添加 updated_at 用于数据回放
|
||
const addUpdatedAt = (table) => {
|
||
try {
|
||
const cols = db.prepare(`PRAGMA table_info(${table})`).all()
|
||
if (!cols.some((c) => c.name === 'updated_at')) {
|
||
db.exec(`ALTER TABLE ${table} ADD COLUMN updated_at TEXT DEFAULT (datetime("now"))`)
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
addUpdatedAt('force_summary')
|
||
addUpdatedAt('power_index')
|
||
addUpdatedAt('force_asset')
|
||
addUpdatedAt('key_location')
|
||
addUpdatedAt('retaliation_current')
|
||
|
||
// 来访统计:visits 用于在看(近期活跃 IP),visitor_count 用于累积人次(每次接入 +1)
|
||
try {
|
||
db.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 {
|
||
db.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 (_) {}
|
||
|
||
module.exports = db
|