// ============================================================ // OPENAPI — what each endpoint is for // ============================================================ // Paths and methods are read from the router (src/utils/openapi.js). This is // the half introspection cannot supply: what an operation does, what it takes, // and whether it needs a session. // // Keyed by "METHOD /path" exactly as the route is mounted, with :params left as // they appear in the code. A route with no entry here fails test/openapi.test.js // — that is the point. A new endpoint cannot ship without a sentence saying what // it is, and an entry for an endpoint that no longer exists fails the same test // from the other direction. // // `public: true` marks an operation that works with no session. Say it // explicitly: guessing from middleware gets it wrong in the direction that // hides a hole. // Path parameters, described once rather than at every route that takes them. var parameters = { id: 'Identifier of the record, scoped to the signed-in account.', workflow: 'Which feature the job belongs to (for example my_resources).', key: 'Setting key, for example clinical_assistant.chat_model.', slug: 'URL-safe name of the document.' }; var operations = { // ── Session ───────────────────────────────────────────────────────── 'POST /api/auth/login': { summary: 'Sign in with a password', description: 'Returns a token and sets the ped_auth cookie. Rate limited.', public: true }, 'POST /api/auth/login-code/request': { summary: 'Email a single-use sign-in code', description: 'Always answers the same way whether or not the address has an account, so it cannot be used to discover who is registered.', public: true }, 'POST /api/auth/login-code/verify': { summary: 'Exchange a sign-in code for a session', public: true }, 'POST /api/auth/register': { summary: 'Create an account', description: 'Requires an invitation code while registration is invite-only.', public: true }, 'GET /api/auth/registration-status': { summary: 'Whether registration is open, and whether it needs an invitation', description: 'Read by the sign-in screen to decide whether to offer the register link.', public: true }, 'POST /api/auth/logout': { summary: 'End this session' }, 'GET /api/auth/me': { summary: 'The signed-in account' }, // ── Clinical assistant ────────────────────────────────────────────── 'POST /api/clinical-assistant/ask': { summary: 'Ask a question against the clinical corpus', description: 'Streams the answer as server-sent events, with the retrieved sources.' }, // ── My Resources ──────────────────────────────────────────────────── 'GET /api/my-resources': { summary: 'Teaching material belonging to this account' }, 'GET /api/my-resources/:id': { summary: 'One saved resource' }, 'POST /api/my-resources/generate': { summary: 'Generate a deck or handout' }, 'POST /api/my-resources/:id/refine': { summary: 'Revise a saved resource' }, 'GET /api/my-resources/:id/export': { summary: 'Download as PowerPoint, Word or PDF' }, 'GET /api/my-resources/theme-sample/:id': { summary: 'A sample deck in one theme', description: 'Every slide layout with placeholder text, as a PowerPoint file, so a theme can be judged before it is used.' }, 'POST /api/my-resources/:id/to-nextcloud': { summary: 'Send the rendered file to the owner\'s Nextcloud' }, // ── Health ────────────────────────────────────────────────────────── 'GET /api/health': { summary: 'Liveness', public: true }, 'GET /api/build': { summary: 'The revision this container is running', public: true } }; module.exports = { operations: operations, parameters: parameters };