Front ends for the rest of the sidecar data, plus a security fix the live data surfaced. - lib/shardEvents.js: shared describe()/category/label for every event kind (sales, deaths & PvP, skills, fame/karma, quests, world, and staff kinds). - Public /site/shard/activity (ShardActivity): the full event log with category filter tabs and a live tail (history + SSE merged, de-duped). Linked from the Shard page. Shard page now reuses the shared describe(). - Admin: a "Live feed (all events)" panel on the Shard admin page subscribing to the admin SSE channel — shows every kind incl. audit/cheat/login attempts. useShardFeed generalized to take a stream url; api.adminShardStreamUrl added. Security fix: GET /public/shard/feed now restricts to the public-safe kind allowlist (shardEvents.list gains a `kinds` IN-filter). Previously it returned whatever was logged — including audit.* / cheat.* / link.request. Those are still stored for the admin channel but never served publicly (verified: a public request for audit.command returns 0 rows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3
42 lines
1.4 KiB
JavaScript
42 lines
1.4 KiB
JavaScript
const { query } = require('../../utils/db')
|
|
|
|
// INSERT IGNORE on the UNIQUE dedupe_key — a re-ingested event (WS-reconnect
|
|
// backfill overlap) is silently skipped rather than duplicated. Returns true if
|
|
// a new row was actually inserted.
|
|
async function insertIgnore({ kind, t, bootId, payload, dedupeKey }) {
|
|
const res = await query(
|
|
`INSERT IGNORE INTO shard_events (kind, t, boot_id, payload, dedupe_key)
|
|
VALUES (?, ?, ?, ?, ?)`,
|
|
[kind, t, bootId || null, JSON.stringify(payload), dedupeKey],
|
|
)
|
|
return res.affectedRows > 0
|
|
}
|
|
|
|
// Recent events, newest first. Filter by a single `kind`, or an allowlist of
|
|
// `kinds` (IN clause) — the public feed uses the allowlist so it can never leak
|
|
// staff/sensitive kinds. limit is clamped by the model.
|
|
async function list({ kind, kinds, limit }) {
|
|
if (kinds && kinds.length) {
|
|
const placeholders = kinds.map(() => '?').join(', ')
|
|
return query(
|
|
`SELECT id, kind, t, boot_id, payload, created_at
|
|
FROM shard_events WHERE kind IN (${placeholders}) ORDER BY t DESC LIMIT ?`,
|
|
[...kinds, limit],
|
|
)
|
|
}
|
|
if (kind) {
|
|
return query(
|
|
`SELECT id, kind, t, boot_id, payload, created_at
|
|
FROM shard_events WHERE kind = ? ORDER BY t DESC LIMIT ?`,
|
|
[kind, limit],
|
|
)
|
|
}
|
|
return query(
|
|
`SELECT id, kind, t, boot_id, payload, created_at
|
|
FROM shard_events ORDER BY t DESC LIMIT ?`,
|
|
[limit],
|
|
)
|
|
}
|
|
|
|
module.exports = { insertIgnore, list }
|