From cd4f9597abac06031c7cef3778287697ca60e57a Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Tue, 17 Feb 2026 17:41:49 +0100 Subject: [PATCH 01/10] feat(courses): add faculty courses models and endpoints --- src/common.tsp | 12 +++ src/examples/faculty/courses.tsp | 47 +++++++++ src/routes/faculty/courses.tsp | 166 +++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 src/examples/faculty/courses.tsp create mode 100644 src/routes/faculty/courses.tsp diff --git a/src/common.tsp b/src/common.tsp index 0b7dec4..024683e 100644 --- a/src/common.tsp +++ b/src/common.tsp @@ -85,6 +85,13 @@ alias NoContentResponse = { @statusCode statusCode: 204; }; +alias AcceptedResponse = { + @statusCode statusCode: 202; + @body body: { + message?: string; + }; +}; + alias RedirectResponse = { @statusCode statusCode: 302; }; @@ -94,6 +101,11 @@ alias BadRequest = { @body body: ErrorResponse; }; +alias NotAuthorized = { + @statusCode statusCode: 401; + @body body: ErrorResponse; +}; + alias NotFound = { @statusCode statusCode: 404; @body body: ErrorResponse; diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp new file mode 100644 index 0000000..3d3cb28 --- /dev/null +++ b/src/examples/faculty/courses.tsp @@ -0,0 +1,47 @@ +const _ex_listFacultyCourseStudents_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + id: "s123456", + firstName: "Mario", + lastName: "Rossi", + username: "s123456", + picture: #{ url: "https://didattica.polito.it/photos/s123456.jpg" }, + }, + #{ + id: "s234567", + firstName: "Luigi", + lastName: "Verdi", + username: "s234567", + picture: #{ url: "https://didattica.polito.it/photos/s234567.jpg" }, + } + ], + }, + }, +}; + +const _ex_listAssignmentCollaborators_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + personId: "p002", + name: "Luigi Verdi", + role: "Teaching Assistant", + courses: #["251007", "252121"], + permissions: #["CAN_READ", "CAN_EDIT"], + }, + #{ + personId: "p003", + name: "Anna Bianchi", + role: "Tutor", + courses: #["251007"], + permissions: #["CAN_READ"], + } + ], + }, + }, +}; diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp new file mode 100644 index 0000000..603e67a --- /dev/null +++ b/src/routes/faculty/courses.tsp @@ -0,0 +1,166 @@ +import "@typespec/http"; +import "@typespec/openapi3"; + +import "../../common.tsp"; +import "../../examples/faculty/courses.tsp"; + +using Http; + +namespace PolitoAPI; + +// Models + +model FacultyCourseStudent { + @example("s123456") + id: string; + + @example("Mario") + firstName: string; + + @example("Rossi") + lastName: string; + + @example("s123456") + username: string; + + picture?: { + @example("https://example.com/picture.jpg") + url: url; + }; +} + +model Collaborator { + @example("p002") + personId: string; + + @example("Luigi Verdi") + name: string; + + @example("Tutor") + role: string; + + sections?: string[]; + permissions: CollaboratorPermission[]; +} + +enum CollaboratorPermission { + CAN_READ, + CAN_EDIT, + CAN_DELETE, +} + +model CollaboratorUpsertRequest { + @example("Tutor") + role?: string; + + sections: string[]; + permissions: CollaboratorPermission[]; +} + +model CourseMessageRequest { + @example("Avviso importante") + subject: string; + + @example("Messaggio importante") + message: string; + + channels: string[]; + audience: { + groups: string[]; + personIds: string[]; + }; +} + +// Response aliases with descriptions + +@doc("Studente già iscritto") +model StudentAlreadyEnrolledResponse { + ...OkDataResponse; +} + +@doc("Updated collaborator") +model CollaboratorUpdatedResponse { + ...OkDataResponse; +} + +@doc("Created collaborator") +model CollaboratorCreatedResponse { + ...OkDataCreatedResponse; +} + +@doc("Removed collaborator") +model CollaboratorRemovedResponse { + ...NoContentResponse; +} + +@doc("Accepted (sending)") +model CourseMessageAcceptedResponse { + ...AcceptedResponse; +} + +@tag("Faculty - Courses") +@route("/faculty/courses/{courseId}") +interface FacultyCourses { + @get + @route("/students") + @summary("Elenca studenti iscritti al corso") + @opExample(_ex_listFacultyCourseStudents_resp) + listFacultyCourseStudents(@path courseId: integer): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @put + @route("/students/{studentId}") + @summary("Aggiungi studente al corso") + @doc("Operazione idempotente per iscrivere uno studente al corso.") + enrollStudentInCourse(@path courseId: integer, @path studentId: string): + | OkDataResponse + | OkDataCreatedResponse + | NotAuthorized + | BadRequest + | NotFound + | BaseErrors; + + @get + @route("/collaborators") + @summary("Elenca collaboratori assegnati al corso") + listAssignmentCollaborators(@path courseId: integer): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @put + @route("/collaborators/{collaboratorId}") + @summary("Aggiungi o aggiorna collaboratore") + @doc("Operazione idempotente per creare o aggiornare un collaboratore sul corso. Se il collaboratore esiste già, i suoi attributi vengono aggiornati.") + upsertAssignmentCollaborator( + @path courseId: integer, + @path collaboratorId: string, + @body body: CollaboratorUpsertRequest, + ): + | CollaboratorUpdatedResponse + | CollaboratorCreatedResponse + | BadRequest + | NotAuthorized + | NotFound + | BaseErrors; + + @delete + @route("/collaborators/{collaboratorId}") + @summary("Rimuovi collaboratore da un incarico") + removeAssignmentCollaborator( + @path courseId: integer, + @path collaboratorId: string, + ): CollaboratorRemovedResponse | NotAuthorized | NotFound | BaseErrors; + + @post + @route("/message") + @summary("Invia comunicazione agli iscritti al corso") + messageCourseSubscribers( + @path courseId: integer, + @body body: CourseMessageRequest, + ): CourseMessageAcceptedResponse | NotAuthorized | BadRequest | BaseErrors; +} From c89f78fecddf7baac6dd37b5149d36747fc2210e Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Wed, 27 May 2026 11:58:42 +0200 Subject: [PATCH 02/10] feat: remove collaborators from courses --- src/examples/faculty/courses.tsp | 24 ---------- src/routes/faculty/courses.tsp | 76 -------------------------------- 2 files changed, 100 deletions(-) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 3d3cb28..98942ba 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -21,27 +21,3 @@ const _ex_listFacultyCourseStudents_resp = #{ }, }, }; - -const _ex_listAssignmentCollaborators_resp = #{ - returnType: #{ - statusCode: 200, - body: #{ - data: #[ - #{ - personId: "p002", - name: "Luigi Verdi", - role: "Teaching Assistant", - courses: #["251007", "252121"], - permissions: #["CAN_READ", "CAN_EDIT"], - }, - #{ - personId: "p003", - name: "Anna Bianchi", - role: "Tutor", - courses: #["251007"], - permissions: #["CAN_READ"], - } - ], - }, - }, -}; diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index 603e67a..37bdb33 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -29,34 +29,6 @@ model FacultyCourseStudent { }; } -model Collaborator { - @example("p002") - personId: string; - - @example("Luigi Verdi") - name: string; - - @example("Tutor") - role: string; - - sections?: string[]; - permissions: CollaboratorPermission[]; -} - -enum CollaboratorPermission { - CAN_READ, - CAN_EDIT, - CAN_DELETE, -} - -model CollaboratorUpsertRequest { - @example("Tutor") - role?: string; - - sections: string[]; - permissions: CollaboratorPermission[]; -} - model CourseMessageRequest { @example("Avviso importante") subject: string; @@ -78,21 +50,6 @@ model StudentAlreadyEnrolledResponse { ...OkDataResponse; } -@doc("Updated collaborator") -model CollaboratorUpdatedResponse { - ...OkDataResponse; -} - -@doc("Created collaborator") -model CollaboratorCreatedResponse { - ...OkDataCreatedResponse; -} - -@doc("Removed collaborator") -model CollaboratorRemovedResponse { - ...NoContentResponse; -} - @doc("Accepted (sending)") model CourseMessageAcceptedResponse { ...AcceptedResponse; @@ -123,39 +80,6 @@ interface FacultyCourses { | NotFound | BaseErrors; - @get - @route("/collaborators") - @summary("Elenca collaboratori assegnati al corso") - listAssignmentCollaborators(@path courseId: integer): - | OkDataResponse - | NotAuthorized - | NotFound - | BaseErrors; - - @put - @route("/collaborators/{collaboratorId}") - @summary("Aggiungi o aggiorna collaboratore") - @doc("Operazione idempotente per creare o aggiornare un collaboratore sul corso. Se il collaboratore esiste già, i suoi attributi vengono aggiornati.") - upsertAssignmentCollaborator( - @path courseId: integer, - @path collaboratorId: string, - @body body: CollaboratorUpsertRequest, - ): - | CollaboratorUpdatedResponse - | CollaboratorCreatedResponse - | BadRequest - | NotAuthorized - | NotFound - | BaseErrors; - - @delete - @route("/collaborators/{collaboratorId}") - @summary("Rimuovi collaboratore da un incarico") - removeAssignmentCollaborator( - @path courseId: integer, - @path collaboratorId: string, - ): CollaboratorRemovedResponse | NotAuthorized | NotFound | BaseErrors; - @post @route("/message") @summary("Invia comunicazione agli iscritti al corso") From 31478a57629e623e5003f5cc3d266bbf3d4e9784 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 31 May 2026 15:54:16 +0200 Subject: [PATCH 03/10] feat: add publish noticies and delete noticies --- dist/clients/faculty/openapi.yaml | 43 +++++++++++++++++++++---------- dist/clients/student/openapi.yaml | 12 +++++++++ src/examples/common/courses.tsp | 1 + src/examples/faculty/courses.tsp | 19 ++++++++++++++ src/routes/common/courses.tsp | 9 +++++++ src/routes/faculty/courses.tsp | 33 ++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 14 deletions(-) diff --git a/dist/clients/faculty/openapi.yaml b/dist/clients/faculty/openapi.yaml index 3a177ed..719fee2 100644 --- a/dist/clients/faculty/openapi.yaml +++ b/dist/clients/faculty/openapi.yaml @@ -1197,6 +1197,7 @@ paths: publishedAt: '2021-09-22T14:00:00Z' expiresAt: null content: "

Dear students,

\r\r

welcome to the 2021 edition of the Human Computer Interaction course (HCI, for short)!

\r\r

Some useful information to get started...

\r\r

The first class will be on Tuesday, September 28, in Room 7T, from 13:00 to 14:30.
\rDon't forget to book a spot in the room, starting from tomorrow, on the Portale della Didattica: the rooms given to this course are currently bigger than the number of enrolled students, so there should be space for everybody!

\r\r

All teaching material, information, and course schedule will be posted on the page: http://bit.ly/polito-hci (we will not use the Portale della Didattica).

\r\r

All messages and communications with the teachers, and among students, will be on Slack. We will completely avoid email communications.
\rPlease, join the HCI Slack workspace at the address:
\r  https://join.slack.com/t/polito-hci-2021/signup
\rPlease note: to have access to the workspace, you must use your @studenti.polito.it email address. You are free to choose your nickname as you prefer. 

\r\r

Finally, all lectures — not labs — will be video-recorded and made available both on YouTube and on the Portale della Didattica. The YouTube playlist is:
\r  https://www.youtube.com/playlist?list=PLs7DWGc_wmwT-1N2vbRkLWrM6LIker9A-

\r\r

See you on Tuesday!

\r\r

Thanks,

\r\r

Luigi and Fulvio

\r" + status: visible '400': description: The server could not understand the request due to invalid syntax. content: @@ -3447,7 +3448,6 @@ components: - teachingPeriod - teacherId - teacherName - - previousEditions - isOverBooking - enrollmentRole - year @@ -3477,14 +3477,6 @@ components: type: string nullable: true example: Mario Rossi - previousEditions: - type: array - items: - $ref: '#/components/schemas/CourseModuleEdition' - description: Previous editions of this course that were part of the student's PSP - example: - - id: 244577 - year: '2021' isOverBooking: type: boolean example: false @@ -3503,9 +3495,6 @@ components: teachingPeriod: 2-2 teacherId: 3001 teacherName: Mario Rossi - previousEditions: - - id: 251005 - year: '2024' isOverBooking: false enrollmentRole: student year: '2025' @@ -3521,6 +3510,27 @@ components: id: type: integer example: 244577 + CourseModuleOverview: + type: object + required: + - previousEditions + properties: + previousEditions: + type: array + items: + $ref: '#/components/schemas/CourseModuleEdition' + description: Previous editions of this course that were part of the student's PSP + example: + - id: 244577 + year: '2021' + allOf: + - $ref: '#/components/schemas/CourseModule' + CourseNoticeStatus: + type: string + enum: + - visible + - draft + - expired CourseNotices: type: object required: @@ -3528,6 +3538,7 @@ components: - publishedAt - expiresAt - content + - status properties: id: type: integer @@ -3543,6 +3554,10 @@ components: content: type: string example:

Conferma spostamento orario esame:

Ore 15

+ status: + allOf: + - $ref: '#/components/schemas/CourseNoticeStatus' + example: visible CourseOverview: type: object required: @@ -3555,10 +3570,10 @@ components: modules: type: array items: - $ref: '#/components/schemas/CourseModule' + $ref: '#/components/schemas/CourseModuleOverview' nullable: true allOf: - - $ref: '#/components/schemas/CourseModule' + - $ref: '#/components/schemas/CourseModuleOverview' CoursePreferencesRequest: type: object properties: diff --git a/dist/clients/student/openapi.yaml b/dist/clients/student/openapi.yaml index 36c8ae2..7516e2d 100644 --- a/dist/clients/student/openapi.yaml +++ b/dist/clients/student/openapi.yaml @@ -1204,6 +1204,7 @@ paths: publishedAt: '2021-09-22T14:00:00Z' expiresAt: null content: "

Dear students,

\r\r

welcome to the 2021 edition of the Human Computer Interaction course (HCI, for short)!

\r\r

Some useful information to get started...

\r\r

The first class will be on Tuesday, September 28, in Room 7T, from 13:00 to 14:30.
\rDon't forget to book a spot in the room, starting from tomorrow, on the Portale della Didattica: the rooms given to this course are currently bigger than the number of enrolled students, so there should be space for everybody!

\r\r

All teaching material, information, and course schedule will be posted on the page: http://bit.ly/polito-hci (we will not use the Portale della Didattica).

\r\r

All messages and communications with the teachers, and among students, will be on Slack. We will completely avoid email communications.
\rPlease, join the HCI Slack workspace at the address:
\r  https://join.slack.com/t/polito-hci-2021/signup
\rPlease note: to have access to the workspace, you must use your @studenti.polito.it email address. You are free to choose your nickname as you prefer. 

\r\r

Finally, all lectures — not labs — will be video-recorded and made available both on YouTube and on the Portale della Didattica. The YouTube playlist is:
\r  https://www.youtube.com/playlist?list=PLs7DWGc_wmwT-1N2vbRkLWrM6LIker9A-

\r\r

See you on Tuesday!

\r\r

Thanks,

\r\r

Luigi and Fulvio

\r" + status: visible '400': description: The server could not understand the request due to invalid syntax. content: @@ -5031,6 +5032,12 @@ components: year: '2021' allOf: - $ref: '#/components/schemas/CourseModule' + CourseNoticeStatus: + type: string + enum: + - visible + - draft + - expired CourseNotices: type: object required: @@ -5038,6 +5045,7 @@ components: - publishedAt - expiresAt - content + - status properties: id: type: integer @@ -5053,6 +5061,10 @@ components: content: type: string example:

Conferma spostamento orario esame:

Ore 15

+ status: + allOf: + - $ref: '#/components/schemas/CourseNoticeStatus' + example: visible CourseOverview: type: object required: diff --git a/src/examples/common/courses.tsp b/src/examples/common/courses.tsp index a3ee7d9..3e55548 100644 --- a/src/examples/common/courses.tsp +++ b/src/examples/common/courses.tsp @@ -1259,6 +1259,7 @@ const _ex_getCourseNotices_resp = #{ publishedAt: utcDateTime.fromISO("2021-09-22T14:00:00Z"), expiresAt: null, content: "

Dear students,

\r\r

welcome to the 2021 edition of the Human Computer Interaction course (HCI, for short)!

\r\r

Some useful information to get started...

\r\r

The first class will be on Tuesday, September 28, in Room 7T, from 13:00 to 14:30.
\rDon't forget to book a spot in the room, starting from tomorrow, on the Portale della Didattica: the rooms given to this course are currently bigger than the number of enrolled students, so there should be space for everybody!

\r\r

All teaching material, information, and course schedule will be posted on the page: http://bit.ly/polito-hci (we will not use the Portale della Didattica).

\r\r

All messages and communications with the teachers, and among students, will be on Slack. We will completely avoid email communications.
\rPlease, join the HCI Slack workspace at the address:
\r  https://join.slack.com/t/polito-hci-2021/signup
\rPlease note: to have access to the workspace, you must use your @studenti.polito.it email address. You are free to choose your nickname as you prefer. 

\r\r

Finally, all lectures — not labs — will be video-recorded and made available both on YouTube and on the Portale della Didattica. The YouTube playlist is:
\r  https://www.youtube.com/playlist?list=PLs7DWGc_wmwT-1N2vbRkLWrM6LIker9A-

\r\r

See you on Tuesday!

\r\r

Thanks,

\r\r

Luigi and Fulvio

\r", + status: CourseNoticeStatus.visible, } ], }, diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 98942ba..29590e9 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -1,3 +1,22 @@ +const _ex_publishCourseNotice_req = #{ + parameters: #{ + courseId: 251007, + isDraft: false, + body: #{ + content: "

Lezione di domani spostata in aula 7T.

", + publishedDate: utcDateTime.fromISO("2025-10-14T09:00:00Z"), + expiresAt: utcDateTime.fromISO("2025-10-20T23:59:59Z"), + isVisible: true, + }, + }, + returnType: #{ statusCode: 204 }, +}; + +const _ex_deleteCourseNotice_resp = #{ + parameters: #{ courseId: 251007, noticeId: 360724 }, + returnType: #{ statusCode: 204 }, +}; + const _ex_listFacultyCourseStudents_resp = #{ returnType: #{ statusCode: 200, diff --git a/src/routes/common/courses.tsp b/src/routes/common/courses.tsp index 3b85e77..118c95c 100644 --- a/src/routes/common/courses.tsp +++ b/src/routes/common/courses.tsp @@ -32,6 +32,12 @@ enum CourseEnrollmentRole { collaborator: "collaborator", } +enum CourseNoticeStatus { + visible: "visible", + draft: "draft", + expired: "expired", +} + @example(_ex_course_module) model CourseModule { /** The identifier for the current instance of this course. @@ -254,6 +260,9 @@ model CourseNotices { @example("

Conferma spostamento orario esame:

Ore 15

") content: string; + + @example(CourseNoticeStatus.visible) + status: CourseNoticeStatus; } model CoursePreferencesRequest { diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index 37bdb33..d2f5eaa 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -2,6 +2,7 @@ import "@typespec/http"; import "@typespec/openapi3"; import "../../common.tsp"; +import "../common/courses.tsp"; import "../../examples/faculty/courses.tsp"; using Http; @@ -43,6 +44,19 @@ model CourseMessageRequest { }; } +model CourseNoticeRequest { + @example("Contenuto dell'avviso") + content: string; + + @example(utcDateTime.fromISO("2022-07-03T14:00:00Z")) + publishedDate: utcDateTime; + + expiresAt: utcDateTime | null; + + @example(true) + isVisible: boolean; +} + // Response aliases with descriptions @doc("Studente già iscritto") @@ -58,6 +72,25 @@ model CourseMessageAcceptedResponse { @tag("Faculty - Courses") @route("/faculty/courses/{courseId}") interface FacultyCourses { + @post + @route("/notices") + @summary("Publish notices | Pubblica avvisi") + @opExample(_ex_publishCourseNotice_req) + publishCourseNotice( + @path courseId: integer, + @path isDraft: boolean, + @body body: CourseNoticeRequest, + ): NoContentResponse | BaseErrors; + + @delete + @route("/notices/{noticeId}") + @summary("Delete notice | Elimina avviso") + @opExample(_ex_deleteCourseNotice_resp) + deleteCourseNotice( + @path courseId: integer, + @path noticeId: integer, + ): NoContentResponse | BaseErrors; + @get @route("/students") @summary("Elenca studenti iscritti al corso") From 63cd32d942f55ea664cbc074572ebdcec8b6ebe7 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 00:24:02 +0200 Subject: [PATCH 04/10] feat(courses): update CourseNoticeRequest model and add updateCourseNotice endpoint --- src/examples/faculty/courses.tsp | 16 +++++++++++++++- src/routes/faculty/courses.tsp | 12 +++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 29590e9..2921f08 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -6,7 +6,21 @@ const _ex_publishCourseNotice_req = #{ content: "

Lezione di domani spostata in aula 7T.

", publishedDate: utcDateTime.fromISO("2025-10-14T09:00:00Z"), expiresAt: utcDateTime.fromISO("2025-10-20T23:59:59Z"), - isVisible: true, + isAlwaysVisible: true, + }, + }, + returnType: #{ statusCode: 204 }, +}; + +const _ex_updateCourseNotice_resp = #{ + parameters: #{ + courseId: 251007, + noticeId: 360724, + body: #{ + content: "

Aggiornamento: lezione spostata in aula 10T.

", + publishedDate: utcDateTime.fromISO("2025-10-14T09:00:00Z"), + expiresAt: utcDateTime.fromISO("2025-10-22T23:59:59Z"), + isAlwaysVisible: false, }, }, returnType: #{ statusCode: 204 }, diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index d2f5eaa..d410a55 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -54,7 +54,7 @@ model CourseNoticeRequest { expiresAt: utcDateTime | null; @example(true) - isVisible: boolean; + isAlwaysVisible: boolean; } // Response aliases with descriptions @@ -82,6 +82,16 @@ interface FacultyCourses { @body body: CourseNoticeRequest, ): NoContentResponse | BaseErrors; + @put + @route("/notices/{noticeId}") + @summary("Update notice | Modifica avviso") + @opExample(_ex_updateCourseNotice_resp) + updateCourseNotice( + @path courseId: integer, + @path noticeId: integer, + @body body: CourseNoticeRequest, + ): NoContentResponse | BaseErrors; + @delete @route("/notices/{noticeId}") @summary("Delete notice | Elimina avviso") From b727e0f924247510e774ba66691996831eb4a0fc Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 00:41:53 +0200 Subject: [PATCH 05/10] feat(courses): add file and folder management models and endpoints --- src/examples/faculty/courses.tsp | 67 ++++++++++++++++++ src/routes/faculty/courses.tsp | 112 +++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 2921f08..553021d 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -26,6 +26,73 @@ const _ex_updateCourseNotice_resp = #{ returnType: #{ statusCode: 204 }, }; +const _ex_createCourseFolder_resp = #{ + parameters: #{ + courseId: 251007, + body: #{ name: "Laboratori", parentId: null }, + }, + returnType: #{ + statusCode: 201, + body: #{ + data: #{ + type: "directory", + id: "33352562", + name: "Laboratori", + files: #[], + }, + }, + }, +}; + +const _ex_updateCourseFolder_resp = #{ + parameters: #{ + courseId: 251007, + folderId: "33352562", + body: #{ name: "Laboratori 2024" }, + }, + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + type: "directory", + id: "33352562", + name: "Laboratori 2024", + files: #[], + }, + }, + }, +}; + +const _ex_updateCourseFile_resp = #{ + parameters: #{ + courseId: 251007, + fileId: "2", + body: #{ name: "Lecture1_rev.pdf", parentId: "33352562" }, + }, + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + type: "file", + id: "2", + name: "Lecture1_rev.pdf", + sizeInKiloBytes: 2048, + mimeType: "application/pdf", + createdAt: utcDateTime.fromISO("2024-06-01T10:00:00Z"), + checksum: "deadbeefcafebabe0000000000000000", + }, + }, + }, +}; + +const _ex_moveCourseItems_resp = #{ + parameters: #{ + courseId: 251007, + body: #{ itemIds: #["2", "33352562"], destinationFolderId: "44463673" }, + }, + returnType: #{ statusCode: 204 }, +}; + const _ex_deleteCourseNotice_resp = #{ parameters: #{ courseId: 251007, noticeId: 360724 }, returnType: #{ statusCode: 204 }, diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index d410a55..66ebf58 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -57,6 +57,49 @@ model CourseNoticeRequest { isAlwaysVisible: boolean; } +// File and folder management requests + +model CreateCourseFolderRequest { + @example("Laboratori") + name: string; + + /** Parent folder. If omitted, the folder is created in the root. */ + parentId?: string | null; +} + +model UpdateCourseFolderRequest { + @example("Laboratori 2024") + name?: string; + + /** New parent folder to move the folder to. */ + parentId?: string | null; +} + +model CourseFileUpload { + /** Files to upload. Multiple files can be uploaded at once. */ + files: HttpPart[]; + + /** Destination folder. If omitted, files are uploaded to the root. */ + parentId?: HttpPart; +} + +model UpdateCourseFileRequest { + @example("Lecture1.pdf") + name?: string; + + /** New parent folder to move the file to. */ + parentId?: string | null; +} + +model MoveCourseItemsRequest { + /** IDs of files and/or folders to move. */ + @example(#["2", "33352562"]) + itemIds: string[]; + + /** Destination folder. `null` moves to the root. */ + destinationFolderId: string | null; +} + // Response aliases with descriptions @doc("Studente già iscritto") @@ -131,3 +174,72 @@ interface FacultyCourses { @body body: CourseMessageRequest, ): CourseMessageAcceptedResponse | NotAuthorized | BadRequest | BaseErrors; } + +@tag("Faculty - Course Files") +@route("/faculty/courses/{courseId}/files") +interface FacultyCourseFiles { + @post + @route("/folders") + @summary("Create folder | Crea cartella") + @opExample(_ex_createCourseFolder_resp) + createCourseFolder( + @path courseId: integer, + @body body: CreateCourseFolderRequest, + ): OkDataCreatedResponse | NotAuthorized | NotFound | BaseErrors; + + @patch(#{ implicitOptionality: false }) + @route("/folders/{folderId}") + @summary("Update folder | Modifica cartella") + @opExample(_ex_updateCourseFolder_resp) + updateCourseFolder( + @path courseId: integer, + @path folderId: string, + @body body: UpdateCourseFolderRequest, + ): OkDataResponse | NotAuthorized | NotFound | BaseErrors; + + @delete + @route("/folders/{folderId}") + @summary("Delete folder | Elimina cartella") + @doc("Elimina la cartella e ricorsivamente tutto il suo contenuto.") + deleteCourseFolder( + @path courseId: integer, + @path folderId: string, + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; + + @post + @summary("Upload files | Carica file") + @doc("Carica uno o più file nel corso. La richiesta è multipart/form-data e accetta più file contemporaneamente.") + uploadCourseFiles( + @path courseId: integer, + @header contentType: "multipart/form-data", + @multipartBody body: CourseFileUpload, + ): OkDataCreatedResponse | NotAuthorized | NotFound | BaseErrors; + + @post + @route("/move") + @summary("Move files | Sposta file") + @doc("Sposta uno o più file e/o cartelle in una cartella di destinazione.") + @opExample(_ex_moveCourseItems_resp) + moveCourseItems( + @path courseId: integer, + @body body: MoveCourseItemsRequest, + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; + + @patch(#{ implicitOptionality: false }) + @route("/{fileId}") + @summary("Update file | Modifica file") + @opExample(_ex_updateCourseFile_resp) + updateCourseFile( + @path courseId: integer, + @path fileId: string, + @body body: UpdateCourseFileRequest, + ): OkDataResponse | NotAuthorized | NotFound | BaseErrors; + + @delete + @route("/{fileId}") + @summary("Delete file | Elimina file") + deleteCourseFile( + @path courseId: integer, + @path fileId: string, + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; +} From 1871f042ae1fc1981e0e732293e6d8d961fb72ea Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 01:14:22 +0200 Subject: [PATCH 06/10] feat(courses): enhance faculty course models and add student statistics and enrollment features --- src/examples/faculty/courses.tsp | 139 +++++++++++++++----- src/routes/faculty/courses.tsp | 219 +++++++++++++++++++++++++------ 2 files changed, 284 insertions(+), 74 deletions(-) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 553021d..d095dee 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -31,17 +31,7 @@ const _ex_createCourseFolder_resp = #{ courseId: 251007, body: #{ name: "Laboratori", parentId: null }, }, - returnType: #{ - statusCode: 201, - body: #{ - data: #{ - type: "directory", - id: "33352562", - name: "Laboratori", - files: #[], - }, - }, - }, + returnType: #{ statusCode: 204 }, }; const _ex_updateCourseFolder_resp = #{ @@ -50,17 +40,7 @@ const _ex_updateCourseFolder_resp = #{ folderId: "33352562", body: #{ name: "Laboratori 2024" }, }, - returnType: #{ - statusCode: 200, - body: #{ - data: #{ - type: "directory", - id: "33352562", - name: "Laboratori 2024", - files: #[], - }, - }, - }, + returnType: #{ statusCode: 204 }, }; const _ex_updateCourseFile_resp = #{ @@ -69,20 +49,7 @@ const _ex_updateCourseFile_resp = #{ fileId: "2", body: #{ name: "Lecture1_rev.pdf", parentId: "33352562" }, }, - returnType: #{ - statusCode: 200, - body: #{ - data: #{ - type: "file", - id: "2", - name: "Lecture1_rev.pdf", - sizeInKiloBytes: 2048, - mimeType: "application/pdf", - createdAt: utcDateTime.fromISO("2024-06-01T10:00:00Z"), - checksum: "deadbeefcafebabe0000000000000000", - }, - }, - }, + returnType: #{ statusCode: 204 }, }; const _ex_moveCourseItems_resp = #{ @@ -108,6 +75,7 @@ const _ex_listFacultyCourseStudents_resp = #{ firstName: "Mario", lastName: "Rossi", username: "s123456", + email: "s123456@studenti.polito.it", picture: #{ url: "https://didattica.polito.it/photos/s123456.jpg" }, }, #{ @@ -115,9 +83,108 @@ const _ex_listFacultyCourseStudents_resp = #{ firstName: "Luigi", lastName: "Verdi", username: "s234567", + email: "s234567@studenti.polito.it", picture: #{ url: "https://didattica.polito.it/photos/s234567.jpg" }, } ], }, }, }; + +const _ex_searchEnrollableStudents_resp = #{ + parameters: #{ courseId: 251007, search: "Federica" }, + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + id: "s123456", + firstName: "Federica", + lastName: "Bianchi", + username: "s123456", + email: "s123456@studenti.polito.it", + picture: #{ url: "https://didattica.polito.it/photos/s123456.jpg" }, + }, + #{ + id: "s123457", + firstName: "Federica", + lastName: "Rossi", + username: "s123457", + email: "s123457@studenti.polito.it", + picture: #{ url: "https://didattica.polito.it/photos/s123457.jpg" }, + }, + #{ + id: "s123458", + firstName: "Federica", + lastName: "Verdi", + username: "s123458", + email: "s123458@studenti.polito.it", + picture: #{ url: "https://didattica.polito.it/photos/s123458.jpg" }, + } + ], + }, + }, +}; + +const _ex_enrollStudents_resp = #{ + parameters: #{ + courseId: 251007, + body: #{ studentIds: #["s123456", "s123458"] }, + }, + returnType: #{ statusCode: 204 }, +}; + +const _ex_getCourseStudentsStats_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + totalEnrolled: 204, + withAttendanceIssues: 10, + tookExam: 0, + withExamDebts: 42, + eligibleForExam: 194, + firstTimeInAcademicYear: 152, + }, + }, + }, +}; + +const _ex_getFacultyCourseStudent_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + id: "s123456", + firstName: "Stefania", + lastName: "Sassi", + username: "s123456", + email: "s123456@studenti.polito.it", + picture: #{ url: "https://didattica.polito.it/photos/s123456.jpg" }, + course: #{ shortcode: "02JSKPD", cfu: 6 }, + degreeCode: "CINZT3", + degreeName: "COMPUTER ENGINEERING", + examResult: #{ date: plainDate.fromISO("2025-02-23"), grade: "30L" }, + subscription: "2025/2026", + citizenship: "Italian", + hasSpecialNeeds: true, + }, + }, + }, +}; + +const _ex_getCourseStudentSpecialNeeds_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + content: "

List of compensatory measures granted to Stefania Sassi

  • Calculator allowed
  • Readability criteria (Arial, 12-point font, 1.5 line spacing, non-justified text, expanded spacing)
  • Cheat sheet for formulas for written and oral exams
  • 30% additional time for completing exams
  • Assessment of content vs. form
", + moreDetailsUrl: "https://didattica.polito.it/special-needs/requests?courseId=251007", + handbook: #{ + name: "Reporting procedure handbook", + url: "https://didattica.polito.it/docs/reporting-procedure-handbook.pdf", + }, + }, + }, + }, +}; diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index 66ebf58..e1ed5b3 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -11,7 +11,8 @@ namespace PolitoAPI; // Models -model FacultyCourseStudent { +/** Student enrolled in the course, used in listings. */ +model FacultyCourseStudentOverview { @example("s123456") id: string; @@ -24,12 +25,98 @@ model FacultyCourseStudent { @example("s123456") username: string; + @example("s123456@studenti.polito.it") + email: string; + picture?: { @example("https://example.com/picture.jpg") url: url; }; } +/** Aggregate statistics about the students enrolled in the course. */ +model FacultyCourseStudentsStats { + @example(204) + totalEnrolled: integer; + + @example(10) + withAttendanceIssues: integer; + + @example(0) + tookExam: integer; + + @example(42) + withExamDebts: integer; + + @example(194) + eligibleForExam: integer; + + @example(152) + firstTimeInAcademicYear: integer; +} + +/** Result of an exam taken by the student. */ +model FacultyCourseExamResult { + @example(plainDate.fromISO("2025-02-23")) + date: plainDate; + + /** Grade obtained (e.g. "30L", "28", "passed"). */ + @example("30L") + grade: string; +} + +/** Full information about a single student enrolled in the course */ +model FacultyCourseStudentDetail { + ...FacultyCourseStudentOverview; + course: { + @example("02JSKPD") + shortcode: string; + + @example(6) + cfu: integer; + }; + @example("CINZT3") + degreeCode: string; + + @example("COMPUTER ENGINEERING") + degreeName: string; + + examResult: FacultyCourseExamResult | null; + + /** Academic year of subscription. */ + @example("2025/2026") + subscription: string; + + @example("Italian") + citizenship: string; + + @example(true) + hasSpecialNeeds: boolean; +} + +/** Document attached to the compensative measures (e.g. a handbook). */ +model CompensativeMeasuresDocument { + @example("Reporting procedure handbook") + name: string; + + @example("https://didattica.polito.it/docs/handbook.pdf") + url: url; +} + +/** Compensative measures (special needs) granted to a student for the course. */ +model CompensativeMeasures { + /** HTML describing the compensatory measures granted to the student. */ + @example("

List of compensatory measures granted to Stefania Sassi

  • Calculator allowed
") + content: string; + + /** Link to the full list of requests for this course. */ + @example("https://didattica.polito.it/special-needs/requests") + moreDetailsUrl: url; + + /** Reporting procedure handbook document. */ + handbook: CompensativeMeasuresDocument; +} + model CourseMessageRequest { @example("Avviso importante") subject: string; @@ -100,13 +187,15 @@ model MoveCourseItemsRequest { destinationFolderId: string | null; } -// Response aliases with descriptions - -@doc("Studente già iscritto") -model StudentAlreadyEnrolledResponse { - ...OkDataResponse; +/** Request to enroll one or more students in the course at once. */ +model EnrollStudentsRequest { + /** IDs (matricole) of the students to enroll. */ + @example(#["s123456", "s234567"]) + studentIds: string[]; } +// Response aliases with descriptions + @doc("Accepted (sending)") model CourseMessageAcceptedResponse { ...AcceptedResponse; @@ -144,35 +233,86 @@ interface FacultyCourses { @path noticeId: integer, ): NoContentResponse | BaseErrors; + @post + @route("/message") + @summary("Invia comunicazione agli iscritti al corso") + messageCourseSubscribers( + @path courseId: integer, + @body body: CourseMessageRequest, + ): CourseMessageAcceptedResponse | NotAuthorized | BadRequest | BaseErrors; +} + +@tag("Faculty - Course Students") +@route("/faculty/courses/{courseId}/students") +interface FacultyCourseStudents { @get - @route("/students") - @summary("Elenca studenti iscritti al corso") + @summary("List students | Elenca studenti iscritti al corso") + @doc("Restituisce la vista overview degli studenti iscritti al corso.") @opExample(_ex_listFacultyCourseStudents_resp) listFacultyCourseStudents(@path courseId: integer): - | OkDataResponse + | OkDataResponse | NotAuthorized | NotFound | BaseErrors; - @put - @route("/students/{studentId}") - @summary("Aggiungi studente al corso") - @doc("Operazione idempotente per iscrivere uno studente al corso.") - enrollStudentInCourse(@path courseId: integer, @path studentId: string): - | OkDataResponse - | OkDataCreatedResponse + @get + @route("/stats") + @summary("Students stats | Statistiche studenti") + @opExample(_ex_getCourseStudentsStats_resp) + getCourseStudentsStats(@path courseId: integer): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @get + @route("/search") + @summary("Search enrollable students | Cerca studenti da iscrivere") + @doc("Cerca studenti da iscrivere al corso per nome, cognome o matricola.") + @opExample(_ex_searchEnrollableStudents_resp) + searchEnrollableStudents( + @path courseId: integer, + + /** Filter students by name, surname or student number. */ + @query search: string, + ): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @get + @route("/{studentId}") + @summary("Show student | Mostra studente") + @doc("Restituisce tutte le informazioni di un singolo studente iscritto al corso.") + @opExample(_ex_getFacultyCourseStudent_resp) + getFacultyCourseStudent(@path courseId: integer, @path studentId: string): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @get + @route("/{studentId}/special-needs") + @summary("Student compensative measures | Misure compensative studente") + @opExample(_ex_getCourseStudentSpecialNeeds_resp) + getCourseStudentSpecialNeeds( + @path courseId: integer, + @path studentId: string, + ): + | OkDataResponse | NotAuthorized - | BadRequest | NotFound | BaseErrors; @post - @route("/message") - @summary("Invia comunicazione agli iscritti al corso") - messageCourseSubscribers( + @summary("Add students | Aggiungi studenti al corso") + @doc("Iscrive uno o più studenti al corso. Operazione idempotente.") + @opExample(_ex_enrollStudents_resp) + enrollStudentsInCourse( @path courseId: integer, - @body body: CourseMessageRequest, - ): CourseMessageAcceptedResponse | NotAuthorized | BadRequest | BaseErrors; + @body body: EnrollStudentsRequest, + ): NoContentResponse | NotAuthorized | BadRequest | NotFound | BaseErrors; } @tag("Faculty - Course Files") @@ -185,7 +325,7 @@ interface FacultyCourseFiles { createCourseFolder( @path courseId: integer, @body body: CreateCourseFolderRequest, - ): OkDataCreatedResponse | NotAuthorized | NotFound | BaseErrors; + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; @patch(#{ implicitOptionality: false }) @route("/folders/{folderId}") @@ -195,16 +335,17 @@ interface FacultyCourseFiles { @path courseId: integer, @path folderId: string, @body body: UpdateCourseFolderRequest, - ): OkDataResponse | NotAuthorized | NotFound | BaseErrors; + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; @delete @route("/folders/{folderId}") @summary("Delete folder | Elimina cartella") @doc("Elimina la cartella e ricorsivamente tutto il suo contenuto.") - deleteCourseFolder( - @path courseId: integer, - @path folderId: string, - ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; + deleteCourseFolder(@path courseId: integer, @path folderId: string): + | NoContentResponse + | NotAuthorized + | NotFound + | BaseErrors; @post @summary("Upload files | Carica file") @@ -213,17 +354,18 @@ interface FacultyCourseFiles { @path courseId: integer, @header contentType: "multipart/form-data", @multipartBody body: CourseFileUpload, - ): OkDataCreatedResponse | NotAuthorized | NotFound | BaseErrors; + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; @post @route("/move") @summary("Move files | Sposta file") @doc("Sposta uno o più file e/o cartelle in una cartella di destinazione.") @opExample(_ex_moveCourseItems_resp) - moveCourseItems( - @path courseId: integer, - @body body: MoveCourseItemsRequest, - ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; + moveCourseItems(@path courseId: integer, @body body: MoveCourseItemsRequest): + | NoContentResponse + | NotAuthorized + | NotFound + | BaseErrors; @patch(#{ implicitOptionality: false }) @route("/{fileId}") @@ -233,13 +375,14 @@ interface FacultyCourseFiles { @path courseId: integer, @path fileId: string, @body body: UpdateCourseFileRequest, - ): OkDataResponse | NotAuthorized | NotFound | BaseErrors; + ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; @delete @route("/{fileId}") @summary("Delete file | Elimina file") - deleteCourseFile( - @path courseId: integer, - @path fileId: string, - ): NoContentResponse | NotAuthorized | NotFound | BaseErrors; + deleteCourseFile(@path courseId: integer, @path fileId: string): + | NoContentResponse + | NotAuthorized + | NotFound + | BaseErrors; } From 85a42614df1aba49bff572f6e6356845238a60be Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 01:19:55 +0200 Subject: [PATCH 07/10] feat(courses): add assignment removal request and messaging functionality for students --- src/examples/faculty/courses.tsp | 73 +++++++++++++++ src/routes/faculty/courses.tsp | 155 ++++++++++++++++++++++++------- 2 files changed, 196 insertions(+), 32 deletions(-) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index d095dee..546bdc6 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -134,6 +134,19 @@ const _ex_enrollStudents_resp = #{ returnType: #{ statusCode: 204 }, }; +const _ex_sendStudentsMessage_resp = #{ + parameters: #{ + courseId: 251007, + body: #{ + studentIds: #["s123456", "s123457", "s123458"], + channel: MessageChannel.email, + title: "Lezione di recupero", + message: "Si avvisano gli studenti che la lezione di recupero si terrà venerdì alle 14:00 in aula 7T.", + }, + }, + returnType: #{ statusCode: 202, body: #{ message: "Message accepted" } }, +}; + const _ex_getCourseStudentsStats_resp = #{ returnType: #{ statusCode: 200, @@ -188,3 +201,63 @@ const _ex_getCourseStudentSpecialNeeds_resp = #{ }, }, }; + +const _ex_listFacultyCourseAssignments_resp = #{ + returnType: #{ + statusCode: 200, + body: #{ + data: #[ + #{ + id: 947503, + filename: "PresentationGroup03.zip", + student: #{ id: "s348759", firstName: "Giuliana", lastName: "Annone" }, + status: FacultyAssignmentStatus.new, + }, + #{ + id: 947502, + filename: "PresentationGroup02.zip", + student: #{ id: "s345658", firstName: "Lorenzo", lastName: "Ciovenco" }, + status: FacultyAssignmentStatus.seen, + }, + #{ + id: 947501, + filename: "PresentationGroup01.zip", + student: #{ id: "s345600", firstName: "Marco", lastName: "Bruno" }, + status: FacultyAssignmentStatus.removalRequested, + } + ], + }, + }, +}; + +const _ex_getFacultyCourseAssignment_resp = #{ + parameters: #{ courseId: 251007, assignmentId: 947503 }, + returnType: #{ + statusCode: 200, + body: #{ + data: #{ + id: 947503, + description: "A presentation on advanced robotics and automation. Group 03.", + mimeType: "application/x-zip-compressed", + filename: "PresentationGroup03.zip", + uploadedAt: utcDateTime.fromISO("2025-11-17T14:00:00Z"), + deletedAt: null, + url: "https://file.didattica.polito.it/down/ELABORATI_PRE/1003948/S348759/6791f0016c78599138828211522fa84d/62cebc94", + sizeInKiloBytes: 305, + student: #{ id: "s348759", firstName: "Giuliana", lastName: "Annone" }, + status: FacultyAssignmentStatus.new, + }, + }, + }, +}; + +const _ex_answerCourseAssignment_resp = #{ + parameters: #{ + courseId: 251007, + assignmentId: 947503, + body: #{ + message: "Dear student, I cannot open the file you uploaded. Please try again to upload the folder in another format. Thank you!", + }, + }, + returnType: #{ statusCode: 202, body: #{ message: "Answer accepted" } }, +}; diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index e1ed5b3..d9ebd38 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -11,7 +11,6 @@ namespace PolitoAPI; // Models -/** Student enrolled in the course, used in listings. */ model FacultyCourseStudentOverview { @example("s123456") id: string; @@ -34,7 +33,6 @@ model FacultyCourseStudentOverview { }; } -/** Aggregate statistics about the students enrolled in the course. */ model FacultyCourseStudentsStats { @example(204) totalEnrolled: integer; @@ -55,7 +53,6 @@ model FacultyCourseStudentsStats { firstTimeInAcademicYear: integer; } -/** Result of an exam taken by the student. */ model FacultyCourseExamResult { @example(plainDate.fromISO("2025-02-23")) date: plainDate; @@ -65,7 +62,6 @@ model FacultyCourseExamResult { grade: string; } -/** Full information about a single student enrolled in the course */ model FacultyCourseStudentDetail { ...FacultyCourseStudentOverview; course: { @@ -94,7 +90,6 @@ model FacultyCourseStudentDetail { hasSpecialNeeds: boolean; } -/** Document attached to the compensative measures (e.g. a handbook). */ model CompensativeMeasuresDocument { @example("Reporting procedure handbook") name: string; @@ -103,7 +98,6 @@ model CompensativeMeasuresDocument { url: url; } -/** Compensative measures (special needs) granted to a student for the course. */ model CompensativeMeasures { /** HTML describing the compensatory measures granted to the student. */ @example("

List of compensatory measures granted to Stefania Sassi

  • Calculator allowed
") @@ -117,19 +111,6 @@ model CompensativeMeasures { handbook: CompensativeMeasuresDocument; } -model CourseMessageRequest { - @example("Avviso importante") - subject: string; - - @example("Messaggio importante") - message: string; - - channels: string[]; - audience: { - groups: string[]; - personIds: string[]; - }; -} model CourseNoticeRequest { @example("Contenuto dell'avviso") @@ -187,18 +168,79 @@ model MoveCourseItemsRequest { destinationFolderId: string | null; } -/** Request to enroll one or more students in the course at once. */ model EnrollStudentsRequest { /** IDs (matricole) of the students to enroll. */ @example(#["s123456", "s234567"]) studentIds: string[]; } -// Response aliases with descriptions +enum MessageChannel { + email: "email", + notify: "notify", +} + +model StudentsMessageRequest { + /** IDs (matricole) of the students to send the message to. */ + @example(#["s123456", "s234567"]) + studentIds: string[]; + + channel: MessageChannel; -@doc("Accepted (sending)") -model CourseMessageAcceptedResponse { - ...AcceptedResponse; + /** Title of the email. Required for the `email` channel, ignored for `notify`. */ + @example("Lezione di recupero") + title?: string; + + /** Message body. Maximum 4000 characters. */ + @maxLength(4000) + @example("Si avvisano gli studenti che...") + message: string; +} + +// Assignment management (faculty) + +enum FacultyAssignmentStatus { + /** Newly submitted, not yet seen by the faculty. */ + new: "new", + + seen: "seen", + + /** The student has requested the removal of this submission. */ + removalRequested: "removal_requested", +} + +model FacultyAssignmentAuthor { + @example("s348759") + id: string; + + @example("Giuliana") + firstName: string; + + @example("Annone") + lastName: string; +} + +model FacultyCourseAssignmentOverview { + @example(947503) + id: integer; + + @example("PresentationGroup03.zip") + filename: string; + + student: FacultyAssignmentAuthor; + status: FacultyAssignmentStatus; +} + +model FacultyCourseAssignmentDetail { + ...CourseAssignment; + student: FacultyAssignmentAuthor; + status: FacultyAssignmentStatus; +} + +model AssignmentAnswerRequest { + /** Answer message. Maximum 4000 characters. */ + @maxLength(4000) + @example("Dear student, I cannot open the file you uploaded. Please try again to upload the folder in another format. Thank you!") + message: string; } @tag("Faculty - Courses") @@ -232,14 +274,6 @@ interface FacultyCourses { @path courseId: integer, @path noticeId: integer, ): NoContentResponse | BaseErrors; - - @post - @route("/message") - @summary("Invia comunicazione agli iscritti al corso") - messageCourseSubscribers( - @path courseId: integer, - @body body: CourseMessageRequest, - ): CourseMessageAcceptedResponse | NotAuthorized | BadRequest | BaseErrors; } @tag("Faculty - Course Students") @@ -313,6 +347,16 @@ interface FacultyCourseStudents { @path courseId: integer, @body body: EnrollStudentsRequest, ): NoContentResponse | NotAuthorized | BadRequest | NotFound | BaseErrors; + + @post + @route("/messages") + @summary("Send message to students | Invia messaggio agli studenti") + @doc("Invia un messaggio agli studenti selezionati via email o notifica in-app. Per il canale `notify`, agli studenti che non hanno installato l'app viene inviato un SMS.") + @opExample(_ex_sendStudentsMessage_resp) + sendStudentsMessage( + @path courseId: integer, + @body body: StudentsMessageRequest, + ): AcceptedResponse | NotAuthorized | BadRequest | NotFound | BaseErrors; } @tag("Faculty - Course Files") @@ -386,3 +430,50 @@ interface FacultyCourseFiles { | NotFound | BaseErrors; } + +@tag("Faculty - Course Assignments") +@route("/faculty/courses/{courseId}/assignments") +interface FacultyCourseAssignments { + @get + @summary("List assignments | Elenca elaborati") + @doc("Restituisce gli elaborati consegnati dagli studenti per il corso.") + @opExample(_ex_listFacultyCourseAssignments_resp) + listFacultyCourseAssignments(@path courseId: integer): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @get + @route("/{assignmentId}") + @summary("Show assignment | Mostra elaborato") + @opExample(_ex_getFacultyCourseAssignment_resp) + getFacultyCourseAssignment( + @path courseId: integer, + @path assignmentId: integer, + ): + | OkDataResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @delete + @route("/{assignmentId}") + @summary("Delete assignment | Elimina elaborato") + deleteCourseAssignment(@path courseId: integer, @path assignmentId: integer): + | NoContentResponse + | NotAuthorized + | NotFound + | BaseErrors; + + @post + @route("/{assignmentId}/answer") + @summary("Answer assignment | Rispondi all'elaborato") + @doc("Invia una risposta allo studente riguardo al suo elaborato.") + @opExample(_ex_answerCourseAssignment_resp) + answerCourseAssignment( + @path courseId: integer, + @path assignmentId: integer, + @body body: AssignmentAnswerRequest, + ): AcceptedResponse | NotAuthorized | BadRequest | NotFound | BaseErrors; +} From b785106c557d5ba1e0c2f7c91a219202f8874a00 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 01:25:48 +0200 Subject: [PATCH 08/10] feat(courses): format Course model --- src/examples/faculty/courses.tsp | 12 ++++++++++-- src/routes/faculty/courses.tsp | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 546bdc6..2956348 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -210,13 +210,21 @@ const _ex_listFacultyCourseAssignments_resp = #{ #{ id: 947503, filename: "PresentationGroup03.zip", - student: #{ id: "s348759", firstName: "Giuliana", lastName: "Annone" }, + student: #{ + id: "s348759", + firstName: "Giuliana", + lastName: "Annone", + }, status: FacultyAssignmentStatus.new, }, #{ id: 947502, filename: "PresentationGroup02.zip", - student: #{ id: "s345658", firstName: "Lorenzo", lastName: "Ciovenco" }, + student: #{ + id: "s345658", + firstName: "Lorenzo", + lastName: "Ciovenco", + }, status: FacultyAssignmentStatus.seen, }, #{ diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index d9ebd38..519a3e6 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -71,6 +71,7 @@ model FacultyCourseStudentDetail { @example(6) cfu: integer; }; + @example("CINZT3") degreeCode: string; @@ -111,7 +112,6 @@ model CompensativeMeasures { handbook: CompensativeMeasuresDocument; } - model CourseNoticeRequest { @example("Contenuto dell'avviso") content: string; From 23a44415fa7fcce739d536e466a0322d8f035283 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Sun, 7 Jun 2026 01:33:10 +0200 Subject: [PATCH 09/10] feat(courses): add endpoint for requesting assignment removal --- dist/clients/faculty/openapi.yaml | 33 +++++++++++++++++++++++++++++++ dist/clients/student/openapi.yaml | 33 +++++++++++++++++++++++++++++++ src/routes/common/courses.tsp | 9 +++++++++ src/routes/faculty/courses.tsp | 20 +++---------------- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/dist/clients/faculty/openapi.yaml b/dist/clients/faculty/openapi.yaml index 719fee2..7565a3f 100644 --- a/dist/clients/faculty/openapi.yaml +++ b/dist/clients/faculty/openapi.yaml @@ -925,6 +925,39 @@ paths: application/json: schema: $ref: '#/components/schemas/UpdateAssignmentRequest' + /courses/{courseId}/assignments/{assignmentId}/removal-request: + post: + operationId: Courses_requestAssignmentRemoval + summary: Request assignment removal | Richiedi rimozione elaborato + description: Lo studente richiede al docente la rimozione del proprio elaborato. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: assignmentId + 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. ' + '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: + - Courses /courses/{courseId}/files: get: operationId: Courses_getCourseFiles diff --git a/dist/clients/student/openapi.yaml b/dist/clients/student/openapi.yaml index 7516e2d..fb90c41 100644 --- a/dist/clients/student/openapi.yaml +++ b/dist/clients/student/openapi.yaml @@ -932,6 +932,39 @@ paths: application/json: schema: $ref: '#/components/schemas/UpdateAssignmentRequest' + /courses/{courseId}/assignments/{assignmentId}/removal-request: + post: + operationId: Courses_requestAssignmentRemoval + summary: Request assignment removal | Richiedi rimozione elaborato + description: Lo studente richiede al docente la rimozione del proprio elaborato. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: assignmentId + 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. ' + '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: + - Courses /courses/{courseId}/files: get: operationId: Courses_getCourseFiles diff --git a/src/routes/common/courses.tsp b/src/routes/common/courses.tsp index 118c95c..88d5748 100644 --- a/src/routes/common/courses.tsp +++ b/src/routes/common/courses.tsp @@ -334,6 +334,15 @@ interface Courses { @body body: UpdateAssignmentRequest, ): NoContentResponse | BaseErrors; + @route("/{courseId}/assignments/{assignmentId}/removal-request") + @post + @summary("Request assignment removal | Richiedi rimozione elaborato") + @doc("Lo studente richiede al docente la rimozione del proprio elaborato.") + requestAssignmentRemoval( + @path courseId: integer, + @path assignmentId: integer, + ): NoContentResponse | BaseErrors; + @route("/{courseId}/guide") @get @summary("Show guide | Mostra guida") diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index 519a3e6..faf6464 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -144,7 +144,6 @@ model UpdateCourseFolderRequest { } model CourseFileUpload { - /** Files to upload. Multiple files can be uploaded at once. */ files: HttpPart[]; /** Destination folder. If omitted, files are uploaded to the root. */ @@ -160,16 +159,13 @@ model UpdateCourseFileRequest { } model MoveCourseItemsRequest { - /** IDs of files and/or folders to move. */ @example(#["2", "33352562"]) itemIds: string[]; - /** Destination folder. `null` moves to the root. */ destinationFolderId: string | null; } model EnrollStudentsRequest { - /** IDs (matricole) of the students to enroll. */ @example(#["s123456", "s234567"]) studentIds: string[]; } @@ -180,17 +176,14 @@ enum MessageChannel { } model StudentsMessageRequest { - /** IDs (matricole) of the students to send the message to. */ @example(#["s123456", "s234567"]) studentIds: string[]; channel: MessageChannel; - /** Title of the email. Required for the `email` channel, ignored for `notify`. */ @example("Lezione di recupero") title?: string; - /** Message body. Maximum 4000 characters. */ @maxLength(4000) @example("Si avvisano gli studenti che...") message: string; @@ -199,13 +192,8 @@ model StudentsMessageRequest { // Assignment management (faculty) enum FacultyAssignmentStatus { - /** Newly submitted, not yet seen by the faculty. */ new: "new", - seen: "seen", - - /** The student has requested the removal of this submission. */ - removalRequested: "removal_requested", } model FacultyAssignmentAuthor { @@ -237,17 +225,15 @@ model FacultyCourseAssignmentDetail { } model AssignmentAnswerRequest { - /** Answer message. Maximum 4000 characters. */ @maxLength(4000) @example("Dear student, I cannot open the file you uploaded. Please try again to upload the folder in another format. Thank you!") message: string; } @tag("Faculty - Courses") -@route("/faculty/courses/{courseId}") +@route("/faculty/courses/{courseId}/notices") interface FacultyCourses { @post - @route("/notices") @summary("Publish notices | Pubblica avvisi") @opExample(_ex_publishCourseNotice_req) publishCourseNotice( @@ -257,7 +243,7 @@ interface FacultyCourses { ): NoContentResponse | BaseErrors; @put - @route("/notices/{noticeId}") + @route("/{noticeId}") @summary("Update notice | Modifica avviso") @opExample(_ex_updateCourseNotice_resp) updateCourseNotice( @@ -267,7 +253,7 @@ interface FacultyCourses { ): NoContentResponse | BaseErrors; @delete - @route("/notices/{noticeId}") + @route("/{noticeId}") @summary("Delete notice | Elimina avviso") @opExample(_ex_deleteCourseNotice_resp) deleteCourseNotice( From cd3f4a8917763e0371d40b62adb813d979edde02 Mon Sep 17 00:00:00 2001 From: FabrizioCostaMedich Date: Fri, 12 Jun 2026 13:18:04 +0200 Subject: [PATCH 10/10] feat(faculty): add courses route, fix example namespace, add removalRequested status --- dist/clients/faculty/openapi.yaml | 1558 +++++++++++++++++++++++++++++ src/clients/faculty/main.tsp | 1 + src/examples/faculty/courses.tsp | 2 + src/routes/faculty/courses.tsp | 1 + 4 files changed, 1562 insertions(+) diff --git a/dist/clients/faculty/openapi.yaml b/dist/clients/faculty/openapi.yaml index 7565a3f..9a9b15c 100644 --- a/dist/clients/faculty/openapi.yaml +++ b/dist/clients/faculty/openapi.yaml @@ -14,6 +14,10 @@ tags: - name: News - name: People - name: Places + - name: Faculty - Courses + - name: Faculty - Course Students + - name: Faculty - Course Files + - name: Faculty - Course Assignments paths: /announcements: get: @@ -1440,6 +1444,1163 @@ paths: $ref: '#/components/schemas/ErrorResponse' tags: - Places + /faculty/courses/{courseId}/assignments: + get: + operationId: FacultyCourseAssignments_listFacultyCourseAssignments + summary: List assignments | Elenca elaborati + description: Restituisce gli elaborati consegnati dagli studenti per il corso. + parameters: + - name: courseId + 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/FacultyCourseAssignmentOverview' + required: + - data + example: + data: + - id: 947503 + filename: PresentationGroup03.zip + student: + id: s348759 + firstName: Giuliana + lastName: Annone + status: new + - id: 947502 + filename: PresentationGroup02.zip + student: + id: s345658 + firstName: Lorenzo + lastName: Ciovenco + status: seen + - id: 947501 + filename: PresentationGroup01.zip + student: + id: s345600 + firstName: Marco + lastName: Bruno + status: removalRequested + '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' + '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 - Course Assignments + /faculty/courses/{courseId}/assignments/{assignmentId}: + get: + operationId: FacultyCourseAssignments_getFacultyCourseAssignment + summary: Show assignment | Mostra elaborato + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: assignmentId + 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/FacultyCourseAssignmentDetail' + required: + - data + example: + data: + id: 947503 + description: A presentation on advanced robotics and automation. Group 03. + mimeType: application/x-zip-compressed + filename: PresentationGroup03.zip + uploadedAt: '2025-11-17T14:00:00Z' + deletedAt: null + url: https://file.didattica.polito.it/down/ELABORATI_PRE/1003948/S348759/6791f0016c78599138828211522fa84d/62cebc94 + sizeInKiloBytes: 305 + student: + id: s348759 + firstName: Giuliana + lastName: Annone + status: new + '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' + '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 - Course Assignments + delete: + operationId: FacultyCourseAssignments_deleteCourseAssignment + summary: Delete assignment | Elimina elaborato + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: assignmentId + 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. ' + '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' + '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 - Course Assignments + /faculty/courses/{courseId}/assignments/{assignmentId}/answer: + post: + operationId: FacultyCourseAssignments_answerCourseAssignment + summary: Answer assignment | Rispondi all'elaborato + description: Invia una risposta allo studente riguardo al suo elaborato. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: assignmentId + 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: + message: + type: string + example: + message: Answer accepted + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ErrorResponse' + - $ref: '#/components/schemas/ErrorResponse' + '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 - Course Assignments + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AssignmentAnswerRequest' + example: + message: Dear student, I cannot open the file you uploaded. Please try again to upload the folder in another format. Thank you! + /faculty/courses/{courseId}/files: + post: + operationId: FacultyCourseFiles_uploadCourseFiles + summary: Upload files | Carica file + description: Carica uno o più file nel corso. La richiesta è multipart/form-data e accetta più file contemporaneamente. + parameters: + - name: courseId + 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. ' + '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' + '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 - Course Files + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseFileUpload' + /faculty/courses/{courseId}/files/folders: + post: + operationId: FacultyCourseFiles_createCourseFolder + summary: Create folder | Crea cartella + parameters: + - name: courseId + 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. ' + '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' + '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 - Course Files + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCourseFolderRequest' + example: + name: Laboratori + parentId: null + /faculty/courses/{courseId}/files/folders/{folderId}: + patch: + operationId: FacultyCourseFiles_updateCourseFolder + summary: Update folder | Modifica cartella + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: folderId + in: path + required: true + schema: + type: string + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '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' + '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 - Course Files + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCourseFolderRequest' + example: + name: Laboratori 2024 + delete: + operationId: FacultyCourseFiles_deleteCourseFolder + summary: Delete folder | Elimina cartella + description: Elimina la cartella e ricorsivamente tutto il suo contenuto. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: folderId + in: path + required: true + schema: + type: string + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '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' + '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 - Course Files + /faculty/courses/{courseId}/files/move: + post: + operationId: FacultyCourseFiles_moveCourseItems + summary: Move files | Sposta file + description: Sposta uno o più file e/o cartelle in una cartella di destinazione. + parameters: + - name: courseId + 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. ' + '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' + '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 - Course Files + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MoveCourseItemsRequest' + example: + itemIds: + - '2' + - '33352562' + destinationFolderId: '44463673' + /faculty/courses/{courseId}/files/{fileId}: + patch: + operationId: FacultyCourseFiles_updateCourseFile + summary: Update file | Modifica file + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: fileId + in: path + required: true + schema: + type: string + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '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' + '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 - Course Files + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCourseFileRequest' + example: + name: Lecture1_rev.pdf + parentId: '33352562' + delete: + operationId: FacultyCourseFiles_deleteCourseFile + summary: Delete file | Elimina file + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: fileId + in: path + required: true + schema: + type: string + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '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' + '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 - Course Files + /faculty/courses/{courseId}/notices/{isDraft}: + post: + operationId: FacultyCourses_publishCourseNotice + summary: Publish notices | Pubblica avvisi + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: isDraft + in: path + required: true + schema: + type: boolean + responses: + '204': + description: 'There is no content to send for this request, but the headers may be useful. ' + '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 - Courses + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CourseNoticeRequest' + example: + content:

Lezione di domani spostata in aula 7T.

+ publishedDate: '2025-10-14T09:00:00Z' + expiresAt: '2025-10-20T23:59:59Z' + isAlwaysVisible: true + /faculty/courses/{courseId}/notices/{noticeId}: + put: + operationId: FacultyCourses_updateCourseNotice + summary: Update notice | Modifica avviso + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: noticeId + 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. ' + '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 - Courses + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CourseNoticeRequest' + example: + content: '

Aggiornamento: lezione spostata in aula 10T.

' + publishedDate: '2025-10-14T09:00:00Z' + expiresAt: '2025-10-22T23:59:59Z' + isAlwaysVisible: false + delete: + operationId: FacultyCourses_deleteCourseNotice + summary: Delete notice | Elimina avviso + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: noticeId + 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. ' + '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 - Courses + /faculty/courses/{courseId}/students: + get: + operationId: FacultyCourseStudents_listFacultyCourseStudents + summary: List students | Elenca studenti iscritti al corso + description: Restituisce la vista overview degli studenti iscritti al corso. + parameters: + - name: courseId + 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/FacultyCourseStudentOverview' + required: + - data + example: + data: + - id: s123456 + firstName: Mario + lastName: Rossi + username: s123456 + email: s123456@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s123456.jpg + - id: s234567 + firstName: Luigi + lastName: Verdi + username: s234567 + email: s234567@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s234567.jpg + '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' + '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 - Course Students + post: + operationId: FacultyCourseStudents_enrollStudentsInCourse + summary: Add students | Aggiungi studenti al corso + description: Iscrive uno o più studenti al corso. Operazione idempotente. + parameters: + - name: courseId + 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. ' + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ErrorResponse' + - $ref: '#/components/schemas/ErrorResponse' + '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 - Course Students + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EnrollStudentsRequest' + example: + studentIds: + - s123456 + - s123458 + /faculty/courses/{courseId}/students/messages: + post: + operationId: FacultyCourseStudents_sendStudentsMessage + summary: Send message to students | Invia messaggio agli studenti + description: Invia un messaggio agli studenti selezionati via email o notifica in-app. Per il canale `notify`, agli studenti che non hanno installato l'app viene inviato un SMS. + parameters: + - name: courseId + 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: + message: + type: string + example: + message: Message accepted + '400': + description: The server could not understand the request due to invalid syntax. + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ErrorResponse' + - $ref: '#/components/schemas/ErrorResponse' + '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 - Course Students + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StudentsMessageRequest' + example: + studentIds: + - s123456 + - s123457 + - s123458 + channel: email + title: Lezione di recupero + message: Si avvisano gli studenti che la lezione di recupero si terrà venerdì alle 14:00 in aula 7T. + /faculty/courses/{courseId}/students/search: + get: + operationId: FacultyCourseStudents_searchEnrollableStudents + summary: Search enrollable students | Cerca studenti da iscrivere + description: Cerca studenti da iscrivere al corso per nome, cognome o matricola. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: search + in: query + required: true + description: Filter students by name, surname or student number. + schema: + type: string + explode: false + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/FacultyCourseStudentOverview' + required: + - data + example: + data: + - id: s123456 + firstName: Federica + lastName: Bianchi + username: s123456 + email: s123456@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s123456.jpg + - id: s123457 + firstName: Federica + lastName: Rossi + username: s123457 + email: s123457@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s123457.jpg + - id: s123458 + firstName: Federica + lastName: Verdi + username: s123458 + email: s123458@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s123458.jpg + '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' + '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 - Course Students + /faculty/courses/{courseId}/students/stats: + get: + operationId: FacultyCourseStudents_getCourseStudentsStats + summary: Students stats | Statistiche studenti + parameters: + - name: courseId + 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/FacultyCourseStudentsStats' + required: + - data + example: + data: + totalEnrolled: 204 + withAttendanceIssues: 10 + tookExam: 0 + withExamDebts: 42 + eligibleForExam: 194 + firstTimeInAcademicYear: 152 + '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' + '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 - Course Students + /faculty/courses/{courseId}/students/{studentId}: + get: + operationId: FacultyCourseStudents_getFacultyCourseStudent + summary: Show student | Mostra studente + description: Restituisce tutte le informazioni di un singolo studente iscritto al corso. + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: studentId + in: path + required: true + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/FacultyCourseStudentDetail' + required: + - data + example: + data: + id: s123456 + firstName: Stefania + lastName: Sassi + username: s123456 + email: s123456@studenti.polito.it + picture: + url: https://didattica.polito.it/photos/s123456.jpg + course: + shortcode: 02JSKPD + cfu: 6 + degreeCode: CINZT3 + degreeName: COMPUTER ENGINEERING + examResult: + date: '2025-02-23' + grade: 30L + subscription: 2025/2026 + citizenship: Italian + hasSpecialNeeds: true + '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' + '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 - Course Students + /faculty/courses/{courseId}/students/{studentId}/special-needs: + get: + operationId: FacultyCourseStudents_getCourseStudentSpecialNeeds + summary: Student compensative measures | Misure compensative studente + parameters: + - name: courseId + in: path + required: true + schema: + type: integer + - name: studentId + in: path + required: true + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/CompensativeMeasures' + required: + - data + example: + data: + content:

List of compensatory measures granted to Stefania Sassi

  • Calculator allowed
  • Readability criteria (Arial, 12-point font, 1.5 line spacing, non-justified text, expanded spacing)
  • Cheat sheet for formulas for written and oral exams
  • 30% additional time for completing exams
  • Assessment of content vs. form
+ moreDetailsUrl: https://didattica.polito.it/special-needs/requests?courseId=251007 + handbook: + name: Reporting procedure handbook + url: https://didattica.polito.it/docs/reporting-procedure-handbook.pdf + '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' + '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 - Course Students /lectures: get: operationId: Lectures_getLectures @@ -2782,6 +3943,15 @@ components: fcmRegistrationToken: type: string example: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 + AssignmentAnswerRequest: + type: object + required: + - message + properties: + message: + type: string + maxLength: 4000 + example: Dear student, I cannot open the file you uploaded. Please try again to upload the folder in another format. Thank you! Booking: type: object required: @@ -3237,6 +4407,39 @@ components: appVersion: type: string example: 1.6.2 + CompensativeMeasures: + type: object + required: + - content + - moreDetailsUrl + - handbook + properties: + content: + type: string + description: HTML describing the compensatory measures granted to the student. + example:

List of compensatory measures granted to Stefania Sassi

  • Calculator allowed
+ moreDetailsUrl: + type: string + format: uri + description: Link to the full list of requests for this course. + example: https://didattica.polito.it/special-needs/requests + handbook: + allOf: + - $ref: '#/components/schemas/CompensativeMeasuresDocument' + description: Reporting procedure handbook document. + CompensativeMeasuresDocument: + type: object + required: + - name + - url + properties: + name: + type: string + example: Reporting procedure handbook + url: + type: string + format: uri + example: https://didattica.polito.it/docs/handbook.pdf Course: type: object required: @@ -3472,6 +4675,19 @@ components: mimeType: application/pdf createdAt: '2024-06-01T10:00:00Z' checksum: deadbeefcafebabe0000000000000000 + CourseFileUpload: + type: object + properties: + files: + type: array + items: + type: string + format: binary + parentId: + type: string + description: Destination folder. If omitted, files are uploaded to the root. + required: + - files CourseModule: type: object required: @@ -3558,6 +4774,28 @@ components: year: '2021' allOf: - $ref: '#/components/schemas/CourseModule' + CourseNoticeRequest: + type: object + required: + - content + - publishedDate + - expiresAt + - isAlwaysVisible + properties: + content: + type: string + example: Contenuto dell'avviso + publishedDate: + type: string + format: date-time + example: '2022-07-03T14:00:00Z' + expiresAt: + type: string + format: date-time + nullable: true + isAlwaysVisible: + type: boolean + example: true CourseNoticeStatus: type: string enum: @@ -3633,6 +4871,18 @@ components: seatId: type: integer example: 20018 + CreateCourseFolderRequest: + type: object + required: + - name + properties: + name: + type: string + example: Laboratori + parentId: + type: string + nullable: true + description: Parent folder. If omitted, the folder is created in the root. Department: type: object required: @@ -3680,6 +4930,18 @@ components: example: description: some identifier you chose pubkey: dGhpcyBzaG91bGQgYmUgYW4gZWNkcyBwdWJsaWMga2V5Li4uIHdoYXQgZGlkIHlvdSB0aGluayB0aGlzIHdhcz8hIPCfp5AK + EnrollStudentsRequest: + type: object + required: + - studentIds + properties: + studentIds: + type: array + items: + type: string + example: + - s123456 + - s234567 ErrorResponse: type: object properties: @@ -3732,6 +4994,238 @@ components: - expiresAt - qrCode nullable: true + FacultyAssignmentAuthor: + type: object + required: + - id + - firstName + - lastName + properties: + id: + type: string + example: s348759 + firstName: + type: string + example: Giuliana + lastName: + type: string + example: Annone + FacultyAssignmentStatus: + type: string + enum: + - new + - seen + - removalRequested + FacultyCourseAssignmentDetail: + type: object + required: + - id + - description + - mimeType + - filename + - uploadedAt + - deletedAt + - url + - sizeInKiloBytes + - student + - status + properties: + id: + type: integer + example: 947503 + description: + type: string + example: laboratorio 3 + mimeType: + type: string + example: application/x-zip-compressed + filename: + type: string + example: lab_03.zip + uploadedAt: + type: string + format: date-time + example: '2022-09-02T14:00:00Z' + deletedAt: + type: string + format: date-time + nullable: true + url: + type: string + example: https://file.didattica.polito.it/down/ELABORATI_PRE/1003948/S290683/6791f0016c78599138828211522fa84d/62cebc94 + sizeInKiloBytes: + type: integer + example: 305 + student: + $ref: '#/components/schemas/FacultyAssignmentAuthor' + status: + $ref: '#/components/schemas/FacultyAssignmentStatus' + FacultyCourseAssignmentOverview: + type: object + required: + - id + - filename + - student + - status + properties: + id: + type: integer + example: 947503 + filename: + type: string + example: PresentationGroup03.zip + student: + $ref: '#/components/schemas/FacultyAssignmentAuthor' + status: + $ref: '#/components/schemas/FacultyAssignmentStatus' + FacultyCourseExamResult: + type: object + required: + - date + - grade + properties: + date: + type: string + format: date + example: '2025-02-23' + grade: + type: string + description: Grade obtained (e.g. "30L", "28", "passed"). + example: 30L + FacultyCourseStudentDetail: + type: object + required: + - id + - firstName + - lastName + - username + - email + - course + - degreeCode + - degreeName + - examResult + - subscription + - citizenship + - hasSpecialNeeds + properties: + id: + type: string + example: s123456 + firstName: + type: string + example: Mario + lastName: + type: string + example: Rossi + username: + type: string + example: s123456 + email: + type: string + example: s123456@studenti.polito.it + picture: + type: object + properties: + url: + type: string + format: uri + example: https://example.com/picture.jpg + required: + - url + course: + type: object + properties: + shortcode: + type: string + example: 02JSKPD + cfu: + type: integer + example: 6 + required: + - shortcode + - cfu + degreeCode: + type: string + example: CINZT3 + degreeName: + type: string + example: COMPUTER ENGINEERING + examResult: + type: object + allOf: + - $ref: '#/components/schemas/FacultyCourseExamResult' + nullable: true + subscription: + type: string + description: Academic year of subscription. + example: 2025/2026 + citizenship: + type: string + example: Italian + hasSpecialNeeds: + type: boolean + example: true + FacultyCourseStudentOverview: + type: object + required: + - id + - firstName + - lastName + - username + - email + properties: + id: + type: string + example: s123456 + firstName: + type: string + example: Mario + lastName: + type: string + example: Rossi + username: + type: string + example: s123456 + email: + type: string + example: s123456@studenti.polito.it + picture: + type: object + properties: + url: + type: string + format: uri + example: https://example.com/picture.jpg + required: + - url + FacultyCourseStudentsStats: + type: object + required: + - totalEnrolled + - withAttendanceIssues + - tookExam + - withExamDebts + - eligibleForExam + - firstTimeInAcademicYear + properties: + totalEnrolled: + type: integer + example: 204 + withAttendanceIssues: + type: integer + example: 10 + tookExam: + type: integer + example: 0 + withExamDebts: + type: integer + example: 42 + eligibleForExam: + type: integer + example: 194 + firstTimeInAcademicYear: + type: integer + example: 152 FcmRegistrationRequest: type: object properties: @@ -3966,6 +5460,11 @@ components: $ref: '#/components/schemas/Device' preferences: $ref: '#/components/schemas/UpdatePreferencesRequest' + MessageChannel: + type: string + enum: + - email + - notify MfaChallenge: type: object required: @@ -4026,6 +5525,22 @@ components: description: |- Additional information about the MFA session. Present only if status is `active` or `locked` + MoveCourseItemsRequest: + type: object + required: + - itemIds + - destinationFolderId + properties: + itemIds: + type: array + items: + type: string + example: + - '2' + - '33352562' + destinationFolderId: + type: string + nullable: true NewsItem: type: object required: @@ -4576,6 +6091,29 @@ components: description: |- A delta in degrees that applied to the centroid coordinates of the site determines its extent + StudentsMessageRequest: + type: object + required: + - studentIds + - channel + - message + properties: + studentIds: + type: array + items: + type: string + example: + - s123456 + - s234567 + channel: + $ref: '#/components/schemas/MessageChannel' + title: + type: string + example: Lezione di recupero + message: + type: string + maxLength: 4000 + example: Si avvisano gli studenti che... SwitchCareerRequest: type: object required: @@ -4608,6 +6146,26 @@ components: isLocationChecked: type: boolean example: true + UpdateCourseFileRequest: + type: object + properties: + name: + type: string + example: Lecture1.pdf + parentId: + type: string + nullable: true + description: New parent folder to move the file to. + UpdateCourseFolderRequest: + type: object + properties: + name: + type: string + example: Laboratori 2024 + parentId: + type: string + nullable: true + description: New parent folder to move the folder to. UpdateInfo: type: object required: diff --git a/src/clients/faculty/main.tsp b/src/clients/faculty/main.tsp index 2e2e803..5ed3e75 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/courses.tsp"; import "./version.tsp"; diff --git a/src/examples/faculty/courses.tsp b/src/examples/faculty/courses.tsp index 2956348..25742ec 100644 --- a/src/examples/faculty/courses.tsp +++ b/src/examples/faculty/courses.tsp @@ -1,3 +1,5 @@ +namespace PolitoAPI; + const _ex_publishCourseNotice_req = #{ parameters: #{ courseId: 251007, diff --git a/src/routes/faculty/courses.tsp b/src/routes/faculty/courses.tsp index faf6464..87a696a 100644 --- a/src/routes/faculty/courses.tsp +++ b/src/routes/faculty/courses.tsp @@ -194,6 +194,7 @@ model StudentsMessageRequest { enum FacultyAssignmentStatus { new: "new", seen: "seen", + removalRequested: "removalRequested", } model FacultyAssignmentAuthor {