// ── Public · Clans — the handlers ───────────────────────────────────────── // // Thin, like `world.controller.js`, and for the same reason: everything worth // testing is in the model, which needs no express and no database to test. // // The one thing these two do differently is read the caller. // `core.auth.getUserFromRequest` is READ-ONLY access to who is asking — minting a // session is core's job, and a module that needs an identity needs to read one, // never to issue one. It is awaited and it never throws for an anonymous caller; // it answers `null`, which is an answer the model expects. const core = require('../../core') const clans = require('../../model/clans/clans.model') const log = core.logger('clans') /** * The viewer core's contract describes: `{ userId, role }`, or `null`. * * Built here rather than passed as a request, so the model takes the same shape * core hands `projectRoster` and one audience rule can serve both. Handing a * model the whole `req` is what makes a rule impossible to reuse from a call that * has no request — and the provider's call has none. */ async function viewerFrom(req) { const user = await core.auth.getUserFromRequest(req) return user ? { userId: user.id, role: user.role } : null } async function list(req, res) { try { res.json(await clans.listPublic()) } catch (err) { log.error('failed to list clans', { error: err.message }) res.status(500).json({ error: 'Failed to list clans' }) } } async function detail(req, res) { try { const clan = await clans.getPublic(req.params.externalId, await viewerFrom(req)) if (!clan) return res.status(404).json({ error: 'No such clan' }) return res.json(clan) } catch (err) { log.error('failed to read clan', { externalId: req.params.externalId, error: err.message }) return res.status(500).json({ error: 'Failed to read clan' }) } } module.exports = { list, detail }