// ── Public · Rust — the handlers ────────────────────────────────────────── // // Thin on purpose: read the request, call a model, answer. Everything worth // testing is in the model, which needs no express and no database to test. // // **A handler must not throw past express.** Core mounts this router inside its // own tier router, so an unhandled rejection here reaches core's error handler // and answers 500 — survivable, but it means an operator sees core blamed for a // fault in this module. Catch, log through `core.logger` (so the line carries the // module id), and answer something honest. const core = require('../../core') const events = require('../../model/events/events.model') const servers = require('../../model/servers/servers.model') const log = core.logger('public') async function listServers(req, res) { try { res.json({ servers: await servers.listPublic() }) } catch (err) { log.error('failed to read the server list', { error: err.message }) res.status(500).json({ error: 'Failed to read the server list' }) } } /** * One server, or a 404. * * **The 404 is the feature.** Everything else under `/servers/:id` answers an * empty list for a server that does not exist — an unknown id has no events, no * leaderboard and nobody online, and each of those is a perfectly good answer to * the question it was asked. Only this route can tell the page that the server * itself is not there, which is what stops `/rust/servers/typo` rendering as a * quiet server with nothing to say. */ async function getServer(req, res) { try { const server = await servers.getPublic(req.params.id) if (!server) { res.status(404).json({ error: 'No such server' }) return } res.json({ server }) } catch (err) { log.error('failed to read a server', { server: req.params.id, error: err.message }) res.status(500).json({ error: 'Failed to read the server' }) } } /** * The killfeed, and everything else public that happened on one server. * * **`admin` is not passed, and that is the whole security posture of this * handler.** `events.recent` takes the viewer explicitly and defaults to the * public allowlist, so the way to leak an IP address from here is to add an * argument rather than to forget one. */ async function listEvents(req, res) { try { res.json({ events: await events.recent({ serverId: req.params.id, kind: req.query.kind, wipeId: req.query.wipe || null, limit: req.query.limit, }), }) } catch (err) { log.error('failed to read events', { server: req.params.id, error: err.message }) res.status(500).json({ error: 'Failed to read events' }) } } async function listLeaderboard(req, res) { try { res.json({ leaderboard: await events.leaderboard({ serverId: req.params.id, wipeId: req.query.wipe || null, sort: req.query.sort, limit: req.query.limit, }), }) } catch (err) { log.error('failed to read the leaderboard', { server: req.params.id, error: err.message }) res.status(500).json({ error: 'Failed to read the leaderboard' }) } } async function listWipes(req, res) { try { res.json({ wipes: await events.wipes(req.params.id) }) } catch (err) { log.error('failed to read wipes', { server: req.params.id, error: err.message }) res.status(500).json({ error: 'Failed to read wipes' }) } } async function listOnline(req, res) { try { res.json({ players: await events.online(req.params.id) }) } catch (err) { log.error('failed to read presence', { server: req.params.id, error: err.message }) res.status(500).json({ error: 'Failed to read who is online' }) } } module.exports = { listServers, getServer, listEvents, listLeaderboard, listWipes, listOnline }