diff --git a/dist/clients/faculty/openapi.yaml b/dist/clients/faculty/openapi.yaml index a57bf43..836c6f0 100644 --- a/dist/clients/faculty/openapi.yaml +++ b/dist/clients/faculty/openapi.yaml @@ -15,6 +15,7 @@ tags: - name: News - name: People - name: Places + - name: Faculty - Exams paths: /announcements: get: @@ -1579,6 +1580,379 @@ paths: $ref: '#/components/schemas/ErrorResponse' tags: - Event Admissions + /exams: + get: + operationId: FacultyExams_getExams + summary: List exams | Elenca esami + parameters: [] + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ExamBase' + required: + - data + example: + data: + - id: 888122 + courseId: 262147 + courseShortcode: 02JSKOV + courseName: Human Computer Interaction + type: Esami scritti a risposta aperta o chiusa tramite PC + places: + - buildingId: TO_CIT22 + floorId: XPTE + name: Aula 1P + roomId: '30' + siteId: TO_CIT + bookingEndsAt: '2026-08-31T14:00:00Z' + examStartsAt: '2026-09-02T14:00:00Z' + examEndsAt: '2026-09-02T17:00:00Z' + bookedCount: 42 + availableCount: 8 + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + /exams/{examId}/attendance: + post: + operationId: FacultyExams_checkAttendance + summary: Check exam attendance | Verifica presenze all'esame + description: Registra le presenze passando l'array delle matricole presenti. Restituisce lo stato aggiornato delle presenze. + parameters: + - name: examId + in: path + required: true + schema: + type: integer + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ExamAttendee' + required: + - data + example: + data: + - username: '290683' + name: Mario + surname: Rossi + needsExtraTime: true + extraTimeMinutes: 30 + isPresent: true + - username: '291045' + name: Laura + surname: Bianchi + needsExtraTime: false + extraTimeMinutes: null + isPresent: true + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CheckAttendanceRequest' + /exams/{examId}/notify: + post: + operationId: FacultyExams_notifyExamSubscribers + summary: Notify exam subscribers | Invia comunicazione agli iscritti all'esame + description: Invia un messaggio/avviso agli studenti iscritti a un appello specifico, permettendo l'invio sia all'intera lista sia a un sottoinsieme selezionato. + parameters: + - name: examId + in: path + required: true + schema: + type: integer + responses: + '202': + description: The request has been accepted for processing, but processing has not yet completed. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/AcceptedData' + required: + - data + example: + data: + status: accepted + trackingId: track-abc-123 + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExamNotificationRequest' + /exams/{examId}/students: + get: + operationId: FacultyExams_listStudentsInExam + summary: List students in exam | Elenca studenti nell'esame + description: Restituisce la lista degli studenti iscritti a un appello specifico. + parameters: + - name: examId + in: path + required: true + schema: + type: integer + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ExamAttendee' + required: + - data + example: + data: + - username: '290683' + name: Mario + surname: Rossi + needsExtraTime: true + extraTimeMinutes: 30 + isPresent: false + - username: '291045' + name: Laura + surname: Bianchi + needsExtraTime: false + extraTimeMinutes: null + isPresent: false + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + /exams/{examId}/students/{studentId}: + put: + operationId: FacultyExams_addStudentToExam + summary: Add student to exam | Aggiungi studente all'esame + description: Inserisce manualmente uno studente tra gli iscritti a un appello (casi eccezionali). Operazione idempotente. + parameters: + - name: examId + in: path + required: true + schema: + type: integer + - name: studentId + in: path + required: true + schema: + type: integer + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ExamEnrollment' + required: + - data + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ExamEnrollment' + required: + - data + example: + data: + username: '292145' + name: Anna + surname: Ferrari + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + get: + operationId: FacultyExams_checkStudentEnrollment + summary: Check if student registration for the exam is possible | Verifica se possibile l'iscrizione studente all'esame + parameters: + - name: examId + in: path + required: true + schema: + type: integer + - name: studentId + in: path + required: true + schema: + type: integer + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams + post: + operationId: FacultyExams_enrollStudentToExam + summary: Enroll student to exam | Iscrivi studente all'esame + description: Iscrive uno studente a un appello specifico. Operazione + parameters: + - name: examId + in: path + required: true + schema: + type: integer + - name: studentId + in: path + required: true + schema: + type: integer + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '401': + description: Access is unauthorized. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: The server cannot find the requested resource. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + tags: + - Faculty - Exams /lectures: get: operationId: Lectures_getLectures @@ -2860,6 +3234,18 @@ security: - BearerAuth: [] components: schemas: + AcceptedData: + type: object + required: + - status + - trackingId + properties: + status: + type: string + example: accepted + trackingId: + type: string + example: track-abc-123 Announcement: type: object required: @@ -3366,6 +3752,19 @@ components: $ref: '#/components/schemas/PlaceCategoryOverview' geoJson: $ref: '#/components/schemas/GeoJsonPolygon' + CheckAttendanceRequest: + type: object + required: + - presentUsernames + properties: + presentUsernames: + type: array + items: + type: string + description: Matricole degli studenti risultati presenti + example: + - '290683' + - '291045' Client: type: object required: @@ -3935,6 +4334,114 @@ components: remainingAdmissions: type: integer example: 8 + ExamAttendee: + type: object + required: + - needsExtraTime + - extraTimeMinutes + - isPresent + properties: + needsExtraTime: + type: boolean + description: Se lo studente ha diritto a tempo aggiuntivo (DSA/BES) + example: true + extraTimeMinutes: + type: integer + nullable: true + description: Minuti aggiuntivi concessi, null se non applicabile + example: 30 + isPresent: + type: boolean + description: Se lo studente risulta presente all'esame + example: false + allOf: + - $ref: '#/components/schemas/ExamEnrollment' + ExamBase: + type: object + required: + - id + - courseId + - courseShortcode + - courseName + - type + - bookingEndsAt + - examStartsAt + - examEndsAt + - bookedCount + - availableCount + properties: + id: + type: integer + courseId: + type: number + courseShortcode: + type: string + courseName: + type: string + type: + type: string + places: + type: array + items: + $ref: '#/components/schemas/PlaceRef' + bookingEndsAt: + type: string + format: date-time + examStartsAt: + type: string + format: date-time + nullable: true + examEndsAt: + type: string + format: date-time + nullable: true + bookedCount: + type: integer + availableCount: + type: integer + ExamEnrollment: + type: object + required: + - username + - name + - surname + properties: + username: + type: string + example: '290683' + name: + type: string + example: Mario + surname: + type: string + example: Rossi + ExamNotificationRequest: + type: object + required: + - subject + - message + - channels + properties: + subject: + type: string + example: 'Importante: modifica orario esame' + message: + type: string + example: Si comunica che l'orario dell'esame è stato spostato alle ore 14:00. + channels: + type: array + items: + type: string + example: + - email + - sms + recipientPersonIds: + type: array + items: + type: string + example: + - '290683' + - '291045' FcmRegistrationRequest: type: object properties: diff --git a/dist/clients/student/openapi.yaml b/dist/clients/student/openapi.yaml index c63f2c0..c792b9f 100644 --- a/dist/clients/student/openapi.yaml +++ b/dist/clients/student/openapi.yaml @@ -5642,19 +5642,9 @@ components: Exam: type: object required: - - id - - courseId - - courseShortcode - - courseName - teacherId - - type - status - bookingStartsAt - - bookingEndsAt - - examStartsAt - - examEndsAt - - bookedCount - - availableCount - question - feedback - isReschedulable @@ -5663,51 +5653,18 @@ components: - requestReason - requestDetails properties: - id: - type: integer - courseId: - type: number - courseShortcode: - type: string - courseName: - type: string teacherId: type: integer - type: - type: string places: type: array items: $ref: '#/components/schemas/PlaceRef' status: - type: string - enum: - - available - - booked - - requestable - - requested - - requestAccepted - - requestRejected - - unavailable + $ref: '#/components/schemas/ExamStatus' bookingStartsAt: type: string format: date-time nullable: true - bookingEndsAt: - type: string - format: date-time - examStartsAt: - type: string - format: date-time - nullable: true - examEndsAt: - type: string - format: date-time - nullable: true - bookedCount: - type: integer - availableCount: - type: integer question: type: object properties: @@ -5742,6 +5699,51 @@ components: requestDetails: type: string nullable: true + allOf: + - $ref: '#/components/schemas/ExamBase' + ExamBase: + type: object + required: + - id + - courseId + - courseShortcode + - courseName + - type + - bookingEndsAt + - examStartsAt + - examEndsAt + - bookedCount + - availableCount + properties: + id: + type: integer + courseId: + type: number + courseShortcode: + type: string + courseName: + type: string + type: + type: string + places: + type: array + items: + $ref: '#/components/schemas/PlaceRef' + bookingEndsAt: + type: string + format: date-time + examStartsAt: + type: string + format: date-time + nullable: true + examEndsAt: + type: string + format: date-time + nullable: true + bookedCount: + type: integer + availableCount: + type: integer ExamGrade: type: object required: @@ -5787,6 +5789,16 @@ components: type: boolean description: Defines whether the credits from the exam count toward the total required for graduation example: true + ExamStatus: + type: string + enum: + - available + - booked + - requestable + - requested + - requestAccepted + - requestRejected + - unavailable FcmRegistrationRequest: type: object properties: diff --git a/src/clients/faculty/main.tsp b/src/clients/faculty/main.tsp index 2e2e803..3dbe431 100644 --- a/src/clients/faculty/main.tsp +++ b/src/clients/faculty/main.tsp @@ -14,6 +14,7 @@ import "../../routes/common/people.tsp"; import "../../routes/common/places.tsp"; // Faculty routes +import "../../routes/faculty/exams.tsp"; import "./version.tsp"; diff --git a/src/common.tsp b/src/common.tsp index 0b7dec4..3aec90a 100644 --- a/src/common.tsp +++ b/src/common.tsp @@ -85,6 +85,11 @@ alias NoContentResponse = { @statusCode statusCode: 204; }; +alias AcceptedResponse = { + @statusCode statusCode: 202; + @body body: T; +}; + alias RedirectResponse = { @statusCode statusCode: 302; }; @@ -99,6 +104,11 @@ alias NotFound = { @body body: ErrorResponse; }; +alias NotAuthorized = { + @statusCode statusCode: 401; + @body body: ErrorResponse; +}; + alias ServerError = { @statusCode statusCode: 500; @body body: ErrorResponse; diff --git a/src/examples/faculty/exams.tsp b/src/examples/faculty/exams.tsp new file mode 100644 index 0000000..537d362 --- /dev/null +++ b/src/examples/faculty/exams.tsp @@ -0,0 +1,123 @@ +import "@typespec/http"; + +using Http; + +namespace PolitoAPI; + +// List exams example +const _ex_getExams_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + id: 888122, + courseId: 262147, + courseShortcode: "02JSKOV", + courseName: "Human Computer Interaction", + type: "Esami scritti a risposta aperta o chiusa tramite PC", + places: #[ + #{ + buildingId: "TO_CIT22", + floorId: "XPTE", + name: "Aula 1P", + roomId: "30", + siteId: "TO_CIT", + }, + ], + bookingEndsAt: utcDateTime.fromISO("2026-08-31T14:00:00Z"), + examStartsAt: utcDateTime.fromISO("2026-09-02T14:00:00Z"), + examEndsAt: utcDateTime.fromISO("2026-09-02T17:00:00Z"), + bookedCount: 42, + availableCount: 8, + }, + ], + }, + }, +}; + +// List students in exam example (with attendance + extra time info) +const _ex_listStudentsInExam_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + username: "290683", + name: "Mario", + surname: "Rossi", + needsExtraTime: true, + extraTimeMinutes: 30, + isPresent: false, + }, + #{ + username: "291045", + name: "Laura", + surname: "Bianchi", + needsExtraTime: false, + extraTimeMinutes: null, + isPresent: false, + }, + ], + }, + }, +}; + +// Check exam attendance example +const _ex_checkAttendance_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + username: "290683", + name: "Mario", + surname: "Rossi", + needsExtraTime: true, + extraTimeMinutes: 30, + isPresent: true, + }, + #{ + username: "291045", + name: "Laura", + surname: "Bianchi", + needsExtraTime: false, + extraTimeMinutes: null, + isPresent: true, + }, + ], + }, + }, +}; + +// Notify exam subscribers example +const _ex_notifyExamSubscribers_resp = #{ + returnType: #{ + statusCode: 202, + body: #{ + data: #{ + status: "accepted", + trackingId: "track-abc-123", + }, + }, + }, +}; + +// Add student to exam example (201 - created) +const _ex_addStudentToExam_resp = #{ + returnType: #{ + statusCode: 201, + body: #{ + data: #{ + username: "292145", + name: "Anna", + surname: "Ferrari", + }, + }, + }, +}; + +// Enroll / check student enrollment example (204 - no content) +const _ex_enrollStudentToExam_resp = #{ + returnType: #{ statusCode: 204 }, +}; diff --git a/src/routes/common/exams.tsp b/src/routes/common/exams.tsp new file mode 100644 index 0000000..bb10157 --- /dev/null +++ b/src/routes/common/exams.tsp @@ -0,0 +1,22 @@ +import "@typespec/http"; +import "@typespec/openapi3"; + +import "../../common.tsp"; + +using Http; + +namespace PolitoAPI; + +model ExamBase { + id: integer; + courseId: numeric; + courseShortcode: string; + courseName: string; + type: string; + places?: PlaceRef[]; + bookingEndsAt: utcDateTime; + examStartsAt: utcDateTime | null; + examEndsAt: utcDateTime | null; + bookedCount: integer; + availableCount: integer; +} diff --git a/src/routes/faculty/exams.tsp b/src/routes/faculty/exams.tsp new file mode 100644 index 0000000..4fcae4f --- /dev/null +++ b/src/routes/faculty/exams.tsp @@ -0,0 +1,143 @@ +import "@typespec/http"; +import "@typespec/openapi3"; + +import "../../common.tsp"; +import "../../routes/common/exams.tsp"; +import "../../examples/faculty/exams.tsp"; + +using Http; + +namespace PolitoAPI; + +model ExamEnrollment { + @example("290683") + username: string; + + @example("Mario") + name: string; + + @example("Rossi") + surname: string; +} + +model ExamAttendee extends ExamEnrollment { + /** Se lo studente ha diritto a tempo aggiuntivo (DSA/BES) */ + @example(true) + needsExtraTime: boolean; + + /** Minuti aggiuntivi concessi, null se non applicabile */ + @example(30) + extraTimeMinutes: integer | null; + + /** Se lo studente risulta presente all'esame */ + @example(false) + isPresent: boolean; +} + +model CheckAttendanceRequest { + /** Matricole degli studenti risultati presenti */ + @example(#["290683", "291045"]) + presentUsernames: string[]; +} + +model ExamNotificationRequest { + @example("Importante: modifica orario esame") + subject: string; + + @example("Si comunica che l'orario dell'esame è stato spostato alle ore 14:00.") + message: string; + + @example(#["email", "sms"]) + channels: string[]; + + @example(#["290683", "291045"]) + recipientPersonIds?: string[]; +} + +model AcceptedData { + @example("accepted") + status: string; + + @example("track-abc-123") + trackingId: string; +} + +@tag("Faculty - Exams") +@route("/exams") +interface FacultyExams { + @get + @summary("List exams | Elenca esami") + @opExample(_ex_getExams_resp) + getExams(): OkDataResponse | BaseErrors; + + @route("/{examId}/students") + @get + @summary("List students in exam | Elenca studenti nell'esame") + @doc("Restituisce la lista degli studenti iscritti a un appello specifico.") + @opExample(_ex_listStudentsInExam_resp) + listStudentsInExam(@path examId: integer): + | OkDataResponse + | NotAuthorized + | NotFound + | ServerError; + + @route("/{examId}/attendance") + @post + @summary("Check exam attendance | Verifica presenze all'esame") + @doc("Registra le presenze passando l'array delle matricole presenti. Restituisce lo stato aggiornato delle presenze.") + @opExample(_ex_checkAttendance_resp) + checkAttendance( + @path examId: integer, + @body body: CheckAttendanceRequest, + ): + | OkDataResponse + | NotAuthorized + | NotFound + | ServerError; + + @route("/{examId}/notify") + @post + @summary("Notify exam subscribers | Invia comunicazione agli iscritti all'esame") + @doc("Invia un messaggio/avviso agli studenti iscritti a un appello specifico, permettendo l'invio sia all'intera lista sia a un sottoinsieme selezionato.") + @opExample(_ex_notifyExamSubscribers_resp) + notifyExamSubscribers( + @path examId: integer, + @body body: ExamNotificationRequest, + ): AcceptedResponse<{ + data: AcceptedData; + }> | NotAuthorized | BaseErrors; + + @route("/{examId}/students/{studentId}") + @put + @summary("Add student to exam | Aggiungi studente all'esame") + @doc("Inserisce manualmente uno studente tra gli iscritti a un appello (casi eccezionali). Operazione idempotente.") + @opExample(_ex_addStudentToExam_resp) + addStudentToExam(@path examId: integer, @path studentId: integer): + | OkDataResponse + | OkDataCreatedResponse + | NotAuthorized + | NotFound + | ServerError; + + @route("/{examId}/students/{studentId}") + @get + @summary("Check if student registration for the exam is possible | Verifica se possibile l'iscrizione studente all'esame") + @opExample(_ex_enrollStudentToExam_resp) + checkStudentEnrollment(@path examId: integer, @path studentId: integer): + | NoContentResponse + | ErrorResponse + | NotAuthorized + | NotFound + | ServerError; + + @route("/{examId}/students/{studentId}") + @post + @summary("Enroll student to exam | Iscrivi studente all'esame") + @doc("Iscrive uno studente a un appello specifico. Operazione") + @opExample(_ex_enrollStudentToExam_resp) + enrollStudentToExam(@path examId: integer, @path studentId: integer): + | NoContentResponse + | NotAuthorized + | NotFound + | ServerError; +} diff --git a/src/routes/student/exams.tsp b/src/routes/student/exams.tsp index 28c1f78..02f3b57 100644 --- a/src/routes/student/exams.tsp +++ b/src/routes/student/exams.tsp @@ -2,34 +2,28 @@ import "@typespec/http"; import "@typespec/openapi3"; import "../../common.tsp"; +import "../../routes/common/exams.tsp"; import "../../examples/student/exams.tsp"; using Http; namespace PolitoAPI; -model Exam { - id: integer; - courseId: numeric; - courseShortcode: string; - courseName: string; +union ExamStatus { + available: "available", + booked: "booked", + requestable: "requestable", + requested: "requested", + requestAccepted: "requestAccepted", + requestRejected: "requestRejected", + unavailable: "unavailable", +} + +model Exam extends ExamBase { teacherId: integer; - type: string; places?: PlaceRef[]; - status: - | "available" - | "booked" - | "requestable" - | "requested" - | "requestAccepted" - | "requestRejected" - | "unavailable"; + status: ExamStatus; bookingStartsAt: utcDateTime | null; - bookingEndsAt: utcDateTime; - examStartsAt: utcDateTime | null; - examEndsAt: utcDateTime | null; - bookedCount: integer; - availableCount: integer; question: { id: integer; statement: string;