fix: bug
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -50,7 +50,7 @@ module.exports = {
|
||||
'/api/visit': {
|
||||
post: {
|
||||
summary: '来访统计',
|
||||
description: '记录 IP,返回当前在看人数和累积访问',
|
||||
description: '记录 IP,返回当前在看人数和看过人数',
|
||||
tags: ['统计'],
|
||||
responses: {
|
||||
200: {
|
||||
|
||||
172
server/routes.js
172
server/routes.js
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user