openapi: 3.0.3 info: title: CEH Score API version: 1.0.0 tags: - name: assessments - name: settings - name: polls paths: /api/assessments: get: tags: [ assessments ] operationId: getAssessments summary: List all assessments responses: '200': description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/Assessment' post: tags: [ assessments ] operationId: createAssessment summary: Create a new assessment description: > `percentage` and `passed` are computed server-side and must not be sent by the client. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AssessmentInput' responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/Assessment' '400': description: Invalid request body '409': description: An assessment with this ID already exists delete: tags: [ assessments ] operationId: clearAssessments summary: Delete all assessments responses: '204': description: No Content /api/assessments/{id}: delete: tags: [ assessments ] operationId: deleteAssessment summary: Delete an assessment by ID parameters: - in: path name: id required: true schema: type: string responses: '204': description: No Content '400': description: Invalid ID /api/settings: get: tags: [ settings ] operationId: getSettings summary: Get user settings responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/UserSettings' put: tags: [ settings ] operationId: updateSettings summary: Update user settings requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserSettings' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/UserSettings' '400': description: Invalid request body /api/polls: get: tags: [ polls ] operationId: getPollResults summary: List poll result rows, optionally filtered by pollId parameters: - in: query name: pollId required: false schema: type: string responses: '200': description: OK content: application/json: schema: type: array items: $ref: '#/components/schemas/PollResult' '400': description: Invalid pollId post: tags: [ polls ] operationId: createPollResult summary: Create a new poll option row requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePollResultRequest' responses: '201': description: Created content: application/json: schema: $ref: '#/components/schemas/PollResult' '400': description: Invalid request body '409': description: A poll option with this text already exists for this poll /api/polls/{pollId}: get: tags: [ polls ] operationId: getPollStats summary: Get aggregated statistics for a poll parameters: - in: path name: pollId required: true schema: type: string responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/PollStats' '400': description: Invalid pollId '404': description: Poll not found delete: tags: [ polls ] operationId: deletePoll summary: Delete all results for a poll parameters: - in: path name: pollId required: true schema: type: string responses: '204': description: No Content '400': description: Invalid pollId /api/polls/{pollId}/votes: post: tags: [ polls ] operationId: votePoll summary: Record a vote for a poll option (rate-limited) parameters: - in: path name: pollId required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VoteRequest' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/PollResult' '400': description: Invalid request body or pollId '429': description: Too many requests components: schemas: AssessmentInput: type: object required: [ id, date, type, score, maxScore, timeTaken, domain, createdAt ] properties: id: type: string date: type: string format: date type: type: string enum: [ practice, official, mock ] score: type: integer maxScore: type: integer timeTaken: type: integer domain: type: string notes: type: string createdAt: type: string format: date-time Assessment: type: object required: [ id, date, type, score, maxScore, percentage, timeTaken, domain, notes, passed, createdAt ] properties: id: type: string date: type: string format: date type: type: string enum: [ practice, official, mock ] score: type: integer maxScore: type: integer percentage: type: number timeTaken: type: integer domain: type: string notes: type: string passed: type: boolean createdAt: type: string format: date-time UserSettings: type: object required: [ name, targetScore, examDate, theme ] properties: name: type: string targetScore: type: integer examDate: type: string theme: type: string enum: [ dark, light ] PollResult: type: object required: [ id, pollId, pollQuestion, optionText, voteCount, createdAt, updatedAt ] properties: id: type: integer pollId: type: string pollQuestion: type: string optionText: type: string voteCount: type: integer userId: type: string nullable: true createdAt: type: string format: date-time updatedAt: type: string format: date-time PollStats: type: object required: [ pollId, pollQuestion, totalVotes, options, createdAt, updatedAt ] properties: pollId: type: string pollQuestion: type: string totalVotes: type: integer options: type: array items: $ref: '#/components/schemas/PollStatsOption' createdAt: type: string format: date-time updatedAt: type: string format: date-time PollStatsOption: type: object required: [ id, optionText, voteCount, percentage ] properties: id: type: integer optionText: type: string voteCount: type: integer percentage: type: integer CreatePollResultRequest: type: object required: [ pollId, pollQuestion, optionText ] properties: pollId: type: string pollQuestion: type: string optionText: type: string userId: type: string nullable: true VoteRequest: type: object required: [ optionText ] properties: optionText: type: string pollQuestion: type: string userId: type: string nullable: true