diff --git a/src/main/resources/api/integration.yaml b/src/main/resources/api/integration.yaml new file mode 100644 index 0000000..564b69c --- /dev/null +++ b/src/main/resources/api/integration.yaml @@ -0,0 +1,110 @@ +# 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 + description: | + Read-only REST API for external integrations with the Pointeur scheduling + 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. + - Endpoints related to users and API key management are not available. + version: 2.0.0 + contact: + name: 2RK-dev + +servers: + - url: /integration + description: Integration API base path + +# Global security — X-Api-Key header for all endpoints. +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 — GET-only subset, each method $refs the operation from shared.yaml +# --------------------------------------------------------------------------- +paths: + + /levels: + get: + $ref: 'shared.yaml#/components/x-operations/listLevels' + + /levels/{levelId}: + get: + $ref: 'shared.yaml#/components/x-operations/getLevel' + + /levels/{levelId}/groups: + get: + $ref: 'shared.yaml#/components/x-operations/listGroupsByLevel' + + /levels/{levelId}/groups/{groupId}: + get: + $ref: 'shared.yaml#/components/x-operations/getGroup' + + /levels/{levelId}/teachingUnits: + get: + $ref: 'shared.yaml#/components/x-operations/listTeachingUnitsByLevel' + + /teachers: + get: + $ref: 'shared.yaml#/components/x-operations/listTeachers' + + /teachers/{teacherId}: + get: + $ref: 'shared.yaml#/components/x-operations/getTeacher' + + /teachingUnits: + get: + $ref: 'shared.yaml#/components/x-operations/listTeachingUnits' + + /teachingUnits/{unitId}: + get: + $ref: 'shared.yaml#/components/x-operations/getTeachingUnit' + + /rooms: + get: + $ref: 'shared.yaml#/components/x-operations/listRooms' + + /rooms/{roomId}: + get: + $ref: 'shared.yaml#/components/x-operations/getRoom' + + /rooms/available: + get: + $ref: 'shared.yaml#/components/x-operations/listAvailableRooms' + + /schedule: + get: + $ref: 'shared.yaml#/components/x-operations/getSchedule' + + /schedule/{scheduleItemId}: + get: + $ref: 'shared.yaml#/components/x-operations/getScheduleItem' + +# --------------------------------------------------------------------------- +# Components — security scheme referenced from shared.yaml +# --------------------------------------------------------------------------- +components: + securitySchemes: + apiKeyAuth: + $ref: 'shared.yaml#/components/securitySchemes/apiKeyAuth' diff --git a/src/main/resources/api/rest.yaml b/src/main/resources/api/rest.yaml index 0a8fe55..f83ef96 100644 --- a/src/main/resources/api/rest.yaml +++ b/src/main/resources/api/rest.yaml @@ -1,1071 +1,216 @@ +# 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 API specification - description: Pointeur Backend API specification - version: 1.0.0 -servers: - - url: 'http://localhost:8080/api/v1' - description: Development server + title: Pointeur Backend – Frontend API + description: | + REST API for the Pointeur scheduling application. + This specification covers the **frontend-facing** surface. + All endpoints require JWT Bearer authentication unless stated otherwise. + version: 2.0.0 + contact: + name: 2RK-dev +servers: + - url: /api/v1 + description: Frontend API base path + +# Global security — inherited by all operations that do not override it. +security: + - bearerAuth: [] + +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 — each HTTP method $refs the corresponding operation from shared.yaml +# --------------------------------------------------------------------------- paths: - /levels: + + # ── Authentication ─────────────────────────────────────────────────────── + /auth/login: + post: + $ref: 'shared.yaml#/components/x-operations/login' + + /auth/me: + get: + $ref: 'shared.yaml#/components/x-operations/getCurrentUser' + + /auth/refresh: + post: + $ref: 'shared.yaml#/components/x-operations/refreshToken' + + /auth/logout: + post: + $ref: 'shared.yaml#/components/x-operations/logout' + + /auth/password: + put: + $ref: 'shared.yaml#/components/x-operations/changePassword' + + # ── Users ──────────────────────────────────────────────────────────────── + /users: + get: + $ref: 'shared.yaml#/components/x-operations/listUsers' + post: + $ref: 'shared.yaml#/components/x-operations/createUser' + + /users/{userId}: + get: + $ref: 'shared.yaml#/components/x-operations/getUser' + delete: + $ref: 'shared.yaml#/components/x-operations/deleteUser' + + # ── API Keys ───────────────────────────────────────────────────────────── + /api-keys: + get: + $ref: 'shared.yaml#/components/x-operations/listApiKeys' post: - description: Create a new level - security: - - auth: [ ] - requestBody: - description: Information about the level - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateLevelDTO' - responses: - 201: - description: The Level was successfully created - content: - application/json: - schema: - $ref: '#/components/schemas/LevelDTO' + $ref: 'shared.yaml#/components/x-operations/createApiKey' - 400: - description: Validation error (the provided level name is blank) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The name of the level already exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + /api-keys/{apiKeyId}: + delete: + $ref: 'shared.yaml#/components/x-operations/deleteApiKey' + + # ── Levels ─────────────────────────────────────────────────────────────── + /levels: get: - description: Fetch all the existing levels - security: - - auth: [ ] - responses: - 200: - description: A list of the levels - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/LevelDTO' + $ref: 'shared.yaml#/components/x-operations/listLevels' + post: + $ref: 'shared.yaml#/components/x-operations/createLevel' /levels/{levelId}: get: - description: Get the informations of a level - security: - - auth: [ ] - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - responses: - 200: - description: The level's name and ID, along its groups - content: - application/json: - schema: - $ref: '#/components/schemas/LevelDetailsDTO' + $ref: 'shared.yaml#/components/x-operations/getLevel' put: - description: Change the infos of a level - security: - - auth: [ ] - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateLevelDTO' - 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 - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - 409: - description: The new name of the level is already taken - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateLevel' 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 + $ref: 'shared.yaml#/components/x-operations/deleteLevel' + # ── Groups ─────────────────────────────────────────────────────────────── /levels/{levelId}/groups: get: - description: Fetches the groups in this level - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - security: - - auth: [ ] - responses: - 200: - description: A list of the groups in this level - 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: 'shared.yaml#/components/x-operations/listGroupsByLevel' 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 - security: - - auth: [ ] - requestBody: - description: The infos of the new group - content: - application/json: - schema: - $ref: '#/components/schemas/CreateGroupDTO' - responses: - 201: - description: Group successfully created - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDTO' - 400: - description: Validation error (blank group name or group size less than 1) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: The queried level does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createGroup' /levels/{levelId}/groups/{groupId}: 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 - security: - - auth: [ ] - responses: - 200: - description: Infos of the group - 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 - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getGroup' put: - description: Update the infos of a group (cannot change levels) - 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 - security: - - auth: [ ] - requestBody: - description: New infos of the group - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateGroupDTO' - responses: - 200: - description: Group successfully updated - content: - application/json: - schema: - $ref: '#/components/schemas/GroupDTO' - 400: - description: Validation error (blank group name or group size less than 1) - 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' + $ref: 'shared.yaml#/components/x-operations/updateGroup' 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 - security: - - auth: [ ] - responses: - 204: - description: The group was deleted, or it didn't exist to begin with + $ref: 'shared.yaml#/components/x-operations/deleteGroup' + # ── Teachers ───────────────────────────────────────────────────────────── /teachers: get: - description: Fetch all the registered teachers - security: - - auth: [ ] - responses: - 200: - description: List of all the registered teachers - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeacherDTO' + $ref: 'shared.yaml#/components/x-operations/listTeachers' post: - description: Register (not create :P) a new teacher - security: - - auth: [ ] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTeacherDTO' - responses: - 201: - description: New teacher successfully registered - content: - application/json: - schema: - $ref: '#/components/schemas/TeacherDTO' - 400: - description: Validation error (the provided teacher's name or abbreviation is blank) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The abbreviation is already taken by an existing teacher - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createTeacher' /teachers/{teacherId}: get: - description: Fetch the infos about a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number - security: - - auth: [ ] - responses: - 200: - description: The requested teacher's informations - content: - application/json: - schema: - $ref: '#/components/schemas/TeacherDTO' - 404: - description: No teacher with the provided ID exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getTeacher' put: - description: Update the infos of a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number - security: - - auth: [ ] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateTeacherDTO' - responses: - 200: - description: The teacher's info was successfully updated - content: - application/json: - schema: - $ref: '#/components/schemas/TeacherDTO' - 400: - description: Validation error (the provided teacher's name or abbreviation is blank) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: No teacher with the provided ID exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - 409: - description: The abbreviation is already taken by an existing teacher - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateTeacher' delete: - description: Delete a teacher - parameters: - - in: path - name: teacherId - required: true - description: The identifier of the teacher - schema: - type: number - security: - - auth: [ ] - responses: - 204: - description: The teacher was deleted, or it didn't exist to begin with + $ref: 'shared.yaml#/components/x-operations/deleteTeacher' - /rooms: + # ── Teaching Units ─────────────────────────────────────────────────────── + /teachingUnits: get: - description: Fetch all the teaching rooms - security: - - auth: [ ] - responses: - 200: - description: A list of all the existing teaching rooms - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/RoomDTO' + $ref: 'shared.yaml#/components/x-operations/listTeachingUnits' post: - description: Create a new teaching room - security: - - auth: [ ] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateRoomDTO' - responses: - 201: - description: Room created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/RoomDTO' - 400: - description: Validation error (room size less than 1) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The room name is already taken (already exists) - 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' + $ref: 'shared.yaml#/components/x-operations/createTeachingUnit' - /rooms/{roomId}: + /teachingUnits/{unitId}: get: - description: Fetch the infos about a teaching room - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number - security: - - auth: [ ] - responses: - 200: - description: The requested room's info - content: - application/json: - schema: - $ref: '#/components/schemas/RoomDTO' - 404: - description: No room with the provided identifier exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getTeachingUnit' put: - description: Alter a teaching room's info - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number - security: - - auth: [ ] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateRoomDTO' - responses: - 200: - description: The room's info was successfully updated - content: - application/json: - schema: - $ref: '#/components/schemas/RoomDTO' - 400: - description: Validation error (room size less than 1) - 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) - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateTeachingUnit' delete: - description: Delete a teaching room - parameters: - - in: path - name: roomId - description: Identifier of the room - required: true - schema: - type: number - security: - - auth: [ ] - responses: - 204: - description: The room was deleted, or it didn't exist to begin with + $ref: 'shared.yaml#/components/x-operations/deleteTeachingUnit' - /teachingUnits: + /levels/{levelId}/teachingUnits: get: - description: Fetch all teaching units - security: - - auth: [ ] - responses: - 200: - description: A list of all the existing teaching units across all levels - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' + $ref: 'shared.yaml#/components/x-operations/listTeachingUnitsByLevel' + + # ── Rooms ──────────────────────────────────────────────────────────────── + /rooms: + get: + $ref: 'shared.yaml#/components/x-operations/listRooms' post: - description: Create a new teaching unit - security: - - auth: [ ] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateTeachingUnitDTO' - responses: - 201: - description: Teaching unit successfully created - 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) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 409: - description: The abbreviation is already associated with an existing teaching unit - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/createRoom' - /teachingUnits/{unitId}: + /rooms/{roomId}: 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 - security: - - auth: [ ] - responses: - 200: - description: the infos about the queried teaching unit - content: - application/json: - schema: - $ref: '#/components/schemas/TeachingUnitDTO' - 404: - description: No teaching unit with the provided identifier exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/getRoom' 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 - security: - - auth: [ ] - responses: - 200: - description: Successfully updated the teaching unit - 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) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: No teaching unit with the provided identifier exists - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - 409: - description: The abbreviation is already associated with an existing teaching unit - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/updateRoom' delete: - description: Delete a teaching unit - parameters: - - in: path - name: unitId - description: the identifier of the teaching unit - required: true - schema: - type: number - security: - - auth: [ ] - responses: - 204: - description: The teaching unit was successfully deleted, or it did not exist + $ref: 'shared.yaml#/components/x-operations/deleteRoom' - /levels/{levelId}/teachingUnits: + /rooms/available: get: - description: Fetch the teaching units taught to this level - parameters: - - in: path - name: levelId - description: the identifier of the level - schema: - type: number - required: true - security: - - auth: [ ] - responses: - 200: - description: List of all the teaching units taught to the queried level - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/TeachingUnitDTO' - 404: - description: The queried level does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' + $ref: 'shared.yaml#/components/x-operations/listAvailableRooms' + # ── 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." - parameters: - - in: query - name: startDate - required: true - schema: - type: string - format: date - - in: query - name: endDate - required: true - schema: - type: string - format: date - - in: query - name: levelId - schema: - type: number - - in: query - name: groupId - schema: - type: number - security: - - auth: [ ] - responses: - 200: - description: List of the schedule items in between startDate and endDate, scheduled for the groupId of levelId. - 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 - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - - /levels/{levelId}/schedule: + $ref: 'shared.yaml#/components/x-operations/getSchedule' post: - description: Add a schedule item to the schedule of a level - security: - - auth: [ ] - parameters: - - in: path - name: levelId - required: true - schema: - type: number - requestBody: - description: Infos about the room, teacher, group, startTime, endTime, and teaching unit - content: - application/json: - schema: - $ref: '#/components/schemas/CreateScheduleItemDTO' - responses: - 201: - description: Item successfully added to the level's schedule - content: - application/json: - schema: - $ref: '#/components/schemas/ScheduleItemDTO' - 400: - description: Validation error (nonexistent group, teacher, teaching unit, room, overlap) - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationErrorDetails' - 404: - description: The queried level does not exist - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorDetails' - -components: - securitySchemes: - auth: - type: http - description: Access token to authenticate and authorize requests - scheme: bearer - bearerFormat: JWT - schemas: - CreateScheduleItemDTO: - type: object - properties: - groupIds: - type: array - items: - type: number - teacherId: - type: number - teachingUnitId: - type: number - roomId: - type: number - startTime: - type: string - format: date-time - endTime: - type: string - format: date-time - required: [ groupIds, teacherId, teachingUnitId, roomId, startTime, endTime ] - - ScheduleItemDTO: - type: object - properties: - id: - type: number - 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: - type: object - properties: - abbreviation: - type: string - name: - type: - string - levelId: - type: number - required: [ abbreviation, name, levelId ] - - CreateTeachingUnitDTO: - type: object - properties: - abbreviation: - type: string - name: - type: - string - levelId: - type: number - required: [ abbreviation, name, levelId ] - - TeachingUnitDTO: - type: object - properties: - id: - type: number - abbreviation: - type: string - name: - type: - string - level: - $ref: '#/components/schemas/LevelDTO' - required: [ id, abbreviation, name, level ] - - UpdateRoomDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - size: - type: number - required: [ name, size ] - - CreateRoomDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - size: - type: number - required: [ name, size ] + $ref: 'shared.yaml#/components/x-operations/createScheduleItem' - RoomDTO: - type: object - properties: - id: - type: number - name: - type: string - abbreviation: - type: string - size: - type: number - required: [ id, name, size ] - - UpdateTeacherDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - required: [ abbreviation, name ] - - CreateTeacherDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - required: [ abbreviation, name ] - - TeacherDTO: - type: object - properties: - id: - type: number - name: - type: string - abbreviation: - type: string - required: [ id, abbreviation, name ] - - CreateGroupDTO: - type: object - properties: - name: - type: string - size: - type: number - required: [ name, size ] - - UpdateGroupDTO: - type: object - properties: - name: - type: string - size: - type: number - required: [ name, size ] - - LevelDetailsDTO: - type: object - properties: - level: - $ref: "#/components/schemas/LevelDTO" - groups: - type: array - items: - $ref: "#/components/schemas/GroupDTO" - required: [ level ] - - GroupDTO: - type: object - properties: - id: - type: number - name: - type: string - size: - type: number - level: - $ref: '#/components/schemas/LevelDTO' - required: [ id, name, size, level ] - - CreateLevelDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - required: [ name, abbreviation ] + /schedule/batch: + post: + $ref: 'shared.yaml#/components/x-operations/createScheduleItemsBatch' - UpdateLevelDTO: - type: object - properties: - name: - type: string - abbreviation: - type: string - required: [ name ] + /schedule/{scheduleItemId}: + get: + $ref: 'shared.yaml#/components/x-operations/getScheduleItem' + put: + $ref: 'shared.yaml#/components/x-operations/updateScheduleItem' + delete: + $ref: 'shared.yaml#/components/x-operations/deleteScheduleItem' - LevelDTO: - type: object - properties: - id: - type: number - name: - type: string - abbreviation: - type: string - required: [ id, name ] + # ── Export ─────────────────────────────────────────────────────────────── + /export: + get: + $ref: 'shared.yaml#/components/x-operations/exportData' - ErrorDetails: - description: Error details - type: object - properties: - timestamp: - type: string - message: - type: string - details: - type: string - errorCode: - type: string - required: [ timestamp, message, errorCode ] + # ── Import ─────────────────────────────────────────────────────────────── + /import/upload: + post: + $ref: 'shared.yaml#/components/x-operations/importData' - ValidationErrorDetails: - description: Details of validation errors - type: object - properties: - timestamp: - type: string - errors: - type: array - items: - type: object - properties: - field: - type: string - error: - type: string - errorCode: - type: string - required: [ timestamp, errors, errorCode ] \ No newline at end of file +# --------------------------------------------------------------------------- +# Components — security scheme referenced from shared.yaml +# --------------------------------------------------------------------------- +components: + securitySchemes: + bearerAuth: + $ref: 'shared.yaml#/components/securitySchemes/bearerAuth' diff --git a/src/main/resources/api/shared.yaml b/src/main/resources/api/shared.yaml new file mode 100644 index 0000000..c9ad32a --- /dev/null +++ b/src/main/resources/api/shared.yaml @@ -0,0 +1,1909 @@ +# 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 + 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: + + # ═══════════════════════════════════════════════════════════════════════════ + # 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 + + 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 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorDetails' + + 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] + 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 + + # -- 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] + 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] + 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: + 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: + 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: + 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: + 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: + 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: + 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: + 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: + 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: + 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' + + # ── Rooms ────────────────────────────────────────────────────────────── + + 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' + + createRoom: + 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: '#/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: + $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: + 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: + $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: 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: + $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: 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: '#/components/schemas/ErrorDetails'