From 9be7b1830ded2298caa1321785312f1c83915cda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:42:55 +0000 Subject: [PATCH 1/6] Initial plan From 8ae26e97ff00e57150ddb9bc0d58aff7de32cd20 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:52:56 +0000 Subject: [PATCH 2/6] Update OpenAPI specs: split into frontend (rest.yaml) and integration (integration.yaml) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite rest.yaml to match current implementation with all endpoints - Add auth, users, API keys, export, import, schedule CRUD, batch - Fix DTO schemas: add missing fields (type, classe in Group), fix required fields - Fix type: number → type: integer for IDs and sizes - Fix schedule endpoints (now under /schedule, not /levels/{levelId}/schedule) - Fix GET /levels returns LevelDetailsDTO[], not LevelDTO[] - Add proper JWT bearer security scheme with operationIds and tags - Create integration.yaml for read-only integration API with X-Api-Key auth - Add reusable parameters, responses, and proper error schemas Co-authored-by: rivon0507 <107705903+rivon0507@users.noreply.github.com> --- src/main/resources/api/integration.yaml | 603 +++++++ src/main/resources/api/rest.yaml | 2046 ++++++++++++++++------- 2 files changed, 2067 insertions(+), 582 deletions(-) create mode 100644 src/main/resources/api/integration.yaml diff --git a/src/main/resources/api/integration.yaml b/src/main/resources/api/integration.yaml new file mode 100644 index 0000000..536e0b0 --- /dev/null +++ b/src/main/resources/api/integration.yaml @@ -0,0 +1,603 @@ +openapi: 3.1.0 +info: + title: Pointeur Backend – Integration API + description: | + Read-only REST API for external integrations with the Pointeur scheduling + application. This surface is served under `/integration` and requires an + API key passed via the `X-Api-Key` header. + + **Constraints:** + - Only `GET` requests are accepted; all other HTTP methods are denied. + - Endpoints related to users and API key management are not available. + version: 2.0.0 + contact: + name: 2RK-dev + +servers: + - url: http://localhost:8080/integration + description: Local development server + +security: + - apiKeyAuth: [] + +tags: + - name: Levels + description: Academic levels + - name: Groups + description: Student groups within levels + - name: Teachers + description: Teacher registry + - name: Teaching Units + description: Teaching unit (subject) registry + - name: Rooms + description: Room registry + - name: Schedule + description: Schedule queries + +# --------------------------------------------------------------------------- +# Paths — read-only subset +# --------------------------------------------------------------------------- +paths: + + # ── Levels ──────────────────────────────────────────────────────────────── + /levels: + get: + operationId: integration_listLevels + tags: [Levels] + summary: List all levels with their groups + responses: + '200': + description: List of levels with nested groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + /levels/{levelId}: + get: + operationId: integration_getLevel + tags: [Levels] + summary: Get a level with its groups + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Level details with groups + content: + application/json: + schema: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Groups ──────────────────────────────────────────────────────────────── + /levels/{levelId}/groups: + get: + operationId: integration_listGroupsByLevel + tags: [Groups] + summary: List groups in a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: List of groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /levels/{levelId}/groups/{groupId}: + get: + operationId: integration_getGroup + tags: [Groups] + summary: Get a group + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' + responses: + '200': + description: Group details + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group or level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Teachers ────────────────────────────────────────────────────────────── + /teachers: + get: + operationId: integration_listTeachers + tags: [Teachers] + summary: List all teachers + responses: + '200': + description: List of teachers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + /teachers/{teacherId}: + get: + operationId: integration_getTeacher + tags: [Teachers] + summary: Get a teacher + parameters: + - $ref: '#/components/parameters/teacherId' + responses: + '200': + description: Teacher details + content: + application/json: + schema: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teacher not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Teaching Units ──────────────────────────────────────────────────────── + /teachingUnits: + get: + operationId: integration_listTeachingUnits + tags: [Teaching Units] + summary: List all teaching units + responses: + '200': + description: List of teaching units + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + /teachingUnits/{unitId}: + get: + operationId: integration_getTeachingUnit + tags: [Teaching Units] + summary: Get a teaching unit + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '200': + description: Teaching unit details + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /levels/{levelId}/teachingUnits: + get: + operationId: integration_listTeachingUnitsByLevel + tags: [Teaching Units] + summary: List teaching units for a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Teaching units taught in the level + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Rooms ───────────────────────────────────────────────────────────────── + /rooms: + get: + operationId: integration_listRooms + tags: [Rooms] + summary: List all rooms + responses: + '200': + description: List of rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + /rooms/{roomId}: + get: + operationId: integration_getRoom + tags: [Rooms] + summary: Get a room + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '200': + description: Room details + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /rooms/available: + get: + operationId: integration_listAvailableRooms + tags: [Rooms] + summary: List available rooms in a time range + description: Returns rooms that are unoccupied between the given start and end times. + parameters: + - name: startTime + in: query + required: true + description: Start date-time (ISO 8601) + schema: + type: string + format: date-time + - name: endTime + in: query + required: true + description: End date-time (ISO 8601) + schema: + type: string + format: date-time + - name: size + in: query + required: false + description: Minimum room capacity (defaults to 1) + schema: + type: integer + minimum: 1 + default: 1 + responses: + '200': + description: Available rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Invalid or missing parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + + # ── Schedule ────────────────────────────────────────────────────────────── + /schedule: + get: + operationId: integration_getSchedule + tags: [Schedule] + summary: Query the schedule + description: | + Returns schedule items in the given date range. Optionally filter by + level and/or group. + parameters: + - name: startDate + in: query + required: true + schema: + type: string + format: date + - name: endDate + in: query + required: true + schema: + type: string + format: date + - name: levelId + in: query + required: false + schema: + type: integer + format: int64 + - name: groupId + in: query + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: Matching schedule items + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Invalid date range or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group not found in the specified level + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /schedule/{scheduleId}: + get: + operationId: integration_getScheduleItem + tags: [Schedule] + summary: Get a schedule item + parameters: + - $ref: '#/components/parameters/scheduleId' + responses: + '200': + description: Schedule item details + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Schedule item not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + +# --------------------------------------------------------------------------- +# Components +# --------------------------------------------------------------------------- +components: + + securitySchemes: + apiKeyAuth: + type: apiKey + in: header + name: X-Api-Key + description: API key issued through the frontend API's API Keys management + + # ── Reusable parameters ────────────────────────────────────────────────── + parameters: + levelId: + name: levelId + in: path + required: true + description: Level identifier + schema: + type: integer + format: int64 + groupId: + name: groupId + in: path + required: true + description: Group identifier + schema: + type: integer + format: int64 + teacherId: + name: teacherId + in: path + required: true + description: Teacher identifier + schema: + type: integer + format: int64 + unitId: + name: unitId + in: path + required: true + description: Teaching unit identifier + schema: + type: integer + format: int64 + roomId: + name: roomId + in: path + required: true + description: Room identifier + schema: + type: integer + format: int64 + scheduleId: + name: scheduleId + in: path + required: true + description: Schedule item identifier + schema: + type: integer + format: int64 + + # ── Reusable responses ─────────────────────────────────────────────────── + responses: + Unauthorized: + description: Missing or invalid API key + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Schemas (read-only subset) ─────────────────────────────────────────── + schemas: + + LevelDTO: + type: object + required: [id, name, abbreviation] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + LevelDetailsDTO: + type: object + required: [level, groups] + properties: + level: + $ref: '#/components/schemas/LevelDTO' + groups: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + + GroupDTO: + type: object + required: [id, name, type, classe, size, level] + properties: + id: + type: integer + format: int64 + name: + type: string + type: + type: string + classe: + type: string + size: + type: integer + level: + $ref: '#/components/schemas/LevelDTO' + + TeacherDTO: + type: object + required: [id, name, abbreviation] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + TeachingUnitDTO: + type: object + required: [id, abbreviation, name] + properties: + id: + type: integer + format: int64 + abbreviation: + type: string + name: + type: string + level: + $ref: '#/components/schemas/LevelDTO' + description: Associated level (may be null) + + RoomDTO: + type: object + required: [id, name, abbreviation, size] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + size: + type: integer + + ScheduleItemDTO: + type: object + required: [id, groups, teacher, teachingUnit, room, startTime, endTime] + properties: + id: + type: integer + format: int64 + groups: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + teacher: + $ref: '#/components/schemas/TeacherDTO' + teachingUnit: + $ref: '#/components/schemas/TeachingUnitDTO' + room: + $ref: '#/components/schemas/RoomDTO' + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time + + ErrorDetails: + type: object + required: [timestamp, message, errorCode] + properties: + timestamp: + type: string + format: date-time + message: + type: string + details: + type: string + errorCode: + type: string + description: Machine-readable error code diff --git a/src/main/resources/api/rest.yaml b/src/main/resources/api/rest.yaml index 0a8fe55..7dc5cfe 100644 --- a/src/main/resources/api/rest.yaml +++ b/src/main/resources/api/rest.yaml @@ -1,1071 +1,1953 @@ openapi: 3.1.0 info: - title: Pointeur Backend API specification - description: Pointeur Backend API specification - version: 1.0.0 + title: Pointeur Backend – Frontend API + description: | + REST API for the Pointeur scheduling application. + This specification covers the **frontend-facing** surface served under `/api/v1`. + All endpoints require JWT Bearer authentication unless stated otherwise. + version: 2.0.0 + contact: + name: 2RK-dev + servers: - - url: 'http://localhost:8080/api/v1' - description: Development server + - url: http://localhost:8080/api/v1 + description: Local development server + +tags: + - name: Authentication + description: Login, logout, token refresh, and password management + - name: Users + description: User account management (SuperAdmin only) + - name: API Keys + description: API key management for the integration surface (SuperAdmin only) + - name: Levels + description: Academic levels + - name: Groups + description: Student groups within levels + - name: Teachers + description: Teacher registry + - name: Teaching Units + description: Teaching unit (subject) registry + - name: Rooms + description: Room registry and availability + - name: Schedule + description: Schedule item management + - name: Import + description: Bulk data import + - name: Export + description: Data export +# --------------------------------------------------------------------------- +# Paths +# --------------------------------------------------------------------------- paths: - /levels: + + # ── Authentication ──────────────────────────────────────────────────────── + /auth/login: post: - description: Create a new level - security: - - auth: [ ] + operationId: login + tags: [Authentication] + summary: Authenticate a user + description: | + Validates credentials and returns a JWT access token together with user + information. Refresh-token and device-id cookies are set automatically. + security: [] requestBody: - description: Information about the level required: true content: application/json: schema: - $ref: '#/components/schemas/CreateLevelDTO' + $ref: '#/components/schemas/LoginRequestDTO' responses: - 201: - description: The Level was successfully created - content: - application/json: + '200': + description: Authentication successful + headers: + Set-Cookie: + description: '`refresh_token` and `device_id` HttpOnly cookies' schema: - $ref: '#/components/schemas/LevelDTO' - - 400: - description: Validation error (the provided level name is blank) + type: string content: application/json: schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The name of the level already exists + $ref: '#/components/schemas/LoginResponseDTO' + '401': + description: Invalid credentials content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' + + /auth/me: get: - description: Fetch all the existing levels + operationId: getCurrentUser + tags: [Authentication] + summary: Get the current authenticated user security: - - auth: [ ] + - bearerAuth: [] responses: - 200: - description: A list of the levels + '200': + description: Current user information content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/LevelDTO' + $ref: '#/components/schemas/UserInfoDTO' + '401': + $ref: '#/components/responses/Unauthorized' - /levels/{levelId}: - get: - description: Get the informations of a level - security: - - auth: [ ] + /auth/refresh: + post: + operationId: refreshToken + tags: [Authentication] + summary: Refresh the access token + description: | + Uses the `refresh_token` and `device_id` cookies to issue a new access + token. The refresh-token cookie is rotated. + security: [] parameters: - - in: path - name: levelId - description: the identifier of the level + - name: device_id + in: cookie schema: - type: number - required: true + type: string + - name: refresh_token + in: cookie + schema: + type: string responses: - 200: - description: The level's name and ID, along its groups + '200': + description: Token refreshed + headers: + Set-Cookie: + description: Updated `refresh_token` HttpOnly cookie + schema: + type: string content: application/json: schema: - $ref: '#/components/schemas/LevelDetailsDTO' - put: - description: Change the infos of a level - security: - - auth: [ ] + $ref: '#/components/schemas/LoginResponseDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + /auth/logout: + post: + operationId: logout + tags: [Authentication] + summary: Log out the current session + description: Clears the refresh-token cookie. + security: [] parameters: - - in: path - name: levelId - description: the identifier of the level + - name: device_id + in: cookie schema: - type: number - required: true + type: string + - name: refresh_token + in: cookie + schema: + type: string + responses: + '204': + description: Logged out successfully + + /auth/password: + put: + operationId: changePassword + tags: [Authentication] + summary: Change the current user's password + security: + - bearerAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateLevelDTO' + $ref: '#/components/schemas/ChangePasswordDTO' responses: - 200: - description: Successfully updated the level - content: - application/json: - schema: - $ref: '#/components/schemas/LevelDTO' - 400: - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: Level does not exist + '200': + description: Password changed successfully content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' - 409: - description: The new name of the level is already taken + type: string + '400': + description: Validation error or wrong old password content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - delete: - description: Deletes a specified level - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - security: - - auth: [ ] - responses: - 204: - description: The level was deleted, or it didn't exist to begin with + '401': + $ref: '#/components/responses/Unauthorized' - /levels/{levelId}/groups: + # ── Users ───────────────────────────────────────────────────────────────── + /users: get: - description: Fetches the groups in this level - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true + operationId: listUsers + tags: [Users] + summary: List all users + description: Returns all users except the SuperAdmin account. Requires SuperAdmin role. security: - - auth: [ ] + - bearerAuth: [] responses: - 200: - description: A list of the groups in this level + '200': + description: List of users content: application/json: schema: type: array items: - $ref: '#/components/schemas/GroupDTO' - 404: - description: The queried level does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: '#/components/schemas/UserDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' post: - description: Create a new group in this level - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true + operationId: createUser + tags: [Users] + summary: Create a new admin user + description: Creates a new user with a randomly generated password. Requires SuperAdmin role. security: - - auth: [ ] + - bearerAuth: [] requestBody: - description: The infos of the new group + required: true content: application/json: schema: - $ref: '#/components/schemas/CreateGroupDTO' + $ref: '#/components/schemas/CreateUserDTO' responses: - 201: - description: Group successfully created + '201': + description: User created successfully content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' - 400: - description: Validation error (blank group name or group size less than 1) + $ref: '#/components/schemas/UserCreatedDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: The queried level does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' - /levels/{levelId}/groups/{groupId}: + /users/{userId}: get: - description: Fetch a group's infos - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - - in: path - name: groupId - description: the identifier of the group, among those of the same level as it - required: true - schema: - type: number + operationId: getUser + tags: [Users] + summary: Get a user by ID security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/userId' responses: - 200: - description: Infos of the group + '200': + description: User details content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' - 404: - description: The queried group does not exist in the level, or the level itself doesn't + $ref: '#/components/schemas/UserDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + description: User not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - put: - description: Update the infos of a group (cannot change levels) + delete: + operationId: deleteUser + tags: [Users] + summary: Delete a user + security: + - bearerAuth: [] parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - - in: path - name: groupId - description: the identifier of the group, among those of the same level as it - required: true - schema: - type: number + - $ref: '#/components/parameters/userId' + responses: + '204': + description: User deleted + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── API Keys ────────────────────────────────────────────────────────────── + /api-keys: + get: + operationId: listApiKeys + tags: [API Keys] + summary: List all API keys + security: + - bearerAuth: [] + responses: + '200': + description: List of API keys (tokens are masked) + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ApiKeyResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + post: + operationId: createApiKey + tags: [API Keys] + summary: Create a new API key + description: | + The raw token is returned **only once** in this response. Store it + securely — it cannot be retrieved again. security: - - auth: [ ] + - bearerAuth: [] requestBody: - description: New infos of the group + required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateGroupDTO' + $ref: '#/components/schemas/ApiKeyCreateRequest' responses: - 200: - description: Group successfully updated + '200': + description: API key created content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' - 400: - description: Validation error (blank group name or group size less than 1) + $ref: '#/components/schemas/ApiKeyWithRawToken' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: The queried group does not exist in the level, or the level itself doesn't - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + /api-keys/{apiKeyId}: delete: - description: Delete a group - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - - in: path - name: groupId - description: the identifier of the group, among those of the same level as it - required: true - schema: - type: number + operationId: deleteApiKey + tags: [API Keys] + summary: Revoke an API key security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/apiKeyId' responses: - 204: - description: The group was deleted, or it didn't exist to begin with + '204': + description: API key revoked + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' - /teachers: + # ── Levels ──────────────────────────────────────────────────────────────── + /levels: get: - description: Fetch all the registered teachers + operationId: listLevels + tags: [Levels] + summary: List all levels with their groups security: - - auth: [ ] + - bearerAuth: [] responses: - 200: - description: List of all the registered teachers + '200': + description: List of levels with nested groups content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeacherDTO' + $ref: '#/components/schemas/LevelDetailsDTO' post: - description: Register (not create :P) a new teacher + operationId: createLevel + tags: [Levels] + summary: Create a new level security: - - auth: [ ] + - bearerAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateTeacherDTO' + $ref: '#/components/schemas/CreateLevelDTO' responses: - 201: - description: New teacher successfully registered + '201': + description: Level created content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' - 400: - description: Validation error (the provided teacher's name or abbreviation is blank) + $ref: '#/components/schemas/LevelDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The abbreviation is already taken by an existing teacher + '409': + description: Level name already exists content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - /teachers/{teacherId}: + /levels/{levelId}: get: - description: Fetch the infos about a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number + operationId: getLevel + tags: [Levels] + summary: Get a level with its groups security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' responses: - 200: - description: The requested teacher's informations + '200': + description: Level details with groups content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' - 404: - description: No teacher with the provided ID exists + $ref: '#/components/schemas/LevelDetailsDTO' + '404': + description: Level not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' put: - description: Update the infos of a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number + operationId: updateLevel + tags: [Levels] + summary: Update a level security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateTeacherDTO' + $ref: '#/components/schemas/UpdateLevelDTO' responses: - 200: - description: The teacher's info was successfully updated + '200': + description: Level updated content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' - 400: - description: Validation error (the provided teacher's name or abbreviation is blank) + $ref: '#/components/schemas/LevelDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: No teacher with the provided ID exists + '404': + description: Level not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - 409: - description: The abbreviation is already taken by an existing teacher + '409': + description: Level name already taken content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' delete: - description: Delete a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number + operationId: deleteLevel + tags: [Levels] + summary: Delete a level security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' responses: - 204: - description: The teacher was deleted, or it didn't exist to begin with + '204': + description: Level deleted - /rooms: + # ── Groups (under a Level) ─────────────────────────────────────────────── + /levels/{levelId}/groups: get: - description: Fetch all the teaching rooms + operationId: listGroupsByLevel + tags: [Groups] + summary: List groups in a level security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' responses: - 200: - description: A list of all the existing teaching rooms + '200': + description: List of groups content: application/json: schema: type: array items: - $ref: '#/components/schemas/RoomDTO' + $ref: '#/components/schemas/GroupDTO' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' post: - description: Create a new teaching room + operationId: createGroup + tags: [Groups] + summary: Create a group in a level security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateRoomDTO' + $ref: '#/components/schemas/CreateGroupDTO' responses: - 201: - description: Room created successfully + '201': + description: Group created content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' - 400: - description: Validation error (room size less than 1) + $ref: '#/components/schemas/GroupDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The room name is already taken (already exists) + '404': + description: Level not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - /rooms/available: - get: - summary: Get available rooms in a time range - description: Returns rooms that are unoccupied between the specified startTime and endTime times. - parameters: - - name: startTime - in: query - required: true - description: startTime datetime (ISO 8601 format) - schema: - type: string - format: date-time - - name: endTime - in: query - required: true - description: End datetime (ISO 8601 format) - schema: - type: string - format: date-time - - name: size - in: query - required: false - description: Minimum room capacity required - schema: - type: integer - minimum: 1 - responses: - 200: - description: List of available rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - 400: - description: Invalid or missing parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - - /rooms/{roomId}: + /levels/{levelId}/groups/{groupId}: get: - description: Fetch the infos about a teaching room - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number + operationId: getGroup + tags: [Groups] + summary: Get a group security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' responses: - 200: - description: The requested room's info + '200': + description: Group details content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' - 404: - description: No room with the provided identifier exists + $ref: '#/components/schemas/GroupDTO' + '404': + description: Group or level not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' put: - description: Alter a teaching room's info - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number + operationId: updateGroup + tags: [Groups] + summary: Update a group security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateRoomDTO' + $ref: '#/components/schemas/UpdateGroupDTO' responses: - 200: - description: The room's info was successfully updated + '200': + description: Group updated content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' - 400: - description: Validation error (room size less than 1) + $ref: '#/components/schemas/GroupDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: No room with the provided identifier exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - 409: - description: The room name is already taken (already exists) + '404': + description: Group or level not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' delete: - description: Delete a teaching room - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number + operationId: deleteGroup + tags: [Groups] + summary: Delete a group security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' responses: - 204: - description: The room was deleted, or it didn't exist to begin with + '204': + description: Group deleted - /teachingUnits: + # ── Teachers ────────────────────────────────────────────────────────────── + /teachers: get: - description: Fetch all teaching units + operationId: listTeachers + tags: [Teachers] + summary: List all teachers security: - - auth: [ ] + - bearerAuth: [] responses: - 200: - description: A list of all the existing teaching units across all levels + '200': + description: List of teachers content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: '#/components/schemas/TeacherDTO' post: - description: Create a new teaching unit + operationId: createTeacher + tags: [Teachers] + summary: Register a new teacher security: - - auth: [ ] + - bearerAuth: [] requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/CreateTeachingUnitDTO' + $ref: '#/components/schemas/CreateTeacherDTO' responses: - 201: - description: Teaching unit successfully created + '201': + description: Teacher registered content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' - 400: - description: Validation error (teaching unit name or abbreviation is blank, some of the provided levels do not exist) + $ref: '#/components/schemas/TeacherDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The abbreviation is already associated with an existing teaching unit + '409': + description: Abbreviation already taken content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - /teachingUnits/{unitId}: + /teachers/{teacherId}: get: - description: Get the infos of a teaching unit - parameters: - - in: path - name: unitId - description: the identifier of the teaching unit - required: true - schema: - type: number + operationId: getTeacher + tags: [Teachers] + summary: Get a teacher security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/teacherId' responses: - 200: - description: the infos about the queried teaching unit + '200': + description: Teacher details content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' - 404: - description: No teaching unit with the provided identifier exists + $ref: '#/components/schemas/TeacherDTO' + '404': + description: Teacher not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' put: - description: update the infos of a teaching unit - parameters: - - in: path - name: unitId - description: the identifier of the teaching unit - required: true - schema: - type: number + operationId: updateTeacher + tags: [Teachers] + summary: Update a teacher security: - - auth: [ ] + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/teacherId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateTeacherDTO' responses: - 200: - description: Successfully updated the teaching unit + '200': + description: Teacher updated content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' - 400: - description: Validation error (teaching unit name or abbreviation is blank, some of the provided levels do not exist) + $ref: '#/components/schemas/TeacherDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: No teaching unit with the provided identifier exists + '404': + description: Teacher not found content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' - 409: - description: The abbreviation is already associated with an existing teaching unit + '409': + description: Abbreviation already taken content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' delete: - description: Delete a teaching unit + operationId: deleteTeacher + tags: [Teachers] + summary: Delete a teacher + security: + - bearerAuth: [] parameters: - - in: path - name: unitId - description: the identifier of the teaching unit - required: true - schema: - type: number + - $ref: '#/components/parameters/teacherId' + responses: + '204': + description: Teacher deleted + + # ── Teaching Units ──────────────────────────────────────────────────────── + /teachingUnits: + get: + operationId: listTeachingUnits + tags: [Teaching Units] + summary: List all teaching units + security: + - bearerAuth: [] + responses: + '200': + description: List of teaching units + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + post: + operationId: createTeachingUnit + tags: [Teaching Units] + summary: Create a teaching unit security: - - auth: [ ] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTeachingUnitDTO' responses: - 204: - description: The teaching unit was successfully deleted, or it did not exist + '201': + description: Teaching unit created + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /teachingUnits/{unitId}: + get: + operationId: getTeachingUnit + tags: [Teaching Units] + summary: Get a teaching unit + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '200': + description: Teaching unit details + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + put: + operationId: updateTeachingUnit + tags: [Teaching Units] + summary: Update a teaching unit + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/unitId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateTeachingUnitDTO' + responses: + '200': + description: Teaching unit updated + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + delete: + operationId: deleteTeachingUnit + tags: [Teaching Units] + summary: Delete a teaching unit + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '204': + description: Teaching unit deleted /levels/{levelId}/teachingUnits: get: - description: Fetch the teaching units taught to this level + operationId: listTeachingUnitsByLevel + tags: [Teaching Units] + summary: List teaching units for a level + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Teaching units taught in the level + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Rooms ───────────────────────────────────────────────────────────────── + /rooms: + get: + operationId: listRooms + tags: [Rooms] + summary: List all rooms + security: + - bearerAuth: [] + responses: + '200': + description: List of rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + post: + operationId: createRoom + tags: [Rooms] + summary: Create a room + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateRoomDTO' + responses: + '201': + description: Room created + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Room name already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + /rooms/{roomId}: + get: + operationId: getRoom + tags: [Rooms] + summary: Get a room + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '200': + description: Room details + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + put: + operationId: updateRoom + tags: [Rooms] + summary: Update a room + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/roomId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateRoomDTO' + responses: + '200': + description: Room updated + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Room name already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + delete: + operationId: deleteRoom + tags: [Rooms] + summary: Delete a room + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '204': + description: Room deleted + + /rooms/available: + get: + operationId: listAvailableRooms + tags: [Rooms] + summary: List available rooms in a time range + description: Returns rooms that are unoccupied between the given start and end times. + security: + - bearerAuth: [] parameters: - - in: path - name: levelId - description: the identifier of the level + - name: startTime + in: query + required: true + description: Start date-time (ISO 8601) schema: - type: number + type: string + format: date-time + - name: endTime + in: query required: true - security: - - auth: [ ] + description: End date-time (ISO 8601) + schema: + type: string + format: date-time + - name: size + in: query + required: false + description: Minimum room capacity (defaults to 1) + schema: + type: integer + minimum: 1 + default: 1 responses: - 200: - description: List of all the teaching units taught to the queried level + '200': + description: Available rooms content: application/json: schema: type: array items: - $ref: '#/components/schemas/TeachingUnitDTO' - 404: - description: The queried level does not exist + $ref: '#/components/schemas/RoomDTO' + '400': + description: Invalid or missing parameters content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' + # ── Schedule ────────────────────────────────────────────────────────────── /schedule: get: - summary: Fetch the schedule, time slots - description: "If groupId does not belong to the level, will return a not found status. If groupId is not provided, - will return the schedule items of all the groups in the level. If levelId is not provided but - groupId is, will return that group's time table." + operationId: getSchedule + tags: [Schedule] + summary: Query the schedule + description: | + Returns schedule items in the given date range. Optionally filter by + level and/or group. If `groupId` is supplied without `levelId`, only + that group's timetable is returned. + security: + - bearerAuth: [] parameters: - - in: query - name: startDate + - name: startDate + in: query required: true schema: type: string format: date - - in: query - name: endDate + - name: endDate + in: query required: true schema: type: string format: date - - in: query - name: levelId + - name: levelId + in: query + required: false schema: - type: number - - in: query - name: groupId + type: integer + format: int64 + - name: groupId + in: query + required: false schema: - type: number - security: - - auth: [ ] + type: integer + format: int64 responses: - 200: - description: List of the schedule items in between startDate and endDate, scheduled for the groupId of levelId. + '200': + description: Matching schedule items content: application/json: schema: type: array items: $ref: '#/components/schemas/ScheduleItemDTO' - 404: - description: Bad request, the requested group does not belong to the provided level + '400': + description: Invalid date range or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '404': + description: Group not found in the specified level + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + post: + operationId: createScheduleItem + tags: [Schedule] + summary: Create a schedule item + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateScheduleItemDTO' + responses: + '201': + description: Schedule item created + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Validation error content: application/json: schema: $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Referenced resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Schedule conflict (room, teacher, or group overlap) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' - /levels/{levelId}/schedule: + /schedule/batch: post: - description: Add a schedule item to the schedule of a level + operationId: createScheduleItemsBatch + tags: [Schedule] + summary: Create multiple schedule items + description: | + Processes each item independently. Successfully created items and + failed items (with reasons) are returned together. security: - - auth: [ ] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CreateScheduleItemDTO' + responses: + '200': + description: Batch result + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCreateResponseScheduleItem' + + /schedule/{scheduleItemId}: + get: + operationId: getScheduleItem + tags: [Schedule] + summary: Get a schedule item + security: + - bearerAuth: [] parameters: - - in: path - name: levelId - required: true - schema: - type: number + - $ref: '#/components/parameters/scheduleItemId' + responses: + '200': + description: Schedule item details + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '404': + description: Schedule item not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + put: + operationId: updateScheduleItem + tags: [Schedule] + summary: Update a schedule item + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/scheduleItemId' requestBody: - description: Infos about the room, teacher, group, startTime, endTime, and teaching unit + required: true content: application/json: schema: - $ref: '#/components/schemas/CreateScheduleItemDTO' + $ref: '#/components/schemas/UpdateScheduleItemDTO' responses: - 201: - description: Item successfully added to the level's schedule + '200': + description: Schedule item updated content: application/json: schema: $ref: '#/components/schemas/ScheduleItemDTO' - 400: - description: Validation error (nonexistent group, teacher, teaching unit, room, overlap) + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Schedule item or referenced resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Schedule conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + delete: + operationId: deleteScheduleItem + tags: [Schedule] + summary: Delete a schedule item + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/scheduleItemId' + responses: + '204': + description: Schedule item deleted + + # ── Export ──────────────────────────────────────────────────────────────── + /export: + get: + operationId: exportData + tags: [Export] + summary: Export entities + description: | + Exports the requested entities in the specified format. The response + is a downloadable file (Excel, ZIP of CSVs, or JSON). + security: + - bearerAuth: [] + parameters: + - name: entitiesList + in: query + required: true + description: Entity types to export (e.g. room, teacher, level, group, teaching_unit) + schema: + type: array + items: + type: string + enum: [room, teacher, teaching_unit, group, level] + - name: format + in: query + required: true + description: Output format + schema: + type: string + enum: [excel, zip_csv, json] + responses: + '200': + description: Exported file + headers: + Content-Disposition: + description: 'attachment; filename=export__.' + schema: + type: string + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: string + format: binary + application/zip: + schema: + type: string + format: binary + application/json: + schema: + type: string + format: binary + '400': + description: Unknown entity or unsupported format + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Import ──────────────────────────────────────────────────────────────── + /import/upload: + post: + operationId: importData + tags: [Import] + summary: Import data from files + description: | + Accepts one or more files together with a metadata mapping that + describes how columns map to entity fields. + security: + - bearerAuth: [] + parameters: + - name: ignoreConflicts + in: query + required: false + description: Whether to skip rows that conflict with existing data + schema: + type: boolean + default: true + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [metadata, files] + properties: + metadata: + $ref: '#/components/schemas/ImportMapping' + files: + type: array + items: + type: string + format: binary + responses: + '200': + description: Import summary content: application/json: schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: The queried level does not exist + $ref: '#/components/schemas/ImportSummary' + '400': + description: Invalid file format or unsupported codec content: application/json: schema: $ref: '#/components/schemas/ErrorDetails' +# --------------------------------------------------------------------------- +# Components +# --------------------------------------------------------------------------- components: + securitySchemes: - auth: + bearerAuth: type: http - description: Access token to authenticate and authorize requests scheme: bearer bearerFormat: JWT + description: JWT access token obtained via `/auth/login` + + # ── Reusable parameters ────────────────────────────────────────────────── + parameters: + levelId: + name: levelId + in: path + required: true + description: Level identifier + schema: + type: integer + format: int64 + groupId: + name: groupId + in: path + required: true + description: Group identifier + schema: + type: integer + format: int64 + teacherId: + name: teacherId + in: path + required: true + description: Teacher identifier + schema: + type: integer + format: int64 + unitId: + name: unitId + in: path + required: true + description: Teaching unit identifier + schema: + type: integer + format: int64 + roomId: + name: roomId + in: path + required: true + description: Room identifier + schema: + type: integer + format: int64 + scheduleItemId: + name: scheduleItemId + in: path + required: true + description: Schedule item identifier + schema: + type: integer + format: int64 + userId: + name: userId + in: path + required: true + description: User identifier + schema: + type: integer + format: int64 + apiKeyId: + name: apiKeyId + in: path + required: true + description: API key identifier + schema: + type: integer + format: int64 + + # ── Reusable responses ─────────────────────────────────────────────────── + responses: + Unauthorized: + description: Authentication required or token invalid + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + Forbidden: + description: Insufficient permissions + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Schemas ────────────────────────────────────────────────────────────── schemas: - CreateScheduleItemDTO: + + # -- Auth -- + LoginRequestDTO: type: object + required: [username, password] properties: - groupIds: - type: array - items: - type: number - teacherId: - type: number - teachingUnitId: - type: number - roomId: - type: number - startTime: + username: + type: string + minLength: 1 + password: + type: string + minLength: 1 + + LoginResponseDTO: + type: object + required: [access_token, user] + properties: + access_token: + type: string + description: JWT access token + user: + $ref: '#/components/schemas/UserInfoDTO' + + UserInfoDTO: + type: object + required: [username, role] + properties: + username: + type: string + role: + type: string + description: 'User role (e.g. ADMIN, SUPERADMIN)' + + ChangePasswordDTO: + type: object + required: [old, new, confirm] + properties: + old: + type: string + minLength: 1 + new: + type: string + minLength: 1 + confirm: + type: string + minLength: 1 + description: Must match `new` + + # -- Users -- + UserDTO: + type: object + required: [id, info] + properties: + id: + type: integer + format: int64 + info: + $ref: '#/components/schemas/UserInfoDTO' + + CreateUserDTO: + type: object + required: [username] + properties: + username: + type: string + pattern: '^[a-zA-Z0-9_\-]{1,50}$' + + UserCreatedDTO: + type: object + required: [id, password, info] + properties: + id: + type: integer + format: int64 + password: + type: string + description: Auto-generated password (shown only once) + info: + $ref: '#/components/schemas/UserInfoDTO' + + # -- API Keys -- + ApiKeyCreateRequest: + type: object + required: [name] + properties: + name: + type: string + minLength: 3 + maxLength: 50 + + ApiKeyResponse: + type: object + required: [id, name, prefix, createdAt] + properties: + id: + type: integer + format: int64 + name: + type: string + prefix: + type: string + createdAt: type: string format: date-time - endTime: + + ApiKeyWithRawToken: + type: object + required: [id, name, prefix, createdAt, rawToken] + properties: + id: + type: integer + format: int64 + name: + type: string + prefix: + type: string + createdAt: type: string format: date-time - required: [ groupIds, teacherId, teachingUnitId, roomId, startTime, endTime ] + rawToken: + type: string + description: Full API key value (shown only once) - ScheduleItemDTO: + # -- Level -- + LevelDTO: type: object + required: [id, name, abbreviation] properties: id: - type: number + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + LevelDetailsDTO: + type: object + required: [level, groups] + properties: + level: + $ref: '#/components/schemas/LevelDTO' groups: type: array items: $ref: '#/components/schemas/GroupDTO' - teacher: - $ref: '#/components/schemas/TeacherDTO' - teachingUnit: - $ref: '#/components/schemas/TeachingUnitDTO' - room: - $ref: '#/components/schemas/RoomDTO' - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - required: [ id, groups, teacher, teachingUnit, room, startTime, endTime ] - UpdateTeachingUnitDTO: + CreateLevelDTO: type: object + required: [name, abbreviation] properties: + name: + type: string + minLength: 1 abbreviation: type: string - name: - type: - string - levelId: - type: number - required: [ abbreviation, name, levelId ] - CreateTeachingUnitDTO: + UpdateLevelDTO: type: object + required: [name, abbreviation] properties: + name: + type: string + minLength: 1 abbreviation: type: string - name: - type: - string - levelId: - type: number - required: [ abbreviation, name, levelId ] - - TeachingUnitDTO: + + # -- Group -- + GroupDTO: type: object + required: [id, name, type, classe, size, level] properties: id: - type: number - abbreviation: - type: string + type: integer + format: int64 name: - type: - string + type: string + type: + type: string + classe: + type: string + size: + type: integer level: $ref: '#/components/schemas/LevelDTO' - required: [ id, abbreviation, name, level ] - UpdateRoomDTO: + CreateGroupDTO: type: object + required: [name, type, classe, size] properties: name: type: string - abbreviation: + type: + type: string + classe: type: string size: - type: number - required: [ name, size ] + type: integer + minimum: 1 - CreateRoomDTO: + UpdateGroupDTO: type: object + required: [name, type, classe, size] properties: name: type: string - abbreviation: + type: + type: string + classe: type: string size: - type: number - required: [ name, size ] + type: integer + minimum: 1 - RoomDTO: + # -- Teacher -- + TeacherDTO: type: object + required: [id, name, abbreviation] properties: id: - type: number + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + CreateTeacherDTO: + type: object + required: [name, abbreviation] + properties: name: type: string abbreviation: type: string - size: - type: number - required: [ id, name, size ] UpdateTeacherDTO: type: object + required: [name, abbreviation] properties: name: type: string abbreviation: type: string - required: [ abbreviation, name ] - CreateTeacherDTO: + # -- Teaching Unit -- + TeachingUnitDTO: + type: object + required: [id, abbreviation, name] + properties: + id: + type: integer + format: int64 + abbreviation: + type: string + name: + type: string + level: + $ref: '#/components/schemas/LevelDTO' + description: Associated level (may be null) + + CreateTeachingUnitDTO: type: object + required: [abbreviation, name] properties: + abbreviation: + type: string name: type: string + levelId: + type: integer + format: int64 + description: Optional level association + + UpdateTeachingUnitDTO: + type: object + properties: abbreviation: type: string - required: [ abbreviation, name ] + name: + type: string + levelId: + type: integer + format: int64 + description: Optional level association - TeacherDTO: + # -- Room -- + RoomDTO: type: object + required: [id, name, abbreviation, size] properties: id: - type: number + type: integer + format: int64 name: type: string abbreviation: type: string - required: [ id, abbreviation, name ] + size: + type: integer - CreateGroupDTO: + CreateRoomDTO: type: object + required: [name, abbreviation, size] properties: name: type: string + abbreviation: + type: string size: - type: number - required: [ name, size ] + type: integer + minimum: 1 - UpdateGroupDTO: + UpdateRoomDTO: type: object + required: [name, abbreviation, size] properties: name: type: string + abbreviation: + type: string size: - type: number - required: [ name, size ] + type: integer + minimum: 1 - LevelDetailsDTO: + # -- Schedule -- + ScheduleItemDTO: type: object + required: [id, groups, teacher, teachingUnit, room, startTime, endTime] properties: - level: - $ref: "#/components/schemas/LevelDTO" + id: + type: integer + format: int64 groups: type: array items: - $ref: "#/components/schemas/GroupDTO" - required: [ level ] + $ref: '#/components/schemas/GroupDTO' + teacher: + $ref: '#/components/schemas/TeacherDTO' + teachingUnit: + $ref: '#/components/schemas/TeachingUnitDTO' + room: + $ref: '#/components/schemas/RoomDTO' + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time - GroupDTO: + CreateScheduleItemDTO: type: object + required: [groupIds, teacherId, teachingUnitId, roomId, startTime, endTime] properties: - id: - type: number - name: + groupIds: + type: array + items: + type: integer + format: int64 + teacherId: + type: integer + format: int64 + teachingUnitId: + type: integer + format: int64 + roomId: + type: integer + format: int64 + startTime: type: string - size: - type: number - level: - $ref: '#/components/schemas/LevelDTO' - required: [ id, name, size, level ] + format: date-time + endTime: + type: string + format: date-time - CreateLevelDTO: + UpdateScheduleItemDTO: type: object properties: - name: + groupIds: + type: array + items: + type: integer + format: int64 + teacherId: + type: integer + format: int64 + teachingUnitId: + type: integer + format: int64 + roomId: + type: integer + format: int64 + startTime: type: string - abbreviation: + format: date-time + endTime: type: string - required: [ name, abbreviation ] + format: date-time - UpdateLevelDTO: + BatchCreateResponseScheduleItem: type: object + required: [successItems, failedItems] properties: - name: + successItems: + type: array + items: + $ref: '#/components/schemas/ScheduleItemDTO' + failedItems: + type: array + items: + $ref: '#/components/schemas/FailedScheduleItem' + + FailedScheduleItem: + type: object + required: [item, reason] + properties: + item: + $ref: '#/components/schemas/CreateScheduleItemDTO' + reason: type: string - abbreviation: + + # -- Import / Export -- + ImportMapping: + type: object + description: | + Maps file names to their table mappings. Structure: + `{ "": { "": TableMapping } }` + additionalProperties: + type: object + additionalProperties: + $ref: '#/components/schemas/TableMapping' + + TableMapping: + type: object + required: [entityType, headersMapping] + properties: + entityType: type: string - required: [ name ] + description: Target entity type (room, teacher, teaching_unit, group, level) + headersMapping: + type: object + additionalProperties: + type: string + description: Source column name → entity property name - LevelDTO: + ImportSummary: type: object + required: [totalRows, successfulRows, failedRows, errors, skippedFiles, entitySummary] properties: - id: - type: number - name: + totalRows: + type: integer + successfulRows: + type: integer + failedRows: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/SyncError' + skippedFiles: + type: array + items: + type: string + entitySummary: + type: object + additionalProperties: + type: integer + description: Count of successfully imported rows per entity type + + SyncError: + type: object + properties: + entityType: type: string - abbreviation: + rowIndex: + type: integer + errorMessage: + type: string + invalidValue: type: string - required: [ id, name ] + # -- Error responses -- ErrorDetails: - description: Error details type: object + required: [timestamp, message, errorCode] properties: timestamp: type: string + format: date-time message: type: string details: type: string errorCode: type: string - required: [ timestamp, message, errorCode ] + description: Machine-readable error code ValidationErrorDetails: - description: Details of validation errors type: object + required: [timestamp, errors, errorCode] properties: timestamp: type: string + format: date-time errors: type: array items: - type: object - properties: - field: - type: string - error: - type: string + $ref: '#/components/schemas/ValidationError' errorCode: type: string - required: [ timestamp, errors, errorCode ] \ No newline at end of file + + ValidationError: + type: object + required: [field, error] + properties: + field: + type: string + error: + type: string From 746e1b8952ab3f96f23fb2ca536593d193b17576 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:53:49 +0000 Subject: [PATCH 3/6] Fix: rename scheduleId to scheduleItemId in integration.yaml for consistency Co-authored-by: rivon0507 <107705903+rivon0507@users.noreply.github.com> --- src/main/resources/api/integration.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/api/integration.yaml b/src/main/resources/api/integration.yaml index 536e0b0..2a18455 100644 --- a/src/main/resources/api/integration.yaml +++ b/src/main/resources/api/integration.yaml @@ -383,13 +383,13 @@ paths: schema: $ref: '#/components/schemas/ErrorDetails' - /schedule/{scheduleId}: + /schedule/{scheduleItemId}: get: operationId: integration_getScheduleItem tags: [Schedule] summary: Get a schedule item parameters: - - $ref: '#/components/parameters/scheduleId' + - $ref: '#/components/parameters/scheduleItemId' responses: '200': description: Schedule item details @@ -460,8 +460,8 @@ components: schema: type: integer format: int64 - scheduleId: - name: scheduleId + scheduleItemId: + name: scheduleItemId in: path required: true description: Schedule item identifier From 556ebf28dc4e8dd5f770a5af48ce3c5fafc7a36a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:56:11 +0000 Subject: [PATCH 4/6] Introduce shared.yaml and refactor specs to eliminate duplication - Create shared.yaml with centralized schemas, securitySchemes, parameters, responses, and pathItems for GET operations shared across both surfaces - Update rest.yaml: add global security, reference shared components, remove duplicated schemas/parameters, use $ref pathItems for GET-only paths - Update integration.yaml: all paths now $ref shared pathItems, components reference shared securitySchemes, no duplicated definitions - Fix all $ref sibling violations (use allOf wrapper for TeachingUnitDTO.level) - Public endpoints explicitly declare security: [] Co-authored-by: rivon0507 <107705903+rivon0507@users.noreply.github.com> --- src/main/resources/api/integration.yaml | 553 +--------------------- src/main/resources/api/rest.yaml | 556 +++++----------------- src/main/resources/api/shared.yaml | 582 ++++++++++++++++++++++++ 3 files changed, 716 insertions(+), 975 deletions(-) create mode 100644 src/main/resources/api/shared.yaml diff --git a/src/main/resources/api/integration.yaml b/src/main/resources/api/integration.yaml index 2a18455..503787b 100644 --- a/src/main/resources/api/integration.yaml +++ b/src/main/resources/api/integration.yaml @@ -3,8 +3,8 @@ info: title: Pointeur Backend – Integration API description: | Read-only REST API for external integrations with the Pointeur scheduling - application. This surface is served under `/integration` and requires an - API key passed via the `X-Api-Key` header. + application. This surface requires an API key passed via the `X-Api-Key` + header. **Constraints:** - Only `GET` requests are accepted; all other HTTP methods are denied. @@ -35,569 +35,56 @@ tags: description: Schedule queries # --------------------------------------------------------------------------- -# Paths — read-only subset +# Paths — read-only subset, all referenced from shared.yaml # --------------------------------------------------------------------------- paths: - # ── Levels ──────────────────────────────────────────────────────────────── /levels: - get: - operationId: integration_listLevels - tags: [Levels] - summary: List all levels with their groups - responses: - '200': - description: List of levels with nested groups - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/LevelDetailsDTO' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/pathItems/listLevels' /levels/{levelId}: - get: - operationId: integration_getLevel - tags: [Levels] - summary: Get a level with its groups - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: Level details with groups - content: - application/json: - schema: - $ref: '#/components/schemas/LevelDetailsDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getLevel' - # ── Groups ──────────────────────────────────────────────────────────────── /levels/{levelId}/groups: - get: - operationId: integration_listGroupsByLevel - tags: [Groups] - summary: List groups in a level - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: List of groups - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/listGroupsByLevel' /levels/{levelId}/groups/{groupId}: - get: - operationId: integration_getGroup - tags: [Groups] - summary: Get a group - parameters: - - $ref: '#/components/parameters/levelId' - - $ref: '#/components/parameters/groupId' - responses: - '200': - description: Group details - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Group or level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getGroup' + + /levels/{levelId}/teachingUnits: + $ref: 'shared.yaml#/components/pathItems/listTeachingUnitsByLevel' - # ── Teachers ────────────────────────────────────────────────────────────── /teachers: - get: - operationId: integration_listTeachers - tags: [Teachers] - summary: List all teachers - responses: - '200': - description: List of teachers - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeacherDTO' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/pathItems/listTeachers' /teachers/{teacherId}: - get: - operationId: integration_getTeacher - tags: [Teachers] - summary: Get a teacher - parameters: - - $ref: '#/components/parameters/teacherId' - responses: - '200': - description: Teacher details - content: - application/json: - schema: - $ref: '#/components/schemas/TeacherDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Teacher not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getTeacher' - # ── Teaching Units ──────────────────────────────────────────────────────── /teachingUnits: - get: - operationId: integration_listTeachingUnits - tags: [Teaching Units] - summary: List all teaching units - responses: - '200': - description: List of teaching units - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/pathItems/listTeachingUnits' /teachingUnits/{unitId}: - get: - operationId: integration_getTeachingUnit - tags: [Teaching Units] - summary: Get a teaching unit - parameters: - - $ref: '#/components/parameters/unitId' - responses: - '200': - description: Teaching unit details - content: - application/json: - schema: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Teaching unit not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - - /levels/{levelId}/teachingUnits: - get: - operationId: integration_listTeachingUnitsByLevel - tags: [Teaching Units] - summary: List teaching units for a level - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: Teaching units taught in the level - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getTeachingUnit' - # ── Rooms ───────────────────────────────────────────────────────────────── /rooms: - get: - operationId: integration_listRooms - tags: [Rooms] - summary: List all rooms - responses: - '200': - description: List of rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/pathItems/listRooms' /rooms/{roomId}: - get: - operationId: integration_getRoom - tags: [Rooms] - summary: Get a room - parameters: - - $ref: '#/components/parameters/roomId' - responses: - '200': - description: Room details - content: - application/json: - schema: - $ref: '#/components/schemas/RoomDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Room not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getRoom' /rooms/available: - get: - operationId: integration_listAvailableRooms - tags: [Rooms] - summary: List available rooms in a time range - description: Returns rooms that are unoccupied between the given start and end times. - parameters: - - name: startTime - in: query - required: true - description: Start date-time (ISO 8601) - schema: - type: string - format: date-time - - name: endTime - in: query - required: true - description: End date-time (ISO 8601) - schema: - type: string - format: date-time - - name: size - in: query - required: false - description: Minimum room capacity (defaults to 1) - schema: - type: integer - minimum: 1 - default: 1 - responses: - '200': - description: Available rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - '400': - description: Invalid or missing parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/pathItems/listAvailableRooms' - # ── Schedule ────────────────────────────────────────────────────────────── /schedule: - get: - operationId: integration_getSchedule - tags: [Schedule] - summary: Query the schedule - description: | - Returns schedule items in the given date range. Optionally filter by - level and/or group. - parameters: - - name: startDate - in: query - required: true - schema: - type: string - format: date - - name: endDate - in: query - required: true - schema: - type: string - format: date - - name: levelId - in: query - required: false - schema: - type: integer - format: int64 - - name: groupId - in: query - required: false - schema: - type: integer - format: int64 - responses: - '200': - description: Matching schedule items - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ScheduleItemDTO' - '400': - description: Invalid date range or parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Group not found in the specified level - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getSchedule' /schedule/{scheduleItemId}: - get: - operationId: integration_getScheduleItem - tags: [Schedule] - summary: Get a schedule item - parameters: - - $ref: '#/components/parameters/scheduleItemId' - responses: - '200': - description: Schedule item details - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduleItemDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Schedule item not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/getScheduleItem' # --------------------------------------------------------------------------- # Components # --------------------------------------------------------------------------- components: - securitySchemes: apiKeyAuth: - type: apiKey - in: header - name: X-Api-Key - description: API key issued through the frontend API's API Keys management - - # ── Reusable parameters ────────────────────────────────────────────────── - parameters: - levelId: - name: levelId - in: path - required: true - description: Level identifier - schema: - type: integer - format: int64 - groupId: - name: groupId - in: path - required: true - description: Group identifier - schema: - type: integer - format: int64 - teacherId: - name: teacherId - in: path - required: true - description: Teacher identifier - schema: - type: integer - format: int64 - unitId: - name: unitId - in: path - required: true - description: Teaching unit identifier - schema: - type: integer - format: int64 - roomId: - name: roomId - in: path - required: true - description: Room identifier - schema: - type: integer - format: int64 - scheduleItemId: - name: scheduleItemId - in: path - required: true - description: Schedule item identifier - schema: - type: integer - format: int64 - - # ── Reusable responses ─────────────────────────────────────────────────── - responses: - Unauthorized: - description: Missing or invalid API key - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - - # ── Schemas (read-only subset) ─────────────────────────────────────────── - schemas: - - LevelDTO: - type: object - required: [id, name, abbreviation] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - - LevelDetailsDTO: - type: object - required: [level, groups] - properties: - level: - $ref: '#/components/schemas/LevelDTO' - groups: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - - GroupDTO: - type: object - required: [id, name, type, classe, size, level] - properties: - id: - type: integer - format: int64 - name: - type: string - type: - type: string - classe: - type: string - size: - type: integer - level: - $ref: '#/components/schemas/LevelDTO' - - TeacherDTO: - type: object - required: [id, name, abbreviation] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - - TeachingUnitDTO: - type: object - required: [id, abbreviation, name] - properties: - id: - type: integer - format: int64 - abbreviation: - type: string - name: - type: string - level: - $ref: '#/components/schemas/LevelDTO' - description: Associated level (may be null) - - RoomDTO: - type: object - required: [id, name, abbreviation, size] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - size: - type: integer - - ScheduleItemDTO: - type: object - required: [id, groups, teacher, teachingUnit, room, startTime, endTime] - properties: - id: - type: integer - format: int64 - groups: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - teacher: - $ref: '#/components/schemas/TeacherDTO' - teachingUnit: - $ref: '#/components/schemas/TeachingUnitDTO' - room: - $ref: '#/components/schemas/RoomDTO' - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - - ErrorDetails: - type: object - required: [timestamp, message, errorCode] - properties: - timestamp: - type: string - format: date-time - message: - type: string - details: - type: string - errorCode: - type: string - description: Machine-readable error code + $ref: 'shared.yaml#/components/securitySchemes/apiKeyAuth' diff --git a/src/main/resources/api/rest.yaml b/src/main/resources/api/rest.yaml index 7dc5cfe..4bd42fe 100644 --- a/src/main/resources/api/rest.yaml +++ b/src/main/resources/api/rest.yaml @@ -1,9 +1,9 @@ openapi: 3.1.0 info: - title: Pointeur Backend – Frontend API + title: Pointeur Backend - Frontend API description: | REST API for the Pointeur scheduling application. - This specification covers the **frontend-facing** surface served under `/api/v1`. + This specification covers the **frontend-facing** surface. All endpoints require JWT Bearer authentication unless stated otherwise. version: 2.0.0 contact: @@ -13,6 +13,9 @@ servers: - url: http://localhost:8080/api/v1 description: Local development server +security: + - bearerAuth: [] + tags: - name: Authentication description: Login, logout, token refresh, and password management @@ -42,7 +45,7 @@ tags: # --------------------------------------------------------------------------- paths: - # ── Authentication ──────────────────────────────────────────────────────── + # -- Authentication ------------------------------------------------------- /auth/login: post: operationId: login @@ -75,15 +78,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /auth/me: get: operationId: getCurrentUser tags: [Authentication] summary: Get the current authenticated user - security: - - bearerAuth: [] responses: '200': description: Current user information @@ -92,7 +93,7 @@ paths: schema: $ref: '#/components/schemas/UserInfoDTO' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' /auth/refresh: post: @@ -125,7 +126,7 @@ paths: schema: $ref: '#/components/schemas/LoginResponseDTO' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' /auth/logout: post: @@ -152,8 +153,6 @@ paths: operationId: changePassword tags: [Authentication] summary: Change the current user's password - security: - - bearerAuth: [] requestBody: required: true content: @@ -172,19 +171,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' - # ── Users ───────────────────────────────────────────────────────────────── + # -- Users ---------------------------------------------------------------- /users: get: operationId: listUsers tags: [Users] summary: List all users description: Returns all users except the SuperAdmin account. Requires SuperAdmin role. - security: - - bearerAuth: [] responses: '200': description: List of users @@ -195,7 +192,7 @@ paths: items: $ref: '#/components/schemas/UserDTO' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: @@ -203,8 +200,6 @@ paths: tags: [Users] summary: Create a new admin user description: Creates a new user with a randomly generated password. Requires SuperAdmin role. - security: - - bearerAuth: [] requestBody: required: true content: @@ -225,7 +220,7 @@ paths: schema: $ref: '#/components/schemas/ValidationErrorDetails' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' @@ -234,8 +229,6 @@ paths: operationId: getUser tags: [Users] summary: Get a user by ID - security: - - bearerAuth: [] parameters: - $ref: '#/components/parameters/userId' responses: @@ -246,7 +239,7 @@ paths: schema: $ref: '#/components/schemas/UserDTO' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': @@ -254,20 +247,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteUser tags: [Users] summary: Delete a user - security: - - bearerAuth: [] parameters: - $ref: '#/components/parameters/userId' responses: '204': description: User deleted '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': @@ -275,16 +266,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' - # ── API Keys ────────────────────────────────────────────────────────────── + # -- API Keys ------------------------------------------------------------- /api-keys: get: operationId: listApiKeys tags: [API Keys] summary: List all API keys - security: - - bearerAuth: [] responses: '200': description: List of API keys (tokens are masked) @@ -295,7 +284,7 @@ paths: items: $ref: '#/components/schemas/ApiKeyResponse' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: @@ -304,9 +293,7 @@ paths: summary: Create a new API key description: | The raw token is returned **only once** in this response. Store it - securely — it cannot be retrieved again. - security: - - bearerAuth: [] + securely - it cannot be retrieved again. requestBody: required: true content: @@ -327,7 +314,7 @@ paths: schema: $ref: '#/components/schemas/ValidationErrorDetails' '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' @@ -336,26 +323,22 @@ paths: operationId: deleteApiKey tags: [API Keys] summary: Revoke an API key - security: - - bearerAuth: [] parameters: - $ref: '#/components/parameters/apiKeyId' responses: '204': description: API key revoked '401': - $ref: '#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - # ── Levels ──────────────────────────────────────────────────────────────── + # -- Levels --------------------------------------------------------------- /levels: get: operationId: listLevels tags: [Levels] summary: List all levels with their groups - security: - - bearerAuth: [] responses: '200': description: List of levels with nested groups @@ -364,13 +347,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/LevelDetailsDTO' + $ref: 'shared.yaml#/components/schemas/LevelDetailsDTO' post: operationId: createLevel tags: [Levels] summary: Create a new level - security: - - bearerAuth: [] requestBody: required: true content: @@ -383,7 +364,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/LevelDTO' + $ref: 'shared.yaml#/components/schemas/LevelDTO' '400': description: Validation error content: @@ -395,38 +376,34 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /levels/{levelId}: get: operationId: getLevel tags: [Levels] summary: Get a level with its groups - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/levelId' responses: '200': description: Level details with groups content: application/json: schema: - $ref: '#/components/schemas/LevelDetailsDTO' + $ref: 'shared.yaml#/components/schemas/LevelDetailsDTO' '404': description: Level not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateLevel tags: [Levels] summary: Update a level - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/levelId' requestBody: required: true content: @@ -439,7 +416,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/LevelDTO' + $ref: 'shared.yaml#/components/schemas/LevelDTO' '400': description: Validation error content: @@ -451,35 +428,31 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Level name already taken content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteLevel tags: [Levels] summary: Delete a level - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/levelId' responses: '204': description: Level deleted - # ── Groups (under a Level) ─────────────────────────────────────────────── + # -- Groups (under a Level) ---------------------------------------------- /levels/{levelId}/groups: get: operationId: listGroupsByLevel tags: [Groups] summary: List groups in a level - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/levelId' responses: '200': description: List of groups @@ -488,21 +461,19 @@ paths: schema: type: array items: - $ref: '#/components/schemas/GroupDTO' + $ref: 'shared.yaml#/components/schemas/GroupDTO' '404': description: Level not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' post: operationId: createGroup tags: [Groups] summary: Create a group in a level - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/levelId' requestBody: required: true content: @@ -515,7 +486,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' + $ref: 'shared.yaml#/components/schemas/GroupDTO' '400': description: Validation error content: @@ -527,40 +498,36 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /levels/{levelId}/groups/{groupId}: get: operationId: getGroup tags: [Groups] summary: Get a group - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' - - $ref: '#/components/parameters/groupId' + - $ref: 'shared.yaml#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/groupId' responses: '200': description: Group details content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' + $ref: 'shared.yaml#/components/schemas/GroupDTO' '404': description: Group or level not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateGroup tags: [Groups] summary: Update a group - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' - - $ref: '#/components/parameters/groupId' + - $ref: 'shared.yaml#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/groupId' requestBody: required: true content: @@ -573,7 +540,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GroupDTO' + $ref: 'shared.yaml#/components/schemas/GroupDTO' '400': description: Validation error content: @@ -585,28 +552,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteGroup tags: [Groups] summary: Delete a group - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/levelId' - - $ref: '#/components/parameters/groupId' + - $ref: 'shared.yaml#/components/parameters/levelId' + - $ref: 'shared.yaml#/components/parameters/groupId' responses: '204': description: Group deleted - # ── Teachers ────────────────────────────────────────────────────────────── + # -- Teachers ------------------------------------------------------------- /teachers: get: operationId: listTeachers tags: [Teachers] summary: List all teachers - security: - - bearerAuth: [] responses: '200': description: List of teachers @@ -615,13 +578,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/schemas/TeacherDTO' post: operationId: createTeacher tags: [Teachers] summary: Register a new teacher - security: - - bearerAuth: [] requestBody: required: true content: @@ -634,7 +595,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/schemas/TeacherDTO' '400': description: Validation error content: @@ -646,38 +607,34 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /teachers/{teacherId}: get: operationId: getTeacher tags: [Teachers] summary: Get a teacher - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/teacherId' + - $ref: 'shared.yaml#/components/parameters/teacherId' responses: '200': description: Teacher details content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/schemas/TeacherDTO' '404': description: Teacher not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateTeacher tags: [Teachers] summary: Update a teacher - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/teacherId' + - $ref: 'shared.yaml#/components/parameters/teacherId' requestBody: required: true content: @@ -690,7 +647,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/schemas/TeacherDTO' '400': description: Validation error content: @@ -702,33 +659,29 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Abbreviation already taken content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteTeacher tags: [Teachers] summary: Delete a teacher - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/teacherId' + - $ref: 'shared.yaml#/components/parameters/teacherId' responses: '204': description: Teacher deleted - # ── Teaching Units ──────────────────────────────────────────────────────── + # -- Teaching Units ------------------------------------------------------- /teachingUnits: get: operationId: listTeachingUnits tags: [Teaching Units] summary: List all teaching units - security: - - bearerAuth: [] responses: '200': description: List of teaching units @@ -737,13 +690,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' post: operationId: createTeachingUnit tags: [Teaching Units] summary: Create a teaching unit - security: - - bearerAuth: [] requestBody: required: true content: @@ -756,7 +707,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' '400': description: Validation error content: @@ -768,38 +719,34 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /teachingUnits/{unitId}: get: operationId: getTeachingUnit tags: [Teaching Units] summary: Get a teaching unit - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/unitId' + - $ref: 'shared.yaml#/components/parameters/unitId' responses: '200': description: Teaching unit details content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' '404': description: Teaching unit not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateTeachingUnit tags: [Teaching Units] summary: Update a teaching unit - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/unitId' + - $ref: 'shared.yaml#/components/parameters/unitId' requestBody: required: true content: @@ -812,7 +759,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' '400': description: Validation error content: @@ -824,58 +771,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Abbreviation already taken content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteTeachingUnit tags: [Teaching Units] summary: Delete a teaching unit - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/unitId' + - $ref: 'shared.yaml#/components/parameters/unitId' responses: '204': description: Teaching unit deleted /levels/{levelId}/teachingUnits: - get: - operationId: listTeachingUnitsByLevel - tags: [Teaching Units] - summary: List teaching units for a level - security: - - bearerAuth: [] - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: Teaching units taught in the level - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/listTeachingUnitsByLevel' - # ── Rooms ───────────────────────────────────────────────────────────────── + # -- Rooms ---------------------------------------------------------------- /rooms: get: operationId: listRooms tags: [Rooms] summary: List all rooms - security: - - bearerAuth: [] responses: '200': description: List of rooms @@ -884,13 +805,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/schemas/RoomDTO' post: operationId: createRoom tags: [Rooms] summary: Create a room - security: - - bearerAuth: [] requestBody: required: true content: @@ -903,7 +822,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/schemas/RoomDTO' '400': description: Validation error content: @@ -915,38 +834,34 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /rooms/{roomId}: get: operationId: getRoom tags: [Rooms] summary: Get a room - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/roomId' + - $ref: 'shared.yaml#/components/parameters/roomId' responses: '200': description: Room details content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/schemas/RoomDTO' '404': description: Room not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateRoom tags: [Rooms] summary: Update a room - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/roomId' + - $ref: 'shared.yaml#/components/parameters/roomId' requestBody: required: true content: @@ -959,7 +874,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/schemas/RoomDTO' '400': description: Validation error content: @@ -971,73 +886,27 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Room name already taken content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteRoom tags: [Rooms] summary: Delete a room - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/roomId' + - $ref: 'shared.yaml#/components/parameters/roomId' responses: '204': description: Room deleted /rooms/available: - get: - operationId: listAvailableRooms - tags: [Rooms] - summary: List available rooms in a time range - description: Returns rooms that are unoccupied between the given start and end times. - security: - - bearerAuth: [] - parameters: - - name: startTime - in: query - required: true - description: Start date-time (ISO 8601) - schema: - type: string - format: date-time - - name: endTime - in: query - required: true - description: End date-time (ISO 8601) - schema: - type: string - format: date-time - - name: size - in: query - required: false - description: Minimum room capacity (defaults to 1) - schema: - type: integer - minimum: 1 - default: 1 - responses: - '200': - description: Available rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - '400': - description: Invalid or missing parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/pathItems/listAvailableRooms' - # ── Schedule ────────────────────────────────────────────────────────────── + # -- Schedule ------------------------------------------------------------- /schedule: get: operationId: getSchedule @@ -1047,8 +916,6 @@ paths: Returns schedule items in the given date range. Optionally filter by level and/or group. If `groupId` is supplied without `levelId`, only that group's timetable is returned. - security: - - bearerAuth: [] parameters: - name: startDate in: query @@ -1082,25 +949,23 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ScheduleItemDTO' + $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' '400': description: Invalid date range or parameters content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '404': description: Group not found in the specified level content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' post: operationId: createScheduleItem tags: [Schedule] summary: Create a schedule item - security: - - bearerAuth: [] requestBody: required: true content: @@ -1113,7 +978,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScheduleItemDTO' + $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' '400': description: Validation error content: @@ -1125,13 +990,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Schedule conflict (room, teacher, or group overlap) content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' /schedule/batch: post: @@ -1141,8 +1006,6 @@ paths: description: | Processes each item independently. Successfully created items and failed items (with reasons) are returned together. - security: - - bearerAuth: [] requestBody: required: true content: @@ -1164,31 +1027,27 @@ paths: operationId: getScheduleItem tags: [Schedule] summary: Get a schedule item - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/scheduleItemId' + - $ref: 'shared.yaml#/components/parameters/scheduleItemId' responses: '200': description: Schedule item details content: application/json: schema: - $ref: '#/components/schemas/ScheduleItemDTO' + $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' '404': description: Schedule item not found content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' put: operationId: updateScheduleItem tags: [Schedule] summary: Update a schedule item - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/scheduleItemId' + - $ref: 'shared.yaml#/components/parameters/scheduleItemId' requestBody: required: true content: @@ -1201,7 +1060,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ScheduleItemDTO' + $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' '400': description: Validation error content: @@ -1213,26 +1072,24 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' '409': description: Schedule conflict content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' delete: operationId: deleteScheduleItem tags: [Schedule] summary: Delete a schedule item - security: - - bearerAuth: [] parameters: - - $ref: '#/components/parameters/scheduleItemId' + - $ref: 'shared.yaml#/components/parameters/scheduleItemId' responses: '204': description: Schedule item deleted - # ── Export ──────────────────────────────────────────────────────────────── + # -- Export --------------------------------------------------------------- /export: get: operationId: exportData @@ -1241,8 +1098,6 @@ paths: description: | Exports the requested entities in the specified format. The response is a downloadable file (Excel, ZIP of CSVs, or JSON). - security: - - bearerAuth: [] parameters: - name: entitiesList in: query @@ -1286,9 +1141,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' - # ── Import ──────────────────────────────────────────────────────────────── + # -- Import --------------------------------------------------------------- /import/upload: post: operationId: importData @@ -1297,8 +1152,6 @@ paths: description: | Accepts one or more files together with a metadata mapping that describes how columns map to entity fields. - security: - - bearerAuth: [] parameters: - name: ignoreConflicts in: query @@ -1334,70 +1187,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' # --------------------------------------------------------------------------- -# Components +# Components (frontend-specific definitions) # --------------------------------------------------------------------------- components: securitySchemes: bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - description: JWT access token obtained via `/auth/login` + $ref: 'shared.yaml#/components/securitySchemes/bearerAuth' - # ── Reusable parameters ────────────────────────────────────────────────── parameters: - levelId: - name: levelId - in: path - required: true - description: Level identifier - schema: - type: integer - format: int64 - groupId: - name: groupId - in: path - required: true - description: Group identifier - schema: - type: integer - format: int64 - teacherId: - name: teacherId - in: path - required: true - description: Teacher identifier - schema: - type: integer - format: int64 - unitId: - name: unitId - in: path - required: true - description: Teaching unit identifier - schema: - type: integer - format: int64 - roomId: - name: roomId - in: path - required: true - description: Room identifier - schema: - type: integer - format: int64 - scheduleItemId: - name: scheduleItemId - in: path - required: true - description: Schedule item identifier - schema: - type: integer - format: int64 userId: name: userId in: path @@ -1415,22 +1216,14 @@ components: type: integer format: int64 - # ── Reusable responses ─────────────────────────────────────────────────── responses: - Unauthorized: - description: Authentication required or token invalid - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' Forbidden: description: Insufficient permissions content: application/json: schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/schemas/ErrorDetails' - # ── Schemas ────────────────────────────────────────────────────────────── schemas: # -- Auth -- @@ -1556,29 +1349,6 @@ components: description: Full API key value (shown only once) # -- Level -- - LevelDTO: - type: object - required: [id, name, abbreviation] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - - LevelDetailsDTO: - type: object - required: [level, groups] - properties: - level: - $ref: '#/components/schemas/LevelDTO' - groups: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - CreateLevelDTO: type: object required: [name, abbreviation] @@ -1600,24 +1370,6 @@ components: type: string # -- Group -- - GroupDTO: - type: object - required: [id, name, type, classe, size, level] - properties: - id: - type: integer - format: int64 - name: - type: string - type: - type: string - classe: - type: string - size: - type: integer - level: - $ref: '#/components/schemas/LevelDTO' - CreateGroupDTO: type: object required: [name, type, classe, size] @@ -1647,18 +1399,6 @@ components: minimum: 1 # -- Teacher -- - TeacherDTO: - type: object - required: [id, name, abbreviation] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - CreateTeacherDTO: type: object required: [name, abbreviation] @@ -1678,21 +1418,6 @@ components: type: string # -- Teaching Unit -- - TeachingUnitDTO: - type: object - required: [id, abbreviation, name] - properties: - id: - type: integer - format: int64 - abbreviation: - type: string - name: - type: string - level: - $ref: '#/components/schemas/LevelDTO' - description: Associated level (may be null) - CreateTeachingUnitDTO: type: object required: [abbreviation, name] @@ -1719,20 +1444,6 @@ components: description: Optional level association # -- Room -- - RoomDTO: - type: object - required: [id, name, abbreviation, size] - properties: - id: - type: integer - format: int64 - name: - type: string - abbreviation: - type: string - size: - type: integer - CreateRoomDTO: type: object required: [name, abbreviation, size] @@ -1758,30 +1469,6 @@ components: minimum: 1 # -- Schedule -- - ScheduleItemDTO: - type: object - required: [id, groups, teacher, teachingUnit, room, startTime, endTime] - properties: - id: - type: integer - format: int64 - groups: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - teacher: - $ref: '#/components/schemas/TeacherDTO' - teachingUnit: - $ref: '#/components/schemas/TeachingUnitDTO' - room: - $ref: '#/components/schemas/RoomDTO' - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - CreateScheduleItemDTO: type: object required: [groupIds, teacherId, teachingUnitId, roomId, startTime, endTime] @@ -1838,7 +1525,7 @@ components: successItems: type: array items: - $ref: '#/components/schemas/ScheduleItemDTO' + $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' failedItems: type: array items: @@ -1875,7 +1562,7 @@ components: type: object additionalProperties: type: string - description: Source column name → entity property name + description: Source column name to entity property name ImportSummary: type: object @@ -1914,21 +1601,6 @@ components: type: string # -- Error responses -- - ErrorDetails: - type: object - required: [timestamp, message, errorCode] - properties: - timestamp: - type: string - format: date-time - message: - type: string - details: - type: string - errorCode: - type: string - description: Machine-readable error code - ValidationErrorDetails: type: object required: [timestamp, errors, errorCode] diff --git a/src/main/resources/api/shared.yaml b/src/main/resources/api/shared.yaml new file mode 100644 index 0000000..31bc3ed --- /dev/null +++ b/src/main/resources/api/shared.yaml @@ -0,0 +1,582 @@ +openapi: 3.1.0 +info: + title: Pointeur Backend – Shared Components + description: | + Reusable components referenced by the frontend (`rest.yaml`) and + integration (`integration.yaml`) specifications. + This file is not intended to be used as a standalone API document. + version: 2.0.0 + +# --------------------------------------------------------------------------- +# Components +# --------------------------------------------------------------------------- +components: + + # ── Security schemes ───────────────────────────────────────────────────── + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: JWT access token obtained via /auth/login + + apiKeyAuth: + type: apiKey + in: header + name: X-Api-Key + description: API key issued through the frontend API's API Keys management + + # ── Reusable parameters ────────────────────────────────────────────────── + parameters: + levelId: + name: levelId + in: path + required: true + description: Level identifier + schema: + type: integer + format: int64 + + groupId: + name: groupId + in: path + required: true + description: Group identifier + schema: + type: integer + format: int64 + + teacherId: + name: teacherId + in: path + required: true + description: Teacher identifier + schema: + type: integer + format: int64 + + unitId: + name: unitId + in: path + required: true + description: Teaching unit identifier + schema: + type: integer + format: int64 + + roomId: + name: roomId + in: path + required: true + description: Room identifier + schema: + type: integer + format: int64 + + scheduleItemId: + name: scheduleItemId + in: path + required: true + description: Schedule item identifier + schema: + type: integer + format: int64 + + # ── Reusable responses ─────────────────────────────────────────────────── + responses: + Unauthorized: + description: Authentication required + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Schemas ────────────────────────────────────────────────────────────── + schemas: + + LevelDTO: + type: object + required: [id, name, abbreviation] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + LevelDetailsDTO: + type: object + required: [level, groups] + properties: + level: + $ref: '#/components/schemas/LevelDTO' + groups: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + + GroupDTO: + type: object + required: [id, name, type, classe, size, level] + properties: + id: + type: integer + format: int64 + name: + type: string + type: + type: string + classe: + type: string + size: + type: integer + level: + $ref: '#/components/schemas/LevelDTO' + + TeacherDTO: + type: object + required: [id, name, abbreviation] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + + TeachingUnitDTO: + type: object + required: [id, abbreviation, name] + properties: + id: + type: integer + format: int64 + abbreviation: + type: string + name: + type: string + level: + description: Associated level (may be null) + allOf: + - $ref: '#/components/schemas/LevelDTO' + + RoomDTO: + type: object + required: [id, name, abbreviation, size] + properties: + id: + type: integer + format: int64 + name: + type: string + abbreviation: + type: string + size: + type: integer + + ScheduleItemDTO: + type: object + required: [id, groups, teacher, teachingUnit, room, startTime, endTime] + properties: + id: + type: integer + format: int64 + groups: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + teacher: + $ref: '#/components/schemas/TeacherDTO' + teachingUnit: + $ref: '#/components/schemas/TeachingUnitDTO' + room: + $ref: '#/components/schemas/RoomDTO' + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time + + ErrorDetails: + type: object + required: [timestamp, message, errorCode] + properties: + timestamp: + type: string + format: date-time + message: + type: string + details: + type: string + errorCode: + type: string + description: Machine-readable error code + + # ── Shared path items (GET operations available on both surfaces) ──────── + pathItems: + + listLevels: + get: + operationId: listLevels + tags: [Levels] + summary: List all levels with their groups + responses: + '200': + description: List of levels with nested groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + getLevel: + get: + operationId: getLevel + tags: [Levels] + summary: Get a level with its groups + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Level details with groups + content: + application/json: + schema: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listGroupsByLevel: + get: + operationId: listGroupsByLevel + tags: [Groups] + summary: List groups in a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: List of groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + getGroup: + get: + operationId: getGroup + tags: [Groups] + summary: Get a group + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' + responses: + '200': + description: Group details + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group or level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listTeachers: + get: + operationId: listTeachers + tags: [Teachers] + summary: List all teachers + responses: + '200': + description: List of teachers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + getTeacher: + get: + operationId: getTeacher + tags: [Teachers] + summary: Get a teacher + parameters: + - $ref: '#/components/parameters/teacherId' + responses: + '200': + description: Teacher details + content: + application/json: + schema: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teacher not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listTeachingUnits: + get: + operationId: listTeachingUnits + tags: [Teaching Units] + summary: List all teaching units + responses: + '200': + description: List of teaching units + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + getTeachingUnit: + get: + operationId: getTeachingUnit + tags: [Teaching Units] + summary: Get a teaching unit + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '200': + description: Teaching unit details + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listTeachingUnitsByLevel: + get: + operationId: listTeachingUnitsByLevel + tags: [Teaching Units] + summary: List teaching units for a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Teaching units taught in the level + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listRooms: + get: + operationId: listRooms + tags: [Rooms] + summary: List all rooms + responses: + '200': + description: List of rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + getRoom: + get: + operationId: getRoom + tags: [Rooms] + summary: Get a room + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '200': + description: Room details + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + listAvailableRooms: + get: + operationId: listAvailableRooms + tags: [Rooms] + summary: List available rooms in a time range + description: Returns rooms that are unoccupied between the given start and end times. + parameters: + - name: startTime + in: query + required: true + description: Start date-time (ISO 8601) + schema: + type: string + format: date-time + - name: endTime + in: query + required: true + description: End date-time (ISO 8601) + schema: + type: string + format: date-time + - name: size + in: query + required: false + description: Minimum room capacity (defaults to 1) + schema: + type: integer + minimum: 1 + default: 1 + responses: + '200': + description: Available rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Invalid or missing parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + + getSchedule: + get: + operationId: getSchedule + tags: [Schedule] + summary: Query the schedule + description: | + Returns schedule items in the given date range. Optionally filter by + level and/or group. If `groupId` is supplied without `levelId`, only + that group's timetable is returned. + parameters: + - name: startDate + in: query + required: true + schema: + type: string + format: date + - name: endDate + in: query + required: true + schema: + type: string + format: date + - name: levelId + in: query + required: false + schema: + type: integer + format: int64 + - name: groupId + in: query + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: Matching schedule items + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Invalid date range or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group not found in the specified level + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + getScheduleItem: + get: + operationId: getScheduleItem + tags: [Schedule] + summary: Get a schedule item + parameters: + - $ref: '#/components/parameters/scheduleItemId' + responses: + '200': + description: Schedule item details + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Schedule item not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' From da2106371e2b069a789ad93fc67b26c9d5ff8e58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:57:23 +0000 Subject: [PATCH 5/6] Fix typography consistency: use em dashes and arrow notation Co-authored-by: rivon0507 <107705903+rivon0507@users.noreply.github.com> --- src/main/resources/api/rest.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/api/rest.yaml b/src/main/resources/api/rest.yaml index 4bd42fe..5bbfab3 100644 --- a/src/main/resources/api/rest.yaml +++ b/src/main/resources/api/rest.yaml @@ -1,6 +1,6 @@ openapi: 3.1.0 info: - title: Pointeur Backend - Frontend API + title: Pointeur Backend – Frontend API description: | REST API for the Pointeur scheduling application. This specification covers the **frontend-facing** surface. @@ -293,7 +293,7 @@ paths: summary: Create a new API key description: | The raw token is returned **only once** in this response. Store it - securely - it cannot be retrieved again. + securely — it cannot be retrieved again. requestBody: required: true content: @@ -1562,7 +1562,7 @@ components: type: object additionalProperties: type: string - description: Source column name to entity property name + description: Source column name → entity property name ImportSummary: type: object From d799ef0a324e7b24ad800f490b63cdcc8d1fb9b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:24:58 +0000 Subject: [PATCH 6/6] Refactor to operation-level $ref architecture - shared.yaml: move ALL schemas and operations into x-operations container (47 operations, 37 schemas, all security schemes and parameters) - rest.yaml: paths compose methods via $ref to shared x-operations, no local schemas or inline operations - integration.yaml: GET-only subset, same $ref pattern, no local definitions - Remove pathItems in favor of x-operations - Public endpoints explicitly declare security: [] - All $ref resolve, no sibling violations, YAML valid Co-authored-by: rivon0507 <107705903+rivon0507@users.noreply.github.com> --- src/main/resources/api/integration.yaml | 56 +- src/main/resources/api/rest.yaml | 1547 +---------------- src/main/resources/api/shared.yaml | 2005 +++++++++++++++++++---- 3 files changed, 1773 insertions(+), 1835 deletions(-) diff --git a/src/main/resources/api/integration.yaml b/src/main/resources/api/integration.yaml index 503787b..564b69c 100644 --- a/src/main/resources/api/integration.yaml +++ b/src/main/resources/api/integration.yaml @@ -1,3 +1,8 @@ +# integration.yaml — Integration API specification. +# Read-only (GET only) subset of the frontend API. +# Each GET method $refs the corresponding operation from shared.yaml. +# No schemas or operations are defined locally. + openapi: 3.1.0 info: title: Pointeur Backend – Integration API @@ -14,9 +19,10 @@ info: name: 2RK-dev servers: - - url: http://localhost:8080/integration - description: Local development server + - url: /integration + description: Integration API base path +# Global security — X-Api-Key header for all endpoints. security: - apiKeyAuth: [] @@ -35,54 +41,68 @@ tags: description: Schedule queries # --------------------------------------------------------------------------- -# Paths — read-only subset, all referenced from shared.yaml +# Paths — GET-only subset, each method $refs the operation from shared.yaml # --------------------------------------------------------------------------- paths: /levels: - $ref: 'shared.yaml#/components/pathItems/listLevels' + get: + $ref: 'shared.yaml#/components/x-operations/listLevels' /levels/{levelId}: - $ref: 'shared.yaml#/components/pathItems/getLevel' + get: + $ref: 'shared.yaml#/components/x-operations/getLevel' /levels/{levelId}/groups: - $ref: 'shared.yaml#/components/pathItems/listGroupsByLevel' + get: + $ref: 'shared.yaml#/components/x-operations/listGroupsByLevel' /levels/{levelId}/groups/{groupId}: - $ref: 'shared.yaml#/components/pathItems/getGroup' + get: + $ref: 'shared.yaml#/components/x-operations/getGroup' /levels/{levelId}/teachingUnits: - $ref: 'shared.yaml#/components/pathItems/listTeachingUnitsByLevel' + get: + $ref: 'shared.yaml#/components/x-operations/listTeachingUnitsByLevel' /teachers: - $ref: 'shared.yaml#/components/pathItems/listTeachers' + get: + $ref: 'shared.yaml#/components/x-operations/listTeachers' /teachers/{teacherId}: - $ref: 'shared.yaml#/components/pathItems/getTeacher' + get: + $ref: 'shared.yaml#/components/x-operations/getTeacher' /teachingUnits: - $ref: 'shared.yaml#/components/pathItems/listTeachingUnits' + get: + $ref: 'shared.yaml#/components/x-operations/listTeachingUnits' /teachingUnits/{unitId}: - $ref: 'shared.yaml#/components/pathItems/getTeachingUnit' + get: + $ref: 'shared.yaml#/components/x-operations/getTeachingUnit' /rooms: - $ref: 'shared.yaml#/components/pathItems/listRooms' + get: + $ref: 'shared.yaml#/components/x-operations/listRooms' /rooms/{roomId}: - $ref: 'shared.yaml#/components/pathItems/getRoom' + get: + $ref: 'shared.yaml#/components/x-operations/getRoom' /rooms/available: - $ref: 'shared.yaml#/components/pathItems/listAvailableRooms' + get: + $ref: 'shared.yaml#/components/x-operations/listAvailableRooms' /schedule: - $ref: 'shared.yaml#/components/pathItems/getSchedule' + get: + $ref: 'shared.yaml#/components/x-operations/getSchedule' /schedule/{scheduleItemId}: - $ref: 'shared.yaml#/components/pathItems/getScheduleItem' + get: + $ref: 'shared.yaml#/components/x-operations/getScheduleItem' # --------------------------------------------------------------------------- -# Components +# Components — security scheme referenced from shared.yaml # --------------------------------------------------------------------------- components: securitySchemes: diff --git a/src/main/resources/api/rest.yaml b/src/main/resources/api/rest.yaml index 5bbfab3..f83ef96 100644 --- a/src/main/resources/api/rest.yaml +++ b/src/main/resources/api/rest.yaml @@ -1,3 +1,7 @@ +# rest.yaml — Frontend API specification. +# Paths compose HTTP methods by referencing operations from shared.yaml. +# No schemas or operations are defined locally. + openapi: 3.1.0 info: title: Pointeur Backend – Frontend API @@ -10,9 +14,10 @@ info: name: 2RK-dev servers: - - url: http://localhost:8080/api/v1 - description: Local development server + - url: /api/v1 + description: Frontend API base path +# Global security — inherited by all operations that do not override it. security: - bearerAuth: [] @@ -41,1585 +46,171 @@ tags: description: Data export # --------------------------------------------------------------------------- -# Paths +# Paths — each HTTP method $refs the corresponding operation from shared.yaml # --------------------------------------------------------------------------- paths: - # -- Authentication ------------------------------------------------------- + # ── Authentication ─────────────────────────────────────────────────────── /auth/login: post: - operationId: login - tags: [Authentication] - summary: Authenticate a user - description: | - Validates credentials and returns a JWT access token together with user - information. Refresh-token and device-id cookies are set automatically. - security: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/LoginRequestDTO' - responses: - '200': - description: Authentication successful - headers: - Set-Cookie: - description: '`refresh_token` and `device_id` HttpOnly cookies' - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/LoginResponseDTO' - '401': - description: Invalid credentials - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/login' /auth/me: get: - operationId: getCurrentUser - tags: [Authentication] - summary: Get the current authenticated user - responses: - '200': - description: Current user information - content: - application/json: - schema: - $ref: '#/components/schemas/UserInfoDTO' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/x-operations/getCurrentUser' /auth/refresh: post: - operationId: refreshToken - tags: [Authentication] - summary: Refresh the access token - description: | - Uses the `refresh_token` and `device_id` cookies to issue a new access - token. The refresh-token cookie is rotated. - security: [] - parameters: - - name: device_id - in: cookie - schema: - type: string - - name: refresh_token - in: cookie - schema: - type: string - responses: - '200': - description: Token refreshed - headers: - Set-Cookie: - description: Updated `refresh_token` HttpOnly cookie - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/LoginResponseDTO' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/x-operations/refreshToken' /auth/logout: post: - operationId: logout - tags: [Authentication] - summary: Log out the current session - description: Clears the refresh-token cookie. - security: [] - parameters: - - name: device_id - in: cookie - schema: - type: string - - name: refresh_token - in: cookie - schema: - type: string - responses: - '204': - description: Logged out successfully + $ref: 'shared.yaml#/components/x-operations/logout' /auth/password: put: - operationId: changePassword - tags: [Authentication] - summary: Change the current user's password - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ChangePasswordDTO' - responses: - '200': - description: Password changed successfully - content: - application/json: - schema: - type: string - '400': - description: Validation error or wrong old password - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' + $ref: 'shared.yaml#/components/x-operations/changePassword' - # -- Users ---------------------------------------------------------------- + # ── Users ──────────────────────────────────────────────────────────────── /users: get: - operationId: listUsers - tags: [Users] - summary: List all users - description: Returns all users except the SuperAdmin account. Requires SuperAdmin role. - responses: - '200': - description: List of users - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/UserDTO' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' + $ref: 'shared.yaml#/components/x-operations/listUsers' post: - operationId: createUser - tags: [Users] - summary: Create a new admin user - description: Creates a new user with a randomly generated password. Requires SuperAdmin role. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserDTO' - responses: - '201': - description: User created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/UserCreatedDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' + $ref: 'shared.yaml#/components/x-operations/createUser' /users/{userId}: get: - operationId: getUser - tags: [Users] - summary: Get a user by ID - parameters: - - $ref: '#/components/parameters/userId' - responses: - '200': - description: User details - content: - application/json: - schema: - $ref: '#/components/schemas/UserDTO' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - '404': - description: User not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getUser' delete: - operationId: deleteUser - tags: [Users] - summary: Delete a user - parameters: - - $ref: '#/components/parameters/userId' - responses: - '204': - description: User deleted - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - '404': - description: User not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/deleteUser' - # -- API Keys ------------------------------------------------------------- + # ── API Keys ───────────────────────────────────────────────────────────── /api-keys: get: - operationId: listApiKeys - tags: [API Keys] - summary: List all API keys - responses: - '200': - description: List of API keys (tokens are masked) - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/ApiKeyResponse' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' + $ref: 'shared.yaml#/components/x-operations/listApiKeys' post: - operationId: createApiKey - tags: [API Keys] - summary: Create a new API key - description: | - The raw token is returned **only once** in this response. Store it - securely — it cannot be retrieved again. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ApiKeyCreateRequest' - responses: - '200': - description: API key created - content: - application/json: - schema: - $ref: '#/components/schemas/ApiKeyWithRawToken' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' + $ref: 'shared.yaml#/components/x-operations/createApiKey' /api-keys/{apiKeyId}: delete: - operationId: deleteApiKey - tags: [API Keys] - summary: Revoke an API key - parameters: - - $ref: '#/components/parameters/apiKeyId' - responses: - '204': - description: API key revoked - '401': - $ref: 'shared.yaml#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' + $ref: 'shared.yaml#/components/x-operations/deleteApiKey' - # -- Levels --------------------------------------------------------------- + # ── Levels ─────────────────────────────────────────────────────────────── /levels: get: - operationId: listLevels - tags: [Levels] - summary: List all levels with their groups - responses: - '200': - description: List of levels with nested groups - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/LevelDetailsDTO' + $ref: 'shared.yaml#/components/x-operations/listLevels' post: - operationId: createLevel - tags: [Levels] - summary: Create a new level - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateLevelDTO' - responses: - '201': - description: Level created - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/LevelDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '409': - description: Level name already exists - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createLevel' /levels/{levelId}: get: - operationId: getLevel - tags: [Levels] - summary: Get a level with its groups - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - responses: - '200': - description: Level details with groups - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/LevelDetailsDTO' - '404': - description: Level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getLevel' put: - operationId: updateLevel - tags: [Levels] - summary: Update a level - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateLevelDTO' - responses: - '200': - description: Level updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/LevelDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Level name already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateLevel' delete: - operationId: deleteLevel - tags: [Levels] - summary: Delete a level - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - responses: - '204': - description: Level deleted + $ref: 'shared.yaml#/components/x-operations/deleteLevel' - # -- Groups (under a Level) ---------------------------------------------- + # ── Groups ─────────────────────────────────────────────────────────────── /levels/{levelId}/groups: get: - operationId: listGroupsByLevel - tags: [Groups] - summary: List groups in a level - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - responses: - '200': - description: List of groups - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/GroupDTO' - '404': - description: Level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/listGroupsByLevel' post: - operationId: createGroup - tags: [Groups] - summary: Create a group in a level - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateGroupDTO' - responses: - '201': - description: Group created - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/GroupDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createGroup' /levels/{levelId}/groups/{groupId}: get: - operationId: getGroup - tags: [Groups] - summary: Get a group - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - - $ref: 'shared.yaml#/components/parameters/groupId' - responses: - '200': - description: Group details - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/GroupDTO' - '404': - description: Group or level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getGroup' put: - operationId: updateGroup - tags: [Groups] - summary: Update a group - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - - $ref: 'shared.yaml#/components/parameters/groupId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateGroupDTO' - responses: - '200': - description: Group updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/GroupDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Group or level not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateGroup' delete: - operationId: deleteGroup - tags: [Groups] - summary: Delete a group - parameters: - - $ref: 'shared.yaml#/components/parameters/levelId' - - $ref: 'shared.yaml#/components/parameters/groupId' - responses: - '204': - description: Group deleted + $ref: 'shared.yaml#/components/x-operations/deleteGroup' - # -- Teachers ------------------------------------------------------------- + # ── Teachers ───────────────────────────────────────────────────────────── /teachers: get: - operationId: listTeachers - tags: [Teachers] - summary: List all teachers - responses: - '200': - description: List of teachers - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/x-operations/listTeachers' post: - operationId: createTeacher - tags: [Teachers] - summary: Register a new teacher - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTeacherDTO' - responses: - '201': - description: Teacher registered - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeacherDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '409': - description: Abbreviation already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createTeacher' /teachers/{teacherId}: get: - operationId: getTeacher - tags: [Teachers] - summary: Get a teacher - parameters: - - $ref: 'shared.yaml#/components/parameters/teacherId' - responses: - '200': - description: Teacher details - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeacherDTO' - '404': - description: Teacher not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getTeacher' put: - operationId: updateTeacher - tags: [Teachers] - summary: Update a teacher - parameters: - - $ref: 'shared.yaml#/components/parameters/teacherId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateTeacherDTO' - responses: - '200': - description: Teacher updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeacherDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Teacher not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Abbreviation already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateTeacher' delete: - operationId: deleteTeacher - tags: [Teachers] - summary: Delete a teacher - parameters: - - $ref: 'shared.yaml#/components/parameters/teacherId' - responses: - '204': - description: Teacher deleted + $ref: 'shared.yaml#/components/x-operations/deleteTeacher' - # -- Teaching Units ------------------------------------------------------- + # ── Teaching Units ─────────────────────────────────────────────────────── /teachingUnits: get: - operationId: listTeachingUnits - tags: [Teaching Units] - summary: List all teaching units - responses: - '200': - description: List of teaching units - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/x-operations/listTeachingUnits' post: - operationId: createTeachingUnit - tags: [Teaching Units] - summary: Create a teaching unit - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTeachingUnitDTO' - responses: - '201': - description: Teaching unit created - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '409': - description: Abbreviation already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createTeachingUnit' /teachingUnits/{unitId}: get: - operationId: getTeachingUnit - tags: [Teaching Units] - summary: Get a teaching unit - parameters: - - $ref: 'shared.yaml#/components/parameters/unitId' - responses: - '200': - description: Teaching unit details - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' - '404': - description: Teaching unit not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getTeachingUnit' put: - operationId: updateTeachingUnit - tags: [Teaching Units] - summary: Update a teaching unit - parameters: - - $ref: 'shared.yaml#/components/parameters/unitId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateTeachingUnitDTO' - responses: - '200': - description: Teaching unit updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/TeachingUnitDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Teaching unit not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Abbreviation already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateTeachingUnit' delete: - operationId: deleteTeachingUnit - tags: [Teaching Units] - summary: Delete a teaching unit - parameters: - - $ref: 'shared.yaml#/components/parameters/unitId' - responses: - '204': - description: Teaching unit deleted + $ref: 'shared.yaml#/components/x-operations/deleteTeachingUnit' /levels/{levelId}/teachingUnits: - $ref: 'shared.yaml#/components/pathItems/listTeachingUnitsByLevel' + get: + $ref: 'shared.yaml#/components/x-operations/listTeachingUnitsByLevel' - # -- Rooms ---------------------------------------------------------------- + # ── Rooms ──────────────────────────────────────────────────────────────── /rooms: get: - operationId: listRooms - tags: [Rooms] - summary: List all rooms - responses: - '200': - description: List of rooms - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/x-operations/listRooms' post: - operationId: createRoom - tags: [Rooms] - summary: Create a room - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateRoomDTO' - responses: - '201': - description: Room created - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/RoomDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '409': - description: Room name already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createRoom' /rooms/{roomId}: get: - operationId: getRoom - tags: [Rooms] - summary: Get a room - parameters: - - $ref: 'shared.yaml#/components/parameters/roomId' - responses: - '200': - description: Room details - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/RoomDTO' - '404': - description: Room not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getRoom' put: - operationId: updateRoom - tags: [Rooms] - summary: Update a room - parameters: - - $ref: 'shared.yaml#/components/parameters/roomId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateRoomDTO' - responses: - '200': - description: Room updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/RoomDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Room not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Room name already taken - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateRoom' delete: - operationId: deleteRoom - tags: [Rooms] - summary: Delete a room - parameters: - - $ref: 'shared.yaml#/components/parameters/roomId' - responses: - '204': - description: Room deleted + $ref: 'shared.yaml#/components/x-operations/deleteRoom' /rooms/available: - $ref: 'shared.yaml#/components/pathItems/listAvailableRooms' + get: + $ref: 'shared.yaml#/components/x-operations/listAvailableRooms' - # -- Schedule ------------------------------------------------------------- + # ── Schedule ───────────────────────────────────────────────────────────── /schedule: get: - operationId: getSchedule - tags: [Schedule] - summary: Query the schedule - description: | - Returns schedule items in the given date range. Optionally filter by - level and/or group. If `groupId` is supplied without `levelId`, only - that group's timetable is returned. - parameters: - - name: startDate - in: query - required: true - schema: - type: string - format: date - - name: endDate - in: query - required: true - schema: - type: string - format: date - - name: levelId - in: query - required: false - schema: - type: integer - format: int64 - - name: groupId - in: query - required: false - schema: - type: integer - format: int64 - responses: - '200': - description: Matching schedule items - content: - application/json: - schema: - type: array - items: - $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' - '400': - description: Invalid date range or parameters - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '404': - description: Group not found in the specified level - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getSchedule' post: - operationId: createScheduleItem - tags: [Schedule] - summary: Create a schedule item - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateScheduleItemDTO' - responses: - '201': - description: Schedule item created - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Referenced resource not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Schedule conflict (room, teacher, or group overlap) - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createScheduleItem' /schedule/batch: post: - operationId: createScheduleItemsBatch - tags: [Schedule] - summary: Create multiple schedule items - description: | - Processes each item independently. Successfully created items and - failed items (with reasons) are returned together. - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/CreateScheduleItemDTO' - responses: - '200': - description: Batch result - content: - application/json: - schema: - $ref: '#/components/schemas/BatchCreateResponseScheduleItem' + $ref: 'shared.yaml#/components/x-operations/createScheduleItemsBatch' /schedule/{scheduleItemId}: get: - operationId: getScheduleItem - tags: [Schedule] - summary: Get a schedule item - parameters: - - $ref: 'shared.yaml#/components/parameters/scheduleItemId' - responses: - '200': - description: Schedule item details - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' - '404': - description: Schedule item not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getScheduleItem' put: - operationId: updateScheduleItem - tags: [Schedule] - summary: Update a schedule item - parameters: - - $ref: 'shared.yaml#/components/parameters/scheduleItemId' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateScheduleItemDTO' - responses: - '200': - description: Schedule item updated - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' - '400': - description: Validation error - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - '404': - description: Schedule item or referenced resource not found - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - '409': - description: Schedule conflict - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateScheduleItem' delete: - operationId: deleteScheduleItem - tags: [Schedule] - summary: Delete a schedule item - parameters: - - $ref: 'shared.yaml#/components/parameters/scheduleItemId' - responses: - '204': - description: Schedule item deleted + $ref: 'shared.yaml#/components/x-operations/deleteScheduleItem' - # -- Export --------------------------------------------------------------- + # ── Export ─────────────────────────────────────────────────────────────── /export: get: - operationId: exportData - tags: [Export] - summary: Export entities - description: | - Exports the requested entities in the specified format. The response - is a downloadable file (Excel, ZIP of CSVs, or JSON). - parameters: - - name: entitiesList - in: query - required: true - description: Entity types to export (e.g. room, teacher, level, group, teaching_unit) - schema: - type: array - items: - type: string - enum: [room, teacher, teaching_unit, group, level] - - name: format - in: query - required: true - description: Output format - schema: - type: string - enum: [excel, zip_csv, json] - responses: - '200': - description: Exported file - headers: - Content-Disposition: - description: 'attachment; filename=export__.' - schema: - type: string - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: - schema: - type: string - format: binary - application/zip: - schema: - type: string - format: binary - application/json: - schema: - type: string - format: binary - '400': - description: Unknown entity or unsupported format - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/exportData' - # -- Import --------------------------------------------------------------- + # ── Import ─────────────────────────────────────────────────────────────── /import/upload: post: - operationId: importData - tags: [Import] - summary: Import data from files - description: | - Accepts one or more files together with a metadata mapping that - describes how columns map to entity fields. - parameters: - - name: ignoreConflicts - in: query - required: false - description: Whether to skip rows that conflict with existing data - schema: - type: boolean - default: true - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, files] - properties: - metadata: - $ref: '#/components/schemas/ImportMapping' - files: - type: array - items: - type: string - format: binary - responses: - '200': - description: Import summary - content: - application/json: - schema: - $ref: '#/components/schemas/ImportSummary' - '400': - description: Invalid file format or unsupported codec - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/importData' # --------------------------------------------------------------------------- -# Components (frontend-specific definitions) +# Components — security scheme referenced from shared.yaml # --------------------------------------------------------------------------- components: - securitySchemes: bearerAuth: $ref: 'shared.yaml#/components/securitySchemes/bearerAuth' - - parameters: - userId: - name: userId - in: path - required: true - description: User identifier - schema: - type: integer - format: int64 - apiKeyId: - name: apiKeyId - in: path - required: true - description: API key identifier - schema: - type: integer - format: int64 - - responses: - Forbidden: - description: Insufficient permissions - content: - application/json: - schema: - $ref: 'shared.yaml#/components/schemas/ErrorDetails' - - schemas: - - # -- Auth -- - LoginRequestDTO: - type: object - required: [username, password] - properties: - username: - type: string - minLength: 1 - password: - type: string - minLength: 1 - - LoginResponseDTO: - type: object - required: [access_token, user] - properties: - access_token: - type: string - description: JWT access token - user: - $ref: '#/components/schemas/UserInfoDTO' - - UserInfoDTO: - type: object - required: [username, role] - properties: - username: - type: string - role: - type: string - description: 'User role (e.g. ADMIN, SUPERADMIN)' - - ChangePasswordDTO: - type: object - required: [old, new, confirm] - properties: - old: - type: string - minLength: 1 - new: - type: string - minLength: 1 - confirm: - type: string - minLength: 1 - description: Must match `new` - - # -- Users -- - UserDTO: - type: object - required: [id, info] - properties: - id: - type: integer - format: int64 - info: - $ref: '#/components/schemas/UserInfoDTO' - - CreateUserDTO: - type: object - required: [username] - properties: - username: - type: string - pattern: '^[a-zA-Z0-9_\-]{1,50}$' - - UserCreatedDTO: - type: object - required: [id, password, info] - properties: - id: - type: integer - format: int64 - password: - type: string - description: Auto-generated password (shown only once) - info: - $ref: '#/components/schemas/UserInfoDTO' - - # -- API Keys -- - ApiKeyCreateRequest: - type: object - required: [name] - properties: - name: - type: string - minLength: 3 - maxLength: 50 - - ApiKeyResponse: - type: object - required: [id, name, prefix, createdAt] - properties: - id: - type: integer - format: int64 - name: - type: string - prefix: - type: string - createdAt: - type: string - format: date-time - - ApiKeyWithRawToken: - type: object - required: [id, name, prefix, createdAt, rawToken] - properties: - id: - type: integer - format: int64 - name: - type: string - prefix: - type: string - createdAt: - type: string - format: date-time - rawToken: - type: string - description: Full API key value (shown only once) - - # -- Level -- - CreateLevelDTO: - type: object - required: [name, abbreviation] - properties: - name: - type: string - minLength: 1 - abbreviation: - type: string - - UpdateLevelDTO: - type: object - required: [name, abbreviation] - properties: - name: - type: string - minLength: 1 - abbreviation: - type: string - - # -- Group -- - CreateGroupDTO: - type: object - required: [name, type, classe, size] - properties: - name: - type: string - type: - type: string - classe: - type: string - size: - type: integer - minimum: 1 - - UpdateGroupDTO: - type: object - required: [name, type, classe, size] - properties: - name: - type: string - type: - type: string - classe: - type: string - size: - type: integer - minimum: 1 - - # -- Teacher -- - CreateTeacherDTO: - type: object - required: [name, abbreviation] - properties: - name: - type: string - abbreviation: - type: string - - UpdateTeacherDTO: - type: object - required: [name, abbreviation] - properties: - name: - type: string - abbreviation: - type: string - - # -- Teaching Unit -- - CreateTeachingUnitDTO: - type: object - required: [abbreviation, name] - properties: - abbreviation: - type: string - name: - type: string - levelId: - type: integer - format: int64 - description: Optional level association - - UpdateTeachingUnitDTO: - type: object - properties: - abbreviation: - type: string - name: - type: string - levelId: - type: integer - format: int64 - description: Optional level association - - # -- Room -- - CreateRoomDTO: - type: object - required: [name, abbreviation, size] - properties: - name: - type: string - abbreviation: - type: string - size: - type: integer - minimum: 1 - - UpdateRoomDTO: - type: object - required: [name, abbreviation, size] - properties: - name: - type: string - abbreviation: - type: string - size: - type: integer - minimum: 1 - - # -- Schedule -- - CreateScheduleItemDTO: - type: object - required: [groupIds, teacherId, teachingUnitId, roomId, startTime, endTime] - properties: - groupIds: - type: array - items: - type: integer - format: int64 - teacherId: - type: integer - format: int64 - teachingUnitId: - type: integer - format: int64 - roomId: - type: integer - format: int64 - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - - UpdateScheduleItemDTO: - type: object - properties: - groupIds: - type: array - items: - type: integer - format: int64 - teacherId: - type: integer - format: int64 - teachingUnitId: - type: integer - format: int64 - roomId: - type: integer - format: int64 - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - - BatchCreateResponseScheduleItem: - type: object - required: [successItems, failedItems] - properties: - successItems: - type: array - items: - $ref: 'shared.yaml#/components/schemas/ScheduleItemDTO' - failedItems: - type: array - items: - $ref: '#/components/schemas/FailedScheduleItem' - - FailedScheduleItem: - type: object - required: [item, reason] - properties: - item: - $ref: '#/components/schemas/CreateScheduleItemDTO' - reason: - type: string - - # -- Import / Export -- - ImportMapping: - type: object - description: | - Maps file names to their table mappings. Structure: - `{ "": { "": TableMapping } }` - additionalProperties: - type: object - additionalProperties: - $ref: '#/components/schemas/TableMapping' - - TableMapping: - type: object - required: [entityType, headersMapping] - properties: - entityType: - type: string - description: Target entity type (room, teacher, teaching_unit, group, level) - headersMapping: - type: object - additionalProperties: - type: string - description: Source column name → entity property name - - ImportSummary: - type: object - required: [totalRows, successfulRows, failedRows, errors, skippedFiles, entitySummary] - properties: - totalRows: - type: integer - successfulRows: - type: integer - failedRows: - type: integer - errors: - type: array - items: - $ref: '#/components/schemas/SyncError' - skippedFiles: - type: array - items: - type: string - entitySummary: - type: object - additionalProperties: - type: integer - description: Count of successfully imported rows per entity type - - SyncError: - type: object - properties: - entityType: - type: string - rowIndex: - type: integer - errorMessage: - type: string - invalidValue: - type: string - - # -- Error responses -- - ValidationErrorDetails: - type: object - required: [timestamp, errors, errorCode] - properties: - timestamp: - type: string - format: date-time - errors: - type: array - items: - $ref: '#/components/schemas/ValidationError' - errorCode: - type: string - - ValidationError: - type: object - required: [field, error] - properties: - field: - type: string - error: - type: string diff --git a/src/main/resources/api/shared.yaml b/src/main/resources/api/shared.yaml index 31bc3ed..c9ad32a 100644 --- a/src/main/resources/api/shared.yaml +++ b/src/main/resources/api/shared.yaml @@ -1,3 +1,8 @@ +# shared.yaml — Single source of truth for all operation logic, schemas, +# security schemes, parameters, and responses. +# Referenced by rest.yaml (frontend) and integration.yaml (integration). +# This file does not define top-level paths, servers, or global security. + openapi: 3.1.0 info: title: Pointeur Backend – Shared Components @@ -7,12 +12,11 @@ info: This file is not intended to be used as a standalone API document. version: 2.0.0 -# --------------------------------------------------------------------------- -# Components -# --------------------------------------------------------------------------- components: - # ── Security schemes ───────────────────────────────────────────────────── + # ═══════════════════════════════════════════════════════════════════════════ + # Security schemes + # ═══════════════════════════════════════════════════════════════════════════ securitySchemes: bearerAuth: type: http @@ -26,7 +30,9 @@ components: name: X-Api-Key description: API key issued through the frontend API's API Keys management - # ── Reusable parameters ────────────────────────────────────────────────── + # ═══════════════════════════════════════════════════════════════════════════ + # Reusable parameters + # ═══════════════════════════════════════════════════════════════════════════ parameters: levelId: name: levelId @@ -82,7 +88,27 @@ components: type: integer format: int64 - # ── Reusable responses ─────────────────────────────────────────────────── + userId: + name: userId + in: path + required: true + description: User identifier + schema: + type: integer + format: int64 + + apiKeyId: + name: apiKeyId + in: path + required: true + description: API key identifier + schema: + type: integer + format: int64 + + # ═══════════════════════════════════════════════════════════════════════════ + # Reusable responses + # ═══════════════════════════════════════════════════════════════════════════ responses: Unauthorized: description: Authentication required @@ -91,9 +117,20 @@ components: schema: $ref: '#/components/schemas/ErrorDetails' - # ── Schemas ────────────────────────────────────────────────────────────── + Forbidden: + description: Insufficient permissions + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ═══════════════════════════════════════════════════════════════════════════ + # Schemas — every DTO used by any endpoint lives here + # ═══════════════════════════════════════════════════════════════════════════ schemas: + # -- Shared read DTOs --------------------------------------------------- + LevelDTO: type: object required: [id, name, abbreviation] @@ -201,6 +238,392 @@ components: type: string format: date-time + # -- Auth DTOs ---------------------------------------------------------- + + LoginRequestDTO: + type: object + required: [username, password] + properties: + username: + type: string + minLength: 1 + password: + type: string + minLength: 1 + + LoginResponseDTO: + type: object + required: [access_token, user] + properties: + access_token: + type: string + description: JWT access token + user: + $ref: '#/components/schemas/UserInfoDTO' + + UserInfoDTO: + type: object + required: [username, role] + properties: + username: + type: string + role: + type: string + description: 'User role (e.g. ADMIN, SUPERADMIN)' + + ChangePasswordDTO: + type: object + required: [old, new, confirm] + properties: + old: + type: string + minLength: 1 + new: + type: string + minLength: 1 + confirm: + type: string + minLength: 1 + description: Must match `new` + + # -- User DTOs ---------------------------------------------------------- + + UserDTO: + type: object + required: [id, info] + properties: + id: + type: integer + format: int64 + info: + $ref: '#/components/schemas/UserInfoDTO' + + CreateUserDTO: + type: object + required: [username] + properties: + username: + type: string + pattern: '^[a-zA-Z0-9_\-]{1,50}$' + + UserCreatedDTO: + type: object + required: [id, password, info] + properties: + id: + type: integer + format: int64 + password: + type: string + description: Auto-generated password (shown only once) + info: + $ref: '#/components/schemas/UserInfoDTO' + + # -- API Key DTOs ------------------------------------------------------- + + ApiKeyCreateRequest: + type: object + required: [name] + properties: + name: + type: string + minLength: 3 + maxLength: 50 + + ApiKeyResponse: + type: object + required: [id, name, prefix, createdAt] + properties: + id: + type: integer + format: int64 + name: + type: string + prefix: + type: string + createdAt: + type: string + format: date-time + + ApiKeyWithRawToken: + type: object + required: [id, name, prefix, createdAt, rawToken] + properties: + id: + type: integer + format: int64 + name: + type: string + prefix: + type: string + createdAt: + type: string + format: date-time + rawToken: + type: string + description: Full API key value (shown only once) + + # -- Write DTOs: Level -------------------------------------------------- + + CreateLevelDTO: + type: object + required: [name, abbreviation] + properties: + name: + type: string + minLength: 1 + abbreviation: + type: string + + UpdateLevelDTO: + type: object + required: [name, abbreviation] + properties: + name: + type: string + minLength: 1 + abbreviation: + type: string + + # -- Write DTOs: Group -------------------------------------------------- + + CreateGroupDTO: + type: object + required: [name, type, classe, size] + properties: + name: + type: string + type: + type: string + classe: + type: string + size: + type: integer + minimum: 1 + + UpdateGroupDTO: + type: object + required: [name, type, classe, size] + properties: + name: + type: string + type: + type: string + classe: + type: string + size: + type: integer + minimum: 1 + + # -- Write DTOs: Teacher ------------------------------------------------ + + CreateTeacherDTO: + type: object + required: [name, abbreviation] + properties: + name: + type: string + abbreviation: + type: string + + UpdateTeacherDTO: + type: object + required: [name, abbreviation] + properties: + name: + type: string + abbreviation: + type: string + + # -- Write DTOs: Teaching Unit ------------------------------------------ + + CreateTeachingUnitDTO: + type: object + required: [abbreviation, name] + properties: + abbreviation: + type: string + name: + type: string + levelId: + type: integer + format: int64 + description: Optional level association + + UpdateTeachingUnitDTO: + type: object + properties: + abbreviation: + type: string + name: + type: string + levelId: + type: integer + format: int64 + description: Optional level association + + # -- Write DTOs: Room --------------------------------------------------- + + CreateRoomDTO: + type: object + required: [name, abbreviation, size] + properties: + name: + type: string + abbreviation: + type: string + size: + type: integer + minimum: 1 + + UpdateRoomDTO: + type: object + required: [name, abbreviation, size] + properties: + name: + type: string + abbreviation: + type: string + size: + type: integer + minimum: 1 + + # -- Write DTOs: Schedule ----------------------------------------------- + + CreateScheduleItemDTO: + type: object + required: [groupIds, teacherId, teachingUnitId, roomId, startTime, endTime] + properties: + groupIds: + type: array + items: + type: integer + format: int64 + teacherId: + type: integer + format: int64 + teachingUnitId: + type: integer + format: int64 + roomId: + type: integer + format: int64 + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time + + UpdateScheduleItemDTO: + type: object + properties: + groupIds: + type: array + items: + type: integer + format: int64 + teacherId: + type: integer + format: int64 + teachingUnitId: + type: integer + format: int64 + roomId: + type: integer + format: int64 + startTime: + type: string + format: date-time + endTime: + type: string + format: date-time + + BatchCreateResponseScheduleItem: + type: object + required: [successItems, failedItems] + properties: + successItems: + type: array + items: + $ref: '#/components/schemas/ScheduleItemDTO' + failedItems: + type: array + items: + $ref: '#/components/schemas/FailedScheduleItem' + + FailedScheduleItem: + type: object + required: [item, reason] + properties: + item: + $ref: '#/components/schemas/CreateScheduleItemDTO' + reason: + type: string + + # -- Import / Export DTOs ----------------------------------------------- + + ImportMapping: + type: object + description: | + Maps file names to their table mappings. Structure: + `{ "": { "": TableMapping } }` + additionalProperties: + type: object + additionalProperties: + $ref: '#/components/schemas/TableMapping' + + TableMapping: + type: object + required: [entityType, headersMapping] + properties: + entityType: + type: string + description: Target entity type (room, teacher, teaching_unit, group, level) + headersMapping: + type: object + additionalProperties: + type: string + description: Source column name → entity property name + + ImportSummary: + type: object + required: [totalRows, successfulRows, failedRows, errors, skippedFiles, entitySummary] + properties: + totalRows: + type: integer + successfulRows: + type: integer + failedRows: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/SyncError' + skippedFiles: + type: array + items: + type: string + entitySummary: + type: object + additionalProperties: + type: integer + description: Count of successfully imported rows per entity type + + SyncError: + type: object + properties: + entityType: + type: string + rowIndex: + type: integer + errorMessage: + type: string + invalidValue: + type: string + + # -- Error responses ---------------------------------------------------- + ErrorDetails: type: object required: [timestamp, message, errorCode] @@ -216,367 +639,1271 @@ components: type: string description: Machine-readable error code - # ── Shared path items (GET operations available on both surfaces) ──────── - pathItems: + ValidationErrorDetails: + type: object + required: [timestamp, errors, errorCode] + properties: + timestamp: + type: string + format: date-time + errors: + type: array + items: + $ref: '#/components/schemas/ValidationError' + errorCode: + type: string + + ValidationError: + type: object + required: [field, error] + properties: + field: + type: string + error: + type: string + + # ═══════════════════════════════════════════════════════════════════════════ + # x-operations — every operation definition lives here. + # Each spec's paths reference these via $ref at the HTTP-method level. + # ═══════════════════════════════════════════════════════════════════════════ + x-operations: + + # ── Authentication ───────────────────────────────────────────────────── + + login: + operationId: login + tags: [Authentication] + summary: Authenticate a user + description: | + Validates credentials and returns a JWT access token together with user + information. Refresh-token and device-id cookies are set automatically. + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequestDTO' + responses: + '200': + description: Authentication successful + headers: + Set-Cookie: + description: '`refresh_token` and `device_id` HttpOnly cookies' + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/LoginResponseDTO' + '401': + description: Invalid credentials + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + getCurrentUser: + operationId: getCurrentUser + tags: [Authentication] + summary: Get the current authenticated user + responses: + '200': + description: Current user information + content: + application/json: + schema: + $ref: '#/components/schemas/UserInfoDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + refreshToken: + operationId: refreshToken + tags: [Authentication] + summary: Refresh the access token + description: | + Uses the `refresh_token` and `device_id` cookies to issue a new access + token. The refresh-token cookie is rotated. + security: [] + parameters: + - name: device_id + in: cookie + schema: + type: string + - name: refresh_token + in: cookie + schema: + type: string + responses: + '200': + description: Token refreshed + headers: + Set-Cookie: + description: Updated `refresh_token` HttpOnly cookie + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/LoginResponseDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + logout: + operationId: logout + tags: [Authentication] + summary: Log out the current session + description: Clears the refresh-token cookie. + security: [] + parameters: + - name: device_id + in: cookie + schema: + type: string + - name: refresh_token + in: cookie + schema: + type: string + responses: + '204': + description: Logged out successfully + + changePassword: + operationId: changePassword + tags: [Authentication] + summary: Change the current user's password + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePasswordDTO' + responses: + '200': + description: Password changed successfully + content: + application/json: + schema: + type: string + '400': + description: Validation error or wrong old password + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + + # ── Users ────────────────────────────────────────────────────────────── + + listUsers: + operationId: listUsers + tags: [Users] + summary: List all users + description: Returns all users except the SuperAdmin account. Requires SuperAdmin role. + responses: + '200': + description: List of users + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/UserDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + createUser: + operationId: createUser + tags: [Users] + summary: Create a new admin user + description: Creates a new user with a randomly generated password. Requires SuperAdmin role. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserDTO' + responses: + '201': + description: User created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserCreatedDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + getUser: + operationId: getUser + tags: [Users] + summary: Get a user by ID + parameters: + - $ref: '#/components/parameters/userId' + responses: + '200': + description: User details + content: + application/json: + schema: + $ref: '#/components/schemas/UserDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteUser: + operationId: deleteUser + tags: [Users] + summary: Delete a user + parameters: + - $ref: '#/components/parameters/userId' + responses: + '204': + description: User deleted + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── API Keys ─────────────────────────────────────────────────────────── + + listApiKeys: + operationId: listApiKeys + tags: [API Keys] + summary: List all API keys + responses: + '200': + description: List of API keys (tokens are masked) + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ApiKeyResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + createApiKey: + operationId: createApiKey + tags: [API Keys] + summary: Create a new API key + description: | + The raw token is returned **only once** in this response. Store it + securely — it cannot be retrieved again. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyCreateRequest' + responses: + '200': + description: API key created + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyWithRawToken' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + deleteApiKey: + operationId: deleteApiKey + tags: [API Keys] + summary: Revoke an API key + parameters: + - $ref: '#/components/parameters/apiKeyId' + responses: + '204': + description: API key revoked + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + + # ── Levels ───────────────────────────────────────────────────────────── listLevels: - get: - operationId: listLevels - tags: [Levels] - summary: List all levels with their groups - responses: - '200': - description: List of levels with nested groups - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/LevelDetailsDTO' - '401': - $ref: '#/components/responses/Unauthorized' + operationId: listLevels + tags: [Levels] + summary: List all levels with their groups + responses: + '200': + description: List of levels with nested groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + createLevel: + operationId: createLevel + tags: [Levels] + summary: Create a new level + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateLevelDTO' + responses: + '201': + description: Level created + content: + application/json: + schema: + $ref: '#/components/schemas/LevelDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Level name already exists + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' getLevel: - get: - operationId: getLevel - tags: [Levels] - summary: Get a level with its groups - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: Level details with groups - content: - application/json: - schema: - $ref: '#/components/schemas/LevelDetailsDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: getLevel + tags: [Levels] + summary: Get a level with its groups + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Level details with groups + content: + application/json: + schema: + $ref: '#/components/schemas/LevelDetailsDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateLevel: + operationId: updateLevel + tags: [Levels] + summary: Update a level + parameters: + - $ref: '#/components/parameters/levelId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateLevelDTO' + responses: + '200': + description: Level updated + content: + application/json: + schema: + $ref: '#/components/schemas/LevelDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Level name already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteLevel: + operationId: deleteLevel + tags: [Levels] + summary: Delete a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '204': + description: Level deleted + + # ── Groups ───────────────────────────────────────────────────────────── listGroupsByLevel: - get: - operationId: listGroupsByLevel - tags: [Groups] - summary: List groups in a level - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: List of groups - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/GroupDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: listGroupsByLevel + tags: [Groups] + summary: List groups in a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: List of groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + createGroup: + operationId: createGroup + tags: [Groups] + summary: Create a group in a level + parameters: + - $ref: '#/components/parameters/levelId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateGroupDTO' + responses: + '201': + description: Group created + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' getGroup: - get: - operationId: getGroup - tags: [Groups] - summary: Get a group - parameters: - - $ref: '#/components/parameters/levelId' - - $ref: '#/components/parameters/groupId' - responses: - '200': - description: Group details - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Group or level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: getGroup + tags: [Groups] + summary: Get a group + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' + responses: + '200': + description: Group details + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group or level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateGroup: + operationId: updateGroup + tags: [Groups] + summary: Update a group + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateGroupDTO' + responses: + '200': + description: Group updated + content: + application/json: + schema: + $ref: '#/components/schemas/GroupDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Group or level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteGroup: + operationId: deleteGroup + tags: [Groups] + summary: Delete a group + parameters: + - $ref: '#/components/parameters/levelId' + - $ref: '#/components/parameters/groupId' + responses: + '204': + description: Group deleted + + # ── Teachers ─────────────────────────────────────────────────────────── listTeachers: - get: - operationId: listTeachers - tags: [Teachers] - summary: List all teachers - responses: - '200': - description: List of teachers - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeacherDTO' - '401': - $ref: '#/components/responses/Unauthorized' + operationId: listTeachers + tags: [Teachers] + summary: List all teachers + responses: + '200': + description: List of teachers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + createTeacher: + operationId: createTeacher + tags: [Teachers] + summary: Register a new teacher + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTeacherDTO' + responses: + '201': + description: Teacher registered + content: + application/json: + schema: + $ref: '#/components/schemas/TeacherDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' getTeacher: - get: - operationId: getTeacher - tags: [Teachers] - summary: Get a teacher - parameters: - - $ref: '#/components/parameters/teacherId' - responses: - '200': - description: Teacher details - content: - application/json: - schema: - $ref: '#/components/schemas/TeacherDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Teacher not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: getTeacher + tags: [Teachers] + summary: Get a teacher + parameters: + - $ref: '#/components/parameters/teacherId' + responses: + '200': + description: Teacher details + content: + application/json: + schema: + $ref: '#/components/schemas/TeacherDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teacher not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateTeacher: + operationId: updateTeacher + tags: [Teachers] + summary: Update a teacher + parameters: + - $ref: '#/components/parameters/teacherId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateTeacherDTO' + responses: + '200': + description: Teacher updated + content: + application/json: + schema: + $ref: '#/components/schemas/TeacherDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Teacher not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteTeacher: + operationId: deleteTeacher + tags: [Teachers] + summary: Delete a teacher + parameters: + - $ref: '#/components/parameters/teacherId' + responses: + '204': + description: Teacher deleted + + # ── Teaching Units ───────────────────────────────────────────────────── listTeachingUnits: - get: - operationId: listTeachingUnits - tags: [Teaching Units] - summary: List all teaching units - responses: - '200': - description: List of teaching units - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' + operationId: listTeachingUnits + tags: [Teaching Units] + summary: List all teaching units + responses: + '200': + description: List of teaching units + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + + createTeachingUnit: + operationId: createTeachingUnit + tags: [Teaching Units] + summary: Create a teaching unit + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateTeachingUnitDTO' + responses: + '201': + description: Teaching unit created + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' getTeachingUnit: - get: - operationId: getTeachingUnit - tags: [Teaching Units] - summary: Get a teaching unit - parameters: - - $ref: '#/components/parameters/unitId' - responses: - '200': - description: Teaching unit details - content: - application/json: - schema: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Teaching unit not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: getTeachingUnit + tags: [Teaching Units] + summary: Get a teaching unit + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '200': + description: Teaching unit details + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateTeachingUnit: + operationId: updateTeachingUnit + tags: [Teaching Units] + summary: Update a teaching unit + parameters: + - $ref: '#/components/parameters/unitId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateTeachingUnitDTO' + responses: + '200': + description: Teaching unit updated + content: + application/json: + schema: + $ref: '#/components/schemas/TeachingUnitDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Teaching unit not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Abbreviation already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteTeachingUnit: + operationId: deleteTeachingUnit + tags: [Teaching Units] + summary: Delete a teaching unit + parameters: + - $ref: '#/components/parameters/unitId' + responses: + '204': + description: Teaching unit deleted listTeachingUnitsByLevel: - get: - operationId: listTeachingUnitsByLevel - tags: [Teaching Units] - summary: List teaching units for a level - parameters: - - $ref: '#/components/parameters/levelId' - responses: - '200': - description: Teaching units taught in the level - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Level not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + operationId: listTeachingUnitsByLevel + tags: [Teaching Units] + summary: List teaching units for a level + parameters: + - $ref: '#/components/parameters/levelId' + responses: + '200': + description: Teaching units taught in the level + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeachingUnitDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Level not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' - listRooms: - get: - operationId: listRooms - tags: [Rooms] - summary: List all rooms - responses: - '200': - description: List of rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - '401': - $ref: '#/components/responses/Unauthorized' + # ── Rooms ────────────────────────────────────────────────────────────── - getRoom: - get: - operationId: getRoom - tags: [Rooms] - summary: Get a room - parameters: - - $ref: '#/components/parameters/roomId' - responses: - '200': - description: Room details - content: - application/json: - schema: + listRooms: + operationId: listRooms + tags: [Rooms] + summary: List all rooms + responses: + '200': + description: List of rooms + content: + application/json: + schema: + type: array + items: $ref: '#/components/schemas/RoomDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Room not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' - listAvailableRooms: - get: - operationId: listAvailableRooms - tags: [Rooms] - summary: List available rooms in a time range - description: Returns rooms that are unoccupied between the given start and end times. - parameters: - - name: startTime - in: query - required: true - description: Start date-time (ISO 8601) - schema: - type: string - format: date-time - - name: endTime - in: query - required: true - description: End date-time (ISO 8601) + createRoom: + operationId: createRoom + tags: [Rooms] + summary: Create a room + requestBody: + required: true + content: + application/json: schema: - type: string - format: date-time - - name: size - in: query - required: false - description: Minimum room capacity (defaults to 1) + $ref: '#/components/schemas/CreateRoomDTO' + responses: + '201': + description: Room created + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '409': + description: Room name already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + getRoom: + operationId: getRoom + tags: [Rooms] + summary: Get a room + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '200': + description: Room details + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateRoom: + operationId: updateRoom + tags: [Rooms] + summary: Update a room + parameters: + - $ref: '#/components/parameters/roomId' + requestBody: + required: true + content: + application/json: schema: - type: integer - minimum: 1 - default: 1 - responses: - '200': - description: Available rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' - '400': - description: Invalid or missing parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - '401': - $ref: '#/components/responses/Unauthorized' + $ref: '#/components/schemas/UpdateRoomDTO' + responses: + '200': + description: Room updated + content: + application/json: + schema: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Room not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Room name already taken + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteRoom: + operationId: deleteRoom + tags: [Rooms] + summary: Delete a room + parameters: + - $ref: '#/components/parameters/roomId' + responses: + '204': + description: Room deleted + + listAvailableRooms: + operationId: listAvailableRooms + tags: [Rooms] + summary: List available rooms in a time range + description: Returns rooms that are unoccupied between the given start and end times. + parameters: + - name: startTime + in: query + required: true + description: Start date-time (ISO 8601) + schema: + type: string + format: date-time + - name: endTime + in: query + required: true + description: End date-time (ISO 8601) + schema: + type: string + format: date-time + - name: size + in: query + required: false + description: Minimum room capacity (defaults to 1) + schema: + type: integer + minimum: 1 + default: 1 + responses: + '200': + description: Available rooms + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/RoomDTO' + '400': + description: Invalid or missing parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + + # ── Schedule ─────────────────────────────────────────────────────────── getSchedule: - get: - operationId: getSchedule - tags: [Schedule] - summary: Query the schedule - description: | - Returns schedule items in the given date range. Optionally filter by - level and/or group. If `groupId` is supplied without `levelId`, only - that group's timetable is returned. - parameters: - - name: startDate - in: query - required: true + operationId: getSchedule + tags: [Schedule] + summary: Query the schedule + description: | + Returns schedule items in the given date range. Optionally filter by + level and/or group. If `groupId` is supplied without `levelId`, only + that group's timetable is returned. + parameters: + - name: startDate + in: query + required: true + schema: + type: string + format: date + - name: endDate + in: query + required: true + schema: + type: string + format: date + - name: levelId + in: query + required: false + schema: + type: integer + format: int64 + - name: groupId + in: query + required: false + schema: + type: integer + format: int64 + responses: + '200': + description: Matching schedule items + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Invalid date range or parameters + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Group not found in the specified level + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + createScheduleItem: + operationId: createScheduleItem + tags: [Schedule] + summary: Create a schedule item + requestBody: + required: true + content: + application/json: schema: - type: string - format: date - - name: endDate - in: query - required: true + $ref: '#/components/schemas/CreateScheduleItemDTO' + responses: + '201': + description: Schedule item created + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Referenced resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Schedule conflict (room, teacher, or group overlap) + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + createScheduleItemsBatch: + operationId: createScheduleItemsBatch + tags: [Schedule] + summary: Create multiple schedule items + description: | + Processes each item independently. Successfully created items and + failed items (with reasons) are returned together. + requestBody: + required: true + content: + application/json: schema: - type: string - format: date - - name: levelId - in: query - required: false + type: array + items: + $ref: '#/components/schemas/CreateScheduleItemDTO' + responses: + '200': + description: Batch result + content: + application/json: + schema: + $ref: '#/components/schemas/BatchCreateResponseScheduleItem' + + getScheduleItem: + operationId: getScheduleItem + tags: [Schedule] + summary: Get a schedule item + parameters: + - $ref: '#/components/parameters/scheduleItemId' + responses: + '200': + description: Schedule item details + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + description: Schedule item not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + updateScheduleItem: + operationId: updateScheduleItem + tags: [Schedule] + summary: Update a schedule item + parameters: + - $ref: '#/components/parameters/scheduleItemId' + requestBody: + required: true + content: + application/json: schema: - type: integer - format: int64 - - name: groupId - in: query - required: false + $ref: '#/components/schemas/UpdateScheduleItemDTO' + responses: + '200': + description: Schedule item updated + content: + application/json: + schema: + $ref: '#/components/schemas/ScheduleItemDTO' + '400': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationErrorDetails' + '404': + description: Schedule item or referenced resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + '409': + description: Schedule conflict + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + deleteScheduleItem: + operationId: deleteScheduleItem + tags: [Schedule] + summary: Delete a schedule item + parameters: + - $ref: '#/components/parameters/scheduleItemId' + responses: + '204': + description: Schedule item deleted + + # ── Export ───────────────────────────────────────────────────────────── + + exportData: + operationId: exportData + tags: [Export] + summary: Export entities + description: | + Exports the requested entities in the specified format. The response + is a downloadable file (Excel, ZIP of CSVs, or JSON). + parameters: + - name: entitiesList + in: query + required: true + description: Entity types to export (e.g. room, teacher, level, group, teaching_unit) + schema: + type: array + items: + type: string + enum: [room, teacher, teaching_unit, group, level] + - name: format + in: query + required: true + description: Output format + schema: + type: string + enum: [excel, zip_csv, json] + responses: + '200': + description: Exported file + headers: + Content-Disposition: + description: 'attachment; filename=export__.' + schema: + type: string + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: + schema: + type: string + format: binary + application/zip: + schema: + type: string + format: binary + application/json: + schema: + type: string + format: binary + '400': + description: Unknown entity or unsupported format + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + # ── Import ───────────────────────────────────────────────────────────── + + importData: + operationId: importData + tags: [Import] + summary: Import data from files + description: | + Accepts one or more files together with a metadata mapping that + describes how columns map to entity fields. + parameters: + - name: ignoreConflicts + in: query + required: false + description: Whether to skip rows that conflict with existing data + schema: + type: boolean + default: true + requestBody: + required: true + content: + multipart/form-data: schema: - type: integer - format: int64 - responses: - '200': - description: Matching schedule items - content: - application/json: - schema: + type: object + required: [metadata, files] + properties: + metadata: + $ref: '#/components/schemas/ImportMapping' + files: type: array items: - $ref: '#/components/schemas/ScheduleItemDTO' - '400': - description: Invalid date range or parameters - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Group not found in the specified level - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - - getScheduleItem: - get: - operationId: getScheduleItem - tags: [Schedule] - summary: Get a schedule item - parameters: - - $ref: '#/components/parameters/scheduleItemId' - responses: - '200': - description: Schedule item details - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduleItemDTO' - '401': - $ref: '#/components/responses/Unauthorized' - '404': - description: Schedule item not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + type: string + format: binary + responses: + '200': + description: Import summary + content: + application/json: + schema: + $ref: '#/components/schemas/ImportSummary' + '400': + description: Invalid file format or unsupported codec + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails'