// ── The OpenAPI fragment: the shared half ───────────────────────────────── // // The tags and component schemas the `#swagger.*` annotations refer to. // `scripts/swaggerFragment.js` feeds this to swagger-autogen; the per-endpoint // detail lives beside each route, exactly as it does in core. // // **Two rules about names, and both belong to the MERGED document rather than to // this file** (MODULE_API.md §6.1a). Core merges every started module's fragment // over its own committed spec and serves the result at `/api/docs.json`, and core // wins any key collision: // // • **Namespace what you DEFINE.** `RustServerList`, not `ServerList`. A second // game's module describing the same idea under the same bare name would // silently clobber this one or be clobbered by it. // • **Reference what CORE defines by core's name.** `#/components/schemas/Error` // and `ValidationError` are core's; point at them and do not redefine them. // // **swagger-autogen renders `components.schemas` from an EXAMPLE object, not from // raw OpenAPI.** `{ type: 'object' }` comes back as a meta-description of itself. // That is uniform across core's committed spec and is the house shape. module.exports = { tags: [ { name: 'Public · Rust', description: 'The Rust servers this site follows, as each one last reported itself', }, { name: 'Player · Rust', description: 'The Rust surface for a signed-in player', }, { name: 'Admin · Rust', description: 'Configuring the Rust servers and their sidecars', }, ], components: { schemas: { RustServerList: { type: 'object', description: 'Every Rust server this site follows (GET /public/rust/servers).', properties: { servers: { type: 'array', items: { $ref: '#/components/schemas/RustServer' }, }, }, }, RustServer: { type: 'object', description: 'One Rust server, as it last reported itself.', properties: { id: { type: 'string', example: 'main' }, name: { type: 'string', example: 'Main · Vanilla' }, online: { type: 'boolean', example: true }, players: { type: 'integer', example: 42 }, maxPlayers: { type: 'integer', example: 100 }, hostname: { type: 'string', nullable: true, example: 'Runic Gateway · Main' }, level: { type: 'string', nullable: true, example: 'Procedural Map' }, worldSize: { type: 'integer', nullable: true, example: 4000 }, seed: { type: 'integer', nullable: true, example: 1234 }, updatedAt: { type: 'string', format: 'date-time', nullable: true }, stale: { type: 'boolean', description: 'Has nothing reported in longer than the freshness window? A stale row is reported offline.', example: false, }, }, }, RustAdminServerList: { type: 'object', description: 'The configured servers, with their sidecar settings (GET /admin/rust/servers).', properties: { servers: { type: 'array', items: { $ref: '#/components/schemas/RustAdminServer' }, }, }, }, RustAdminServer: { type: 'object', description: 'One configured server. The sidecar token is never included — `hasToken` reports only whether one is stored.', properties: { id: { type: 'string', example: 'main' }, name: { type: 'string', example: 'Main · Vanilla' }, sidecarBaseUrl: { type: 'string', example: 'http://10.0.0.5:8090' }, hasToken: { type: 'boolean', example: true }, protocol: { type: 'integer', example: 1 }, enabled: { type: 'boolean', example: true }, sortOrder: { type: 'integer', example: 0 }, reachable: { type: 'boolean', description: 'Did the sidecar answer on the last poll? Separate from `online`, which is about the game rather than the bridge.', example: true, }, bootId: { type: 'string', nullable: true, example: 'boot-20260915T194502Z' }, sidecarProtocol: { type: 'integer', nullable: true, example: 1 }, online: { type: 'boolean', example: true }, players: { type: 'integer', example: 42 }, stale: { type: 'boolean', example: false }, }, }, RustLink: { type: 'object', description: 'One Steam account linked to a website user. Never carries a code.', properties: { steamId: { type: 'string', example: '76561198000000000' }, name: { type: 'string', nullable: true, description: 'What the player was called in game when they linked. A display name only — a Rust name changes on a whim and nothing identifies anybody by it.', example: 'Wanderer', }, serverId: { type: 'string', nullable: true, description: 'Which server minted the code. Not part of the identity — a link is fleet-wide — but it is where a support conversation starts.', example: 'main', }, linkedAt: { type: 'string', format: 'date-time' }, }, }, RustLinkList: { type: 'object', description: 'The Steam accounts one website user holds (GET /player/rust/links).', properties: { links: { type: 'array', items: { $ref: '#/components/schemas/RustLink' } }, }, }, RustLinkRequest: { type: 'object', required: ['code'], properties: { code: { type: 'string', description: 'The six-character code /link handed the player in game. Good for five minutes, and it works once.', example: 'K7M2PQ', }, }, }, RustLinkResult: { type: 'object', description: 'The result of redeeming a code.', properties: { linked: { type: 'boolean', example: true }, link: { $ref: '#/components/schemas/RustLink' }, already: { type: 'boolean', description: 'True when this Steam id was already linked to the caller — a second press of the button, not an error.', example: false, }, }, }, RustAdminLinkList: { type: 'object', description: 'One user’s Rust identity, for the admin.users.detail panel (GET /admin/users/{id}/rust/links).', properties: { links: { type: 'array', items: { type: 'object', properties: { steamId: { type: 'string', example: '76561198000000000' }, name: { type: 'string', nullable: true, description: 'What the game last saw this player called, falling back to the name recorded at link time.', example: 'Wanderer', }, linkedName: { type: 'string', nullable: true, example: 'Wanderer' }, serverId: { type: 'string', nullable: true, example: 'main' }, linkedAt: { type: 'string', format: 'date-time' }, firstSeen: { type: 'string', format: 'date-time', nullable: true }, lastSeen: { type: 'string', format: 'date-time', nullable: true }, servers: { type: 'array', description: 'All-time totals per server, summed across every wipe.', items: { type: 'object', properties: { serverId: { type: 'string', example: 'main' }, serverName: { type: 'string', example: 'Main · Vanilla' }, kills: { type: 'integer', example: 41 }, deaths: { type: 'integer', example: 37 }, npcKills: { type: 'integer', example: 120 }, structures: { type: 'integer', example: 64 }, playtimeSec: { type: 'integer', example: 43200 }, wipes: { type: 'integer', example: 2 }, lastSeen: { type: 'string', format: 'date-time', nullable: true }, }, }, }, }, }, }, }, }, RustSidecarProbe: { type: 'object', description: 'What a sidecar said when probed (POST /admin/rust/servers/{id}/test).', properties: { ok: { type: 'boolean', example: true }, status: { type: 'string', description: 'What happened, in one word — this is what tells a wrong URL from a wrong token from a mismatched protocol. One of `ok`, `no-token`, `unauthorized`, `protocol-mismatch`, `timeout`, `transport-error`, or `http-`.', example: 'ok', }, sidecar: { type: 'object', nullable: true, description: 'The sidecar’s own health document, or the mismatch detail on a protocol disagreement.', properties: { status: { type: 'string', example: 'ok' }, protocol: { type: 'integer', example: 1 }, plugin_connected: { type: 'boolean', example: true }, database: { type: 'string', example: 'ok' }, uptime: { type: 'string', example: '3h 2m' }, last_event: { type: 'string', format: 'date-time', nullable: true }, }, }, }, }, }, }, }