This commit is contained in:
Daniel
2026-03-03 20:17:38 +08:00
parent 034c088bac
commit 09ec2e3a69
20 changed files with 395 additions and 19 deletions

View File

@@ -98,19 +98,29 @@ router.get('/situation', (req, res) => {
}
})
// 来访统计:记录 IP返回在看/累积
// 来访统计:记录 IP(或开发环境下每标签 viewer-id,返回在看/看过
function getClientIp(req) {
const forwarded = req.headers['x-forwarded-for']
if (forwarded) return forwarded.split(',')[0].trim()
return req.ip || req.socket?.remoteAddress || 'unknown'
}
function getVisitKey(req) {
const vid = req.headers['x-viewer-id']
const ip = getClientIp(req)
const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1'
if (typeof vid === 'string' && vid.trim().length > 0 && (process.env.NODE_ENV === 'development' || isLocal)) {
return 'vid:' + vid.trim()
}
return ip
}
router.post('/visit', (req, res) => {
try {
const ip = getClientIp(req)
const visitKey = getVisitKey(req)
db.prepare(
"INSERT OR REPLACE INTO visits (ip, last_seen) VALUES (?, datetime('now'))"
).run(ip)
).run(visitKey)
db.prepare(
'INSERT INTO visitor_count (id, total) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET total = total + 1'
).run()
@@ -177,4 +187,160 @@ router.get('/events', (req, res) => {
}
})
// ---------- 手动修正看板数据(编辑页用) ----------
function broadcastAfterEdit(req) {
try {
const broadcast = req.app?.get?.('broadcastSituation')
if (typeof broadcast === 'function') broadcast()
} catch (_) {}
}
/** GET 原始可编辑数据:战损、据点、事件脉络、军力概要 */
router.get('/edit/raw', (req, res) => {
try {
const lossesUs = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('us')
const lossesIr = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get('iran')
const locUs = db.prepare('SELECT id, side, name, lat, lng, type, region, status, damage_level FROM key_location WHERE side = ?').all('us')
const locIr = db.prepare('SELECT id, side, name, lat, lng, type, region, status, damage_level FROM key_location WHERE side = ?').all('iran')
const updates = db.prepare('SELECT id, timestamp, category, summary, severity FROM situation_update ORDER BY timestamp DESC LIMIT 80').all()
const summaryUs = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('us')
const summaryIr = db.prepare('SELECT * FROM force_summary WHERE side = ?').get('iran')
res.json({
combatLosses: { us: lossesUs || null, iran: lossesIr || null },
keyLocations: { us: locUs || [], iran: locIr || [] },
situationUpdates: updates || [],
forceSummary: { us: summaryUs || null, iran: summaryIr || null },
})
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
/** PUT 更新战损(美/伊) */
router.put('/edit/combat-losses', (req, res) => {
try {
const side = req.body?.side
if (side !== 'us' && side !== 'iran') {
return res.status(400).json({ error: 'side must be us or iran' })
}
const row = db.prepare('SELECT * FROM combat_losses WHERE side = ?').get(side)
if (!row) return res.status(404).json({ error: 'combat_losses row not found' })
const cols = ['bases_destroyed', 'bases_damaged', 'personnel_killed', 'personnel_wounded',
'civilian_killed', 'civilian_wounded', 'aircraft', 'warships', 'armor', 'vehicles',
'drones', 'missiles', 'helicopters', 'submarines', 'tanks', 'carriers', 'civilian_ships', 'airport_port']
const updates = []
const values = []
for (const c of cols) {
if (req.body[c] !== undefined) {
updates.push(`${c} = ?`)
values.push(Number(req.body[c]) || 0)
}
}
if (updates.length === 0) return res.status(400).json({ error: 'no fields to update' })
values.push(side)
db.prepare(`UPDATE combat_losses SET ${updates.join(', ')} WHERE side = ?`).run(...values)
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastAfterEdit(req)
res.json({ ok: true })
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
/** PATCH 更新单个据点 */
router.patch('/edit/key-location/:id', (req, res) => {
try {
const id = parseInt(req.params.id, 10)
if (!Number.isFinite(id)) return res.status(400).json({ error: 'invalid id' })
const row = db.prepare('SELECT id FROM key_location WHERE id = ?').get(id)
if (!row) return res.status(404).json({ error: 'key_location not found' })
const allowed = ['name', 'lat', 'lng', 'type', 'region', 'status', 'damage_level']
const updates = []
const values = []
for (const k of allowed) {
if (req.body[k] !== undefined) {
if (k === 'status' && !['operational', 'damaged', 'attacked'].includes(req.body[k])) continue
updates.push(`${k} = ?`)
values.push(k === 'lat' || k === 'lng' ? Number(req.body[k]) : req.body[k])
}
}
if (updates.length === 0) return res.status(400).json({ error: 'no fields to update' })
values.push(id)
db.prepare(`UPDATE key_location SET ${updates.join(', ')} WHERE id = ?`).run(...values)
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastAfterEdit(req)
res.json({ ok: true })
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
/** POST 新增事件脉络 */
router.post('/edit/situation-update', (req, res) => {
try {
const id = (req.body?.id || '').toString().trim() || `man_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`
const timestamp = (req.body?.timestamp || new Date().toISOString()).toString().trim()
const category = (req.body?.category || 'other').toString().toLowerCase()
const summary = (req.body?.summary || '').toString().trim().slice(0, 500)
const severity = (req.body?.severity || 'medium').toString().toLowerCase()
if (!summary) return res.status(400).json({ error: 'summary required' })
const validCat = ['deployment', 'alert', 'intel', 'diplomatic', 'other'].includes(category) ? category : 'other'
const validSev = ['low', 'medium', 'high', 'critical'].includes(severity) ? severity : 'medium'
db.prepare('INSERT OR REPLACE INTO situation_update (id, timestamp, category, summary, severity) VALUES (?, ?, ?, ?, ?)').run(id, timestamp, validCat, summary, validSev)
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastAfterEdit(req)
res.json({ ok: true, id })
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
/** DELETE 删除一条事件脉络 */
router.delete('/edit/situation-update/:id', (req, res) => {
try {
const id = (req.params.id || '').toString().trim()
if (!id) return res.status(400).json({ error: 'id required' })
const r = db.prepare('DELETE FROM situation_update WHERE id = ?').run(id)
if (r.changes === 0) return res.status(404).json({ error: 'not found' })
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastAfterEdit(req)
res.json({ ok: true })
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
/** PUT 更新军力概要(美/伊) */
router.put('/edit/force-summary', (req, res) => {
try {
const side = req.body?.side
if (side !== 'us' && side !== 'iran') {
return res.status(400).json({ error: 'side must be us or iran' })
}
const cols = ['total_assets', 'personnel', 'naval_ships', 'aircraft', 'ground_units', 'uav', 'missile_consumed', 'missile_stock']
const updates = []
const values = []
for (const c of cols) {
if (req.body[c] !== undefined) {
updates.push(`${c} = ?`)
values.push(Number(req.body[c]) || 0)
}
}
if (updates.length === 0) return res.status(400).json({ error: 'no fields to update' })
values.push(side)
db.prepare(`UPDATE force_summary SET ${updates.join(', ')} WHERE side = ?`).run(...values)
db.prepare("INSERT OR REPLACE INTO situation (id, data, updated_at) VALUES (1, '{}', ?)").run(new Date().toISOString())
broadcastAfterEdit(req)
res.json({ ok: true })
} catch (err) {
console.error(err)
res.status(500).json({ error: err.message })
}
})
module.exports = router