168 lines
4.8 KiB
JavaScript
168 lines
4.8 KiB
JavaScript
const express = require('express')
|
||
const { getSituation } = require('./situationData')
|
||
const db = require('./db')
|
||
|
||
const router = express.Router()
|
||
|
||
// 数据库 Dashboard:返回各表原始数据
|
||
router.get('/db/dashboard', (req, res) => {
|
||
try {
|
||
const tables = [
|
||
'feedback',
|
||
'situation',
|
||
'force_summary',
|
||
'power_index',
|
||
'force_asset',
|
||
'key_location',
|
||
'combat_losses',
|
||
'wall_street_trend',
|
||
'retaliation_current',
|
||
'retaliation_history',
|
||
'situation_update',
|
||
'news_content',
|
||
'gdelt_events',
|
||
'conflict_stats',
|
||
]
|
||
const data = {}
|
||
const timeSort = {
|
||
feedback: 'created_at DESC',
|
||
situation: 'updated_at DESC',
|
||
situation_update: 'timestamp DESC',
|
||
news_content: 'published_at DESC',
|
||
gdelt_events: 'event_time DESC',
|
||
wall_street_trend: 'time DESC',
|
||
retaliation_history: 'time DESC',
|
||
conflict_stats: 'updated_at DESC',
|
||
}
|
||
for (const name of tables) {
|
||
try {
|
||
const order = timeSort[name]
|
||
let rows
|
||
try {
|
||
rows = order
|
||
? db.prepare(`SELECT * FROM ${name} ORDER BY ${order}`).all()
|
||
: db.prepare(`SELECT * FROM ${name}`).all()
|
||
} catch (qerr) {
|
||
rows = db.prepare(`SELECT * FROM ${name}`).all()
|
||
}
|
||
data[name] = rows
|
||
} catch (e) {
|
||
data[name] = { error: e.message }
|
||
}
|
||
}
|
||
res.json(data)
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
// 资讯内容(独立表,供后续消费)
|
||
router.get('/news', (req, res) => {
|
||
try {
|
||
const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200)
|
||
const rows = db.prepare('SELECT id, title, summary, url, source, published_at, category, severity, created_at FROM news_content ORDER BY published_at DESC LIMIT ?').all(limit)
|
||
res.json({ items: rows })
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
router.get('/situation', (req, res) => {
|
||
try {
|
||
res.json(getSituation())
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
// 来访统计:记录 IP,返回在看/累积
|
||
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 getStats() {
|
||
const viewers = db.prepare(
|
||
"SELECT COUNT(*) as n FROM visits WHERE last_seen > datetime('now', '-2 minutes')"
|
||
).get().n
|
||
const cumulative = db.prepare('SELECT total FROM visitor_count WHERE id = 1').get()?.total ?? 0
|
||
const feedbackCount = db.prepare('SELECT COUNT(*) as n FROM feedback').get().n ?? 0
|
||
const shareCount = db.prepare('SELECT total FROM share_count WHERE id = 1').get()?.total ?? 0
|
||
return { viewers, cumulative, feedbackCount, shareCount }
|
||
}
|
||
|
||
router.post('/visit', (req, res) => {
|
||
try {
|
||
const ip = getClientIp(req)
|
||
db.prepare(
|
||
"INSERT OR REPLACE INTO visits (ip, last_seen) VALUES (?, datetime('now'))"
|
||
).run(ip)
|
||
db.prepare(
|
||
'INSERT INTO visitor_count (id, total) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET total = total + 1'
|
||
).run()
|
||
res.json(getStats())
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0 })
|
||
}
|
||
})
|
||
|
||
router.post('/feedback', (req, res) => {
|
||
try {
|
||
const content = (req.body?.content ?? '').toString().trim()
|
||
if (!content || content.length > 2000) {
|
||
return res.status(400).json({ ok: false, error: '留言内容 1–2000 字' })
|
||
}
|
||
const ip = getClientIp(req)
|
||
db.prepare(
|
||
'INSERT INTO feedback (content, ip) VALUES (?, ?)'
|
||
).run(content.slice(0, 2000), ip)
|
||
res.json({ ok: true })
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ ok: false, error: err.message })
|
||
}
|
||
})
|
||
|
||
router.post('/share', (req, res) => {
|
||
try {
|
||
db.prepare(
|
||
'INSERT INTO share_count (id, total) VALUES (1, 1) ON CONFLICT(id) DO UPDATE SET total = total + 1'
|
||
).run()
|
||
const shareCount = db.prepare('SELECT total FROM share_count WHERE id = 1').get()?.total ?? 0
|
||
res.json({ ok: true, shareCount })
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ ok: false, shareCount: 0 })
|
||
}
|
||
})
|
||
|
||
router.get('/stats', (req, res) => {
|
||
try {
|
||
res.json(getStats())
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ viewers: 0, cumulative: 0, feedbackCount: 0, shareCount: 0 })
|
||
}
|
||
})
|
||
|
||
router.get('/events', (req, res) => {
|
||
try {
|
||
const s = getSituation()
|
||
res.json({
|
||
updated_at: s.lastUpdated,
|
||
count: (s.conflictEvents || []).length,
|
||
events: s.conflictEvents || [],
|
||
conflict_stats: s.conflictStats || {},
|
||
})
|
||
} catch (err) {
|
||
console.error(err)
|
||
res.status(500).json({ error: err.message })
|
||
}
|
||
})
|
||
|
||
module.exports = router
|