Spaces:
Sleeping
Sleeping
| # π EduVerse API Documentation β Courses / Assignments / Labs | |
| > **Purpose**: Complete Flutter frontend integration guide. | |
| > **Base URL**: `http://<host>:3001` (default port `3001`) | |
| > **Auth**: Most endpoints require `Authorization: Bearer <JWT>` header. | |
| > **Content-Type**: `application/json` unless otherwise noted (file uploads use `multipart/form-data`). | |
| --- | |
| ## Table of Contents | |
| 1. [Global Information](#1-global-information) | |
| 2. [Enums Reference](#2-enums-reference) | |
| 3. [Courses Module](#3-courses-module) | |
| 4. [Course Sections Module](#4-course-sections-module) | |
| 5. [Course Schedules Module](#5-course-schedules-module) | |
| 6. [Assignments Module](#6-assignments-module) | |
| 7. [Labs Module](#7-labs-module) | |
| 8. [Role-Based Access Matrix](#8-role-based-access-matrix) | |
| 9. [Error Handling](#9-error-handling) | |
| 10. [Flutter Integration Tips](#10-flutter-integration-tips) | |
| 11. [Course Materials & Video Lectures Module](#11-course-materials--video-lectures-module) | |
| 12. [Course Structure Module](#12-course-structure-module) | |
| 13. [YouTube Integration Module](#13-youtube-integration-module) | |
| --- | |
| ## 1. Global Information | |
| ### 1.1 Authentication | |
| All **authenticated endpoints** require the header: | |
| ``` | |
| Authorization: Bearer <access_token> | |
| ``` | |
| The token is obtained from `POST /api/auth/login`. | |
| ### 1.2 Roles | |
| | Role Enum Value | Display Name | Description | | |
| |---|---|---| | |
| | `student` | Student | Regular student users | | |
| | `instructor` | Instructor | Faculty members | | |
| | `teaching_assistant` | TA | Teaching assistants | | |
| | `admin` | Admin | Administrative staff | | |
| | `it_admin` | IT Admin | Full system access | | |
| | `department_head` | Department Head | Department leadership | | |
| ### 1.3 Pagination Response Shape | |
| All paginated endpoints return: | |
| ```json | |
| { | |
| "data": [ /* array of items */ ], | |
| "meta": { | |
| "total": 150, // integer β total matching records | |
| "page": 1, // integer β current page number | |
| "limit": 20, // integer β items per page | |
| "totalPages": 8 // integer β total number of pages | |
| } | |
| } | |
| ``` | |
| ### 1.4 Validation Rules | |
| The API uses `class-validator` with: | |
| - `whitelist: true` β unknown properties are **stripped** | |
| - `forbidNonWhitelisted: true` β unknown properties cause **400 error** | |
| - `transform: true` β query strings auto-converted to proper types | |
| --- | |
| ## 2. Enums Reference | |
| ### 2.1 Course Enums | |
| #### CourseLevel | |
| | Value | Description | | |
| |---|---| | |
| | `FRESHMAN` | Freshman-level course | | |
| | `SOPHOMORE` | Sophomore-level course | | |
| | `JUNIOR` | Junior-level course | | |
| | `SENIOR` | Senior-level course | | |
| | `GRADUATE` | Graduate-level course | | |
| #### CourseStatus | |
| | Value | Description | | |
| |---|---| | |
| | `ACTIVE` | Course is currently active | | |
| | `INACTIVE` | Course is inactive | | |
| | `ARCHIVED` | Course is archived | | |
| #### SectionStatus | |
| | Value | Description | | |
| |---|---| | |
| | `OPEN` | Section accepting enrollments | | |
| | `CLOSED` | Section manually closed | | |
| | `FULL` | Section at max capacity | | |
| | `CANCELLED` | Section cancelled | | |
| #### ScheduleType | |
| | Value | Description | | |
| |---|---| | |
| | `LECTURE` | Lecture session | | |
| | `LAB` | Laboratory session | | |
| | `TUTORIAL` | Tutorial/recitation session | | |
| | `EXAM` | Examination session | | |
| #### DayOfWeek | |
| | Value | | |
| |---| | |
| | `MONDAY` | | |
| | `TUESDAY` | | |
| | `WEDNESDAY` | | |
| | `THURSDAY` | | |
| | `FRIDAY` | | |
| | `SATURDAY` | | |
| | `SUNDAY` | | |
| ### 2.2 Assignment Enums | |
| #### SubmissionType | |
| | Value | Description | | |
| |---|---| | |
| | `file` | File-based submission | | |
| | `text` | Text-based submission | | |
| | `link` | Link/URL submission | | |
| | `multiple` | Combination of types | | |
| #### AssignmentStatus | |
| | Value | Description | | |
| |---|---| | |
| | `draft` | Not visible to students | | |
| | `published` | Visible and accepting submissions | | |
| | `closed` | No longer accepting submissions | | |
| | `archived` | Hidden from all views | | |
| > **Status Transitions**: `draft` β `published` β `closed` β `archived` (one-way only) | |
| #### SubmissionStatus | |
| | Value | Description | | |
| |---|---| | |
| | `submitted` | Student has submitted | | |
| | `graded` | Submission has been graded | | |
| | `returned` | Returned to student for review | | |
| | `resubmit` | Student must resubmit | | |
| ### 2.3 Lab Enums | |
| #### LabStatus | |
| | Value | Description | | |
| |---|---| | |
| | `draft` | Not visible to students | | |
| | `published` | Visible and accepting submissions | | |
| | `closed` | No longer accepting submissions | | |
| | `archived` | Hidden from all views | | |
| #### LabSubmissionStatus | |
| | Value | Description | | |
| |---|---| | |
| | `submitted` | Student has submitted | | |
| | `graded` | Submission has been graded | | |
| | `returned` | Returned to student | | |
| | `resubmit` | Student must resubmit | | |
| #### LabAttendanceStatus | |
| | Value | Description | | |
| |---|---| | |
| | `present` | Student was present | | |
| | `absent` | Student was absent | | |
| | `excused` | Excused absence | | |
| | `late` | Student arrived late | | |
| --- | |
| ## 3. Courses Module | |
| **Base Path**: `/api/courses` | |
| > **Authentication**: Courses listing/viewing endpoints are **public** (no auth required). | |
| > Course creation/update/delete endpoints need authentication and specific roles. | |
| --- | |
| ### 3.1 List All Courses | |
| ``` | |
| GET /api/courses | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All (public endpoint) | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `departmentId` | `integer` | β | β | Filter by department ID | | |
| | `level` | `string` | β | β | Filter by course level enum (e.g. `FRESHMAN`, `GRADUATE`) | | |
| | `status` | `string` | β | β | Filter by `CourseStatus` enum (`ACTIVE`, `INACTIVE`, `ARCHIVED`) | | |
| | `search` | `string` | β | β | Search in course name or code (partial match) | | |
| | `page` | `integer` | β | `1` | Page number (1-indexed) | | |
| | `limit` | `integer` | β | `20` | Items per page (max 100) | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "data": [ | |
| { | |
| "id": 1, // number β Course ID (bigint) | |
| "departmentId": 3, // number β FK to departments | |
| "name": "Introduction to CS", // string β Course name | |
| "code": "CS101", // string β Unique course code | |
| "description": "Fundamentals of...", // string | null β Description | |
| "credits": 3, // number β Credit hours (1-6) | |
| "level": "FRESHMAN", // string β CourseLevel enum | |
| "syllabusUrl": "https://...", // string | null β Syllabus URL | |
| "instructorId": 5, // number | null β Assigned instructor | |
| "taIds": [1, 2], // number[] | null β Assigned TA IDs | |
| "status": "ACTIVE", // string β CourseStatus enum | |
| "createdAt": "2025-01-15T10:00:00Z", // string (ISO 8601) β Creation timestamp | |
| "updatedAt": "2025-01-15T10:00:00Z", // string (ISO 8601) β Last update | |
| "department": { // object β Joined department info | |
| "id": 3, | |
| "name": "Computer Science", | |
| "code": "CS" | |
| } | |
| } | |
| ], | |
| "meta": { | |
| "total": 42, | |
| "page": 1, | |
| "limit": 20, | |
| "totalPages": 3 | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 3.2 Get Courses by Department | |
| ``` | |
| GET /api/courses/department/:deptId | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `deptId` | `integer` | β | Department ID | | |
| #### Response `200 OK` | |
| Returns an **array** of course objects (same shape as items in `data` array above, including `department` relation). | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Department not found | | |
| --- | |
| ### 3.3 Get Course by ID | |
| ``` | |
| GET /api/courses/:id | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "id": 1, | |
| "departmentId": 3, | |
| "name": "Introduction to CS", | |
| "code": "CS101", | |
| "description": "Fundamentals of programming...", | |
| "credits": 3, | |
| "level": "FRESHMAN", | |
| "syllabusUrl": "https://example.com/syllabus.pdf", | |
| "instructorId": 5, | |
| "taIds": [1, 2], | |
| "status": "ACTIVE", | |
| "createdAt": "2025-01-15T10:00:00Z", | |
| "updatedAt": "2025-01-15T10:00:00Z", | |
| "department": { | |
| "id": 3, | |
| "name": "Computer Science", | |
| "code": "CS" | |
| }, | |
| "prerequisites": [], // CoursePrerequisite[] β loaded relation | |
| "sections": [], // CourseSection[] β loaded relation | |
| "prerequisitesCount": 0, // number β count of prerequisites | |
| "sectionsCount": 2 // number β count of active sections | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Course not found | | |
| --- | |
| ### 3.4 Create Course | |
| ``` | |
| POST /api/courses | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `departmentId` | `number` | β | Must exist | Department to assign course to | | |
| | `name` | `string` | β | β | Course name | | |
| | `code` | `string` | β | 2-10 uppercase alphanumeric (`/^[A-Z0-9]{2,10}$/`) | Unique course code | | |
| | `description` | `string` | β | β | Course description | | |
| | `credits` | `integer` | β | 1β6 | Credit hours | | |
| | `level` | `string` | β | `CourseLevel` enum | Course level | | |
| | `syllabusUrl` | `string` | β | Valid URL | Syllabus URL | | |
| | `instructorId` | `number` | β | Must reference valid user | Instructor user ID | | |
| | `taIds` | `number[]` | β | Array of valid user IDs | TA user IDs | | |
| #### Example Request | |
| ```json | |
| { | |
| "departmentId": 1, | |
| "name": "Introduction to Computer Science", | |
| "code": "CS101", | |
| "description": "Fundamentals of programming and computational thinking.", | |
| "credits": 3, | |
| "level": "FRESHMAN", | |
| "syllabusUrl": "https://example.com/syllabus/cs101.pdf", | |
| "instructorId": 5, | |
| "taIds": [10, 11] | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created course object (same shape as Get Course by ID, without `prerequisitesCount`/`sectionsCount`). | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data / validation failure | | |
| | `404` | Department not found | | |
| | `409` | Course code already exists in department | | |
| --- | |
| ### 3.5 Update Course | |
| ``` | |
| PATCH /api/courses/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID | | |
| #### Request Body (`application/json`) β All fields optional | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `name` | `string` | β | β | Updated course name | | |
| | `description` | `string` | β | β | Updated description | | |
| | `credits` | `integer` | β | 1β6 | Updated credit hours | | |
| | `level` | `string` | β | `CourseLevel` enum | Updated course level | | |
| | `syllabusUrl` | `string` | β | β | Updated syllabus URL | | |
| | `status` | `string` | β | `CourseStatus` enum | Change course status | | |
| | `instructorId` | `number` | β | β | Change instructor | | |
| | `taIds` | `number[]` | β | β | Change TAs | | |
| #### Response `200 OK` | |
| Returns the updated course object. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data | | |
| | `404` | Course not found | | |
| --- | |
| ### 3.6 Delete Course (Soft Delete) | |
| ``` | |
| DELETE /api/courses/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID | | |
| #### Response `204 No Content` | |
| Empty body. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Cannot delete course with active enrollments | | |
| | `404` | Course not found | | |
| --- | |
| ### 3.7 Get Course Prerequisites | |
| ``` | |
| GET /api/courses/:id/prerequisites | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, // number β Prerequisite record ID | |
| "courseId": 5, // number β The course that has this prerequisite | |
| "prerequisiteCourseId": 2, // number β The required prereq course ID | |
| "isMandatory": true, // boolean β Whether this prereq is mandatory | |
| "prerequisiteCourse": { // object β Joined prerequisite course info | |
| "id": 2, | |
| "name": "Programming Basics", | |
| "code": "CS100", | |
| "level": "FRESHMAN" | |
| }, | |
| "createdAt": "2025-01-10T08:00:00Z" // string (ISO 8601) | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 3.8 Add Prerequisite | |
| ``` | |
| POST /api/courses/:id/prerequisites | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID to add prerequisite to | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `prerequisiteCourseId` | `number` | β | ID of the prerequisite course | | |
| | `isMandatory` | `boolean` | β | Whether this prerequisite is mandatory | | |
| #### Example Request | |
| ```json | |
| { | |
| "prerequisiteCourseId": 2, | |
| "isMandatory": true | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created prerequisite record. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Circular dependency detected / Self-prerequisite | | |
| | `404` | Course or prerequisite course not found | | |
| | `409` | Prerequisite already exists | | |
| --- | |
| ### 3.9 Remove Prerequisite | |
| ``` | |
| DELETE /api/courses/:id/prerequisites/:prereqId | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Course ID | | |
| | `prereqId` | `integer` | β | Prerequisite record ID | | |
| #### Response `204 No Content` | |
| Empty body. | |
| --- | |
| ## 4. Course Sections Module | |
| **Base Path**: `/api/sections` | |
| --- | |
| ### 4.1 Get Sections by Course | |
| ``` | |
| GET /api/sections/course/:courseId | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `semesterId` | `integer` | β | β | Filter by semester | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 11, // number β Section ID (bigint) | |
| "courseId": 1, // number β FK to courses | |
| "semesterId": 1, // number β FK to semesters | |
| "sectionNumber": "1", // string β Section number | |
| "maxCapacity": 30, // number β Maximum students | |
| "currentEnrollment": 25, // number β Currently enrolled | |
| "location": "Room A101", // string | null β Room/location | |
| "status": "OPEN", // string β SectionStatus enum | |
| "createdAt": "2025-01-15T10:00:00Z", | |
| "updatedAt": "2025-01-15T10:00:00Z", | |
| "course": { | |
| "id": 1, | |
| "name": "Introduction to CS", | |
| "code": "CS101" | |
| }, | |
| "semester": { | |
| "id": 1, | |
| "name": "Fall 2025", | |
| "startDate": "2025-09-01", | |
| "endDate": "2025-12-15" | |
| }, | |
| "schedules": [ | |
| { | |
| "id": 1, | |
| "dayOfWeek": "MONDAY", // DayOfWeek enum | |
| "startTime": "09:00", // string (HH:mm) | |
| "endTime": "10:30", // string (HH:mm) | |
| "room": "A101", // string | null | |
| "building": "Main Building", // string | null | |
| "scheduleType": "LECTURE" // ScheduleType enum | |
| } | |
| ] | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 4.2 Get Section by ID | |
| ``` | |
| GET /api/sections/:id | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| Same shape as a single item from the array above (includes `course`, `semester`, `schedules` relations). | |
| --- | |
| ### 4.3 Create Section | |
| ``` | |
| POST /api/sections | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| #### Request Body | |
| | Field | Type | Required | Default | Constraints | Description | | |
| |---|---|---|---|---|---| | |
| | `courseId` | `number` | β | β | Must exist | Course to create section for | | |
| | `semesterId` | `number` | β | β | Must exist | Semester ID | | |
| | `sectionNumber` | `integer` | β | Auto-generated | >= 1 | Section number | | |
| | `maxCapacity` | `integer` | β | β | >= 1 | Maximum student capacity | | |
| | `currentEnrollment` | `integer` | β | `0` | >= 0 | Current enrollment count | | |
| | `location` | `string` | β | `null` | β | Room/location | | |
| #### Example Request | |
| ```json | |
| { | |
| "courseId": 1, | |
| "semesterId": 1, | |
| "maxCapacity": 30, | |
| "location": "Room A101" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created section object. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data | | |
| | `404` | Course or semester not found | | |
| | `409` | Section number already exists for this course/semester combo | | |
| --- | |
| ### 4.4 Update Section | |
| ``` | |
| PATCH /api/sections/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` (section owner) | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Section ID | | |
| #### Request Body β All fields optional | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `maxCapacity` | `integer` | β | >= 1, cannot be less than current enrollment | Updated max capacity | | |
| | `currentEnrollment` | `integer` | β | >= 0 | Updated enrollment count | | |
| | `location` | `string` | β | β | Updated location | | |
| | `status` | `string` | β | `SectionStatus` enum | Change section status | | |
| #### Response `200 OK` | |
| Returns the updated section object. | |
| --- | |
| ### 4.5 Update Section Enrollment Count | |
| ``` | |
| PATCH /api/sections/:id/enrollment | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| > Typically called automatically by the enrollment system. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Section ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `currentEnrollment` | `number` | β | New enrollment count | | |
| #### Response `200 OK` | |
| Returns the updated section object with recalculated status. | |
| --- | |
| ## 5. Course Schedules Module | |
| **Base Path**: `/api/schedules` | |
| --- | |
| ### 5.1 Get Schedules by Section | |
| ``` | |
| GET /api/schedules/section/:sectionId | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, // number β Schedule ID | |
| "sectionId": 11, // number β FK to sections | |
| "dayOfWeek": "MONDAY", // string β DayOfWeek enum | |
| "startTime": "09:00", // string β HH:mm format (24-hour) | |
| "endTime": "10:30", // string β HH:mm format (24-hour) | |
| "room": "A101", // string | null | |
| "building": "Main Building", // string | null | |
| "scheduleType": "LECTURE", // string β ScheduleType enum | |
| "createdAt": "2025-01-15T10:00:00Z" | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 5.2 Get Schedule by ID | |
| ``` | |
| GET /api/schedules/:id | |
| ``` | |
| **Auth Required**: β No (Public) | |
| **Roles**: All | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Schedule ID | | |
| #### Response `200 OK` | |
| Returns single schedule object with `section` relation included. | |
| --- | |
| ### 5.3 Create Schedule | |
| ``` | |
| POST /api/schedules/section/:sectionId | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section to add schedule to | | |
| #### Request Body | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `dayOfWeek` | `string` | β | `DayOfWeek` enum | Day of week | | |
| | `startTime` | `string` | β | HH:mm format 24-hour (`/^([0-1][0-9]\|2[0-3]):[0-5][0-9]$/`) | Start time | | |
| | `endTime` | `string` | β | HH:mm format (same regex, must be after startTime) | End time | | |
| | `room` | `string` | β | β | Room number/name | | |
| | `building` | `string` | β | β | Building name | | |
| | `scheduleType` | `string` | β | `ScheduleType` enum | Type of session | | |
| #### Example Request | |
| ```json | |
| { | |
| "dayOfWeek": "MONDAY", | |
| "startTime": "09:00", | |
| "endTime": "10:30", | |
| "room": "A101", | |
| "building": "Main Building", | |
| "scheduleType": "LECTURE" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created schedule object. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid time range (end <= start) or schedule conflict detected | | |
| | `404` | Section not found | | |
| --- | |
| ### 5.4 Delete Schedule | |
| ``` | |
| DELETE /api/schedules/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `it_admin`, `instructor` | |
| #### Response `204 No Content` | |
| Empty body. | |
| --- | |
| ## 6. Assignments Module | |
| **Base Path**: `/api/assignments` | |
| > **All endpoints require JWT authentication** (`@UseGuards(JwtAuthGuard, RolesGuard)`). | |
| --- | |
| ### 6.1 List Assignments | |
| ``` | |
| GET /api/assignments | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `courseId` | `integer` | β | β | Filter by course ID | | |
| | `sectionId` | `integer` | β | β | Filter by section ID | | |
| | `status` | `string` | β | β | Filter by `AssignmentStatus` enum (`draft`, `published`, `closed`, `archived`) | | |
| | `dueBefore` | `string` | β | β | Filter assignments due before this date (ISO 8601) | | |
| | `dueAfter` | `string` | β | β | Filter assignments due after this date (ISO 8601) | | |
| | `search` | `string` | β | β | Search in assignment title (partial match) | | |
| | `page` | `integer` | β | `1` | Page number | | |
| | `limit` | `integer` | β | `10` | Items per page | | |
| | `sortBy` | `string` | β | `createdAt` | Sort field: `dueDate`, `createdAt`, `title` | | |
| | `sortOrder` | `string` | β | `DESC` | Sort order: `ASC` or `DESC` | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "data": [ | |
| { | |
| "id": 3, // number β Assignment ID (bigint) | |
| "courseId": 1, // number β FK to courses | |
| "title": "Homework 1 - Binary Search", // string β Assignment title | |
| "description": "Implement binary search...", // string | null | |
| "instructions": "Submit as .zip file...", // string | null | |
| "maxScore": 100.00, // number (decimal) β Maximum possible score | |
| "weight": 15.00, // number (decimal) β Weight % in final grade | |
| "dueDate": "2025-06-15T23:59:59Z", // string | null β Due date (ISO 8601) | |
| "availableFrom": "2025-06-01T00:00:00Z", // string | null β Available from date | |
| "lateSubmissionAllowed": 0, // number (0 or 1) β 0=false, 1=true | |
| "latePenaltyPercent": 10.00, // number (decimal) β Late penalty % | |
| "submissionType": "file", // string β SubmissionType enum | |
| "maxFileSizeMb": 10, // number β Max file size in MB | |
| "allowedFileTypes": "[\"pdf\",\"zip\"]", // string | null β JSON string of allowed types | |
| "status": "published", // string β AssignmentStatus enum | |
| "createdBy": 5, // number β Creator user ID | |
| "createdAt": "2025-06-01T08:00:00Z", // string (ISO 8601) | |
| "updatedAt": "2025-06-01T08:00:00Z", // string (ISO 8601) | |
| "course": { // object β Joined course info | |
| "id": 1, | |
| "departmentId": 3, | |
| "name": "Introduction to CS", | |
| "code": "CS101", | |
| "description": "...", | |
| "credits": 3, | |
| "level": "FRESHMAN", | |
| "status": "ACTIVE" | |
| } | |
| } | |
| ], | |
| "meta": { | |
| "total": 12, | |
| "page": 1, | |
| "limit": 10, | |
| "totalPages": 2 | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 6.2 Get Assignment Details | |
| ``` | |
| GET /api/assignments/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "id": 3, | |
| "courseId": 1, | |
| "title": "Homework 1 - Binary Search", | |
| "description": "Implement binary search...", | |
| "instructions": "Submit as .zip file...", | |
| "maxScore": 100.00, | |
| "weight": 15.00, | |
| "dueDate": "2025-06-15T23:59:59Z", | |
| "availableFrom": "2025-06-01T00:00:00Z", | |
| "lateSubmissionAllowed": 0, | |
| "latePenaltyPercent": 10.00, | |
| "submissionType": "file", | |
| "maxFileSizeMb": 10, | |
| "allowedFileTypes": "[\"pdf\",\"zip\"]", | |
| "status": "published", | |
| "createdBy": 5, | |
| "createdAt": "2025-06-01T08:00:00Z", | |
| "updatedAt": "2025-06-01T08:00:00Z", | |
| "course": { /* full course object */ }, | |
| "submissions": [ | |
| { | |
| "id": 1, // number β Submission ID | |
| "assignmentId": 3, // number | |
| "userId": 57, // number β Student user ID | |
| "submissionText": "My essay...", // string | null | |
| "submissionLink": "https://...", // string | null | |
| "fileId": 12, // number | null β FK to files | |
| "submissionStatus": "submitted", // string β SubmissionStatus enum | |
| "isLate": 0, // number (0 or 1) | |
| "attemptNumber": 1, // number β Attempt count | |
| "submittedAt": "2025-06-10T14:30:00Z", | |
| "score": null, // number | null β Graded score | |
| "feedback": null, // string | null β Grader feedback | |
| "gradedBy": null, // number | null β Grader user ID | |
| "gradedAt": null, // string | null β Grading timestamp | |
| "user": { | |
| "user_id": 57, | |
| "first_name": "John", | |
| "last_name": "Doe", | |
| "email": "john@example.com" | |
| } | |
| } | |
| ] | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Assignment not found | | |
| --- | |
| ### 6.3 Create Assignment | |
| ``` | |
| POST /api/assignments | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin` | |
| #### Request Body | |
| | Field | Type | Required | Default | Constraints | Description | | |
| |---|---|---|---|---|---| | |
| | `courseId` | `number` | β | β | Must exist | Course ID | | |
| | `title` | `string` | β | β | 3-200 chars | Assignment title | | |
| | `description` | `string` | β | `null` | β | Description | | |
| | `instructions` | `string` | β | `null` | β | Detailed instructions | | |
| | `submissionType` | `string` | β | `file` | `SubmissionType` enum | How students submit | | |
| | `maxScore` | `number` | β | `100` | 0-1000 | Maximum score | | |
| | `weight` | `number` | β | `0` | 0-100 | Weight percentage in final grade | | |
| | `dueDate` | `string` | β | `null` | ISO 8601 datetime | Due date | | |
| | `availableFrom` | `string` | β | `null` | ISO 8601 datetime | Available from date | | |
| | `lateSubmissionAllowed` | `boolean` | β | `false` | β | Allow late submissions | | |
| | `latePenaltyPercent` | `number` | β | `0` | 0-100 | Late penalty % | | |
| | `maxFileSizeMb` | `number` | β | `10` | β | Max file size in MB | | |
| | `allowedFileTypes` | `string` | β | `null` | JSON string array | Allowed file extensions | | |
| | `status` | `string` | β | `draft` | `AssignmentStatus` enum | Initial status | | |
| #### Example Request | |
| ```json | |
| { | |
| "courseId": 1, | |
| "title": "Homework 1 - Binary Search", | |
| "description": "Implement a binary search tree with insert, delete, and search operations.", | |
| "instructions": "Submit your code as a single .zip file. Include a README with compilation instructions.", | |
| "submissionType": "file", | |
| "maxScore": 100, | |
| "weight": 15, | |
| "dueDate": "2025-06-15T23:59:59Z", | |
| "availableFrom": "2025-06-01T00:00:00Z", | |
| "lateSubmissionAllowed": true, | |
| "latePenaltyPercent": 10, | |
| "maxFileSizeMb": 10, | |
| "allowedFileTypes": "[\"pdf\",\"docx\",\"zip\"]", | |
| "status": "draft" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created assignment with joined `course` relation. | |
| --- | |
| ### 6.4 Update Assignment | |
| ``` | |
| PATCH /api/assignments/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Request Body | |
| All fields from `CreateAssignmentDto` are **optional** (uses `PartialType`). | |
| #### Response `200 OK` | |
| Returns the updated assignment object. | |
| --- | |
| ### 6.5 Delete Assignment (Soft Delete) | |
| ``` | |
| DELETE /api/assignments/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Response `200 OK` | |
| Empty body (assignment removed). | |
| --- | |
| ### 6.6 Change Assignment Status | |
| ``` | |
| PATCH /api/assignments/:id/status | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Request Body | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `status` | `string` | β | Must follow: `draft` -> `published` -> `closed` -> `archived` | New status | | |
| > **WARNING β Transition rules**: Only the next status in the chain is allowed. You cannot skip statuses. E.g., you cannot go from `draft` directly to `closed`. | |
| #### Example Request | |
| ```json | |
| { | |
| "status": "published" | |
| } | |
| ``` | |
| #### Response `200 OK` | |
| Returns the updated assignment object. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid status transition | | |
| | `404` | Assignment not found | | |
| --- | |
| ### 6.7 Submit Assignment (Student) | |
| ``` | |
| POST /api/assignments/:id/submit | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `submissionText` | `string` | β | Text content for text submissions | | |
| | `fileId` | `number` | β | File ID from the files module (for file submissions) | | |
| | `submissionLink` | `string` | β | External URL (e.g., GitHub repo link) | | |
| > At least one of `submissionText`, `fileId`, or `submissionLink` should be provided. | |
| #### Example Request | |
| ```json | |
| { | |
| "submissionText": "This is my essay submission about data structures...", | |
| "submissionLink": "https://github.com/student/project" | |
| } | |
| ``` | |
| #### Response `201 Created` (via POST) | |
| ```json | |
| { | |
| "id": 15, // number β Submission ID | |
| "assignmentId": 3, // number | |
| "userId": 57, // number β From JWT | |
| "submissionText": "This is my essay...", | |
| "submissionLink": "https://github.com/student/project", | |
| "fileId": null, | |
| "submissionStatus": "submitted", | |
| "isLate": 0, // number β 0=on-time, 1=late | |
| "attemptNumber": 1, // number β Auto-incremented | |
| "submittedAt": "2025-06-10T14:30:00Z", | |
| "score": null, | |
| "feedback": null, | |
| "gradedBy": null, | |
| "gradedAt": null | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Student not enrolled in course / Late submission not allowed / Assignment not available yet | | |
| | `404` | Assignment not found | | |
| #### Business Rules | |
| - Assignment must be in `published` status | |
| - If `availableFrom` is set, current time must be after it | |
| - If `dueDate` is set and past, `lateSubmissionAllowed` must be `true` (otherwise 400) | |
| - Auto-marks `isLate = 1` if submitted after `dueDate` | |
| - Auto-increments `attemptNumber` | |
| - Student must be enrolled in the assignment's course | |
| --- | |
| ### 6.8 Get My Submission (Student) | |
| ``` | |
| GET /api/assignments/:id/submissions/my | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Response `200 OK` | |
| Returns the student's **latest** submission (highest `attemptNumber`): | |
| ```json | |
| { | |
| "id": 15, | |
| "assignmentId": 3, | |
| "userId": 57, | |
| "submissionText": "My submission...", | |
| "submissionLink": null, | |
| "fileId": 12, | |
| "submissionStatus": "graded", | |
| "isLate": 0, | |
| "attemptNumber": 2, | |
| "submittedAt": "2025-06-10T14:30:00Z", | |
| "score": 85.00, | |
| "feedback": "Well done! Consider improving error handling.", | |
| "gradedBy": 5, | |
| "gradedAt": "2025-06-12T10:00:00Z" | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | No submission found for this student/assignment | | |
| --- | |
| ### 6.9 List All Submissions (Instructor/TA/Admin) | |
| ``` | |
| GET /api/assignments/:id/submissions | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 15, | |
| "assignmentId": 3, | |
| "userId": 57, | |
| "submissionText": "My submission...", | |
| "submissionLink": null, | |
| "fileId": 12, | |
| "submissionStatus": "submitted", | |
| "isLate": 0, | |
| "attemptNumber": 1, | |
| "submittedAt": "2025-06-10T14:30:00Z", | |
| "score": null, | |
| "feedback": null, | |
| "gradedBy": null, | |
| "gradedAt": null, | |
| "user": { | |
| "user_id": 57, | |
| "first_name": "John", | |
| "last_name": "Doe", | |
| "email": "john@example.com" | |
| } | |
| } | |
| ] | |
| ``` | |
| > Returns submissions sorted by `submittedAt` DESC. | |
| --- | |
| ### 6.10 Grade a Submission | |
| ``` | |
| PATCH /api/assignments/:id/submissions/:subId/grade | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| | `subId` | `integer` | β | Submission ID | | |
| #### Request Body | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `score` | `number` | β | >= 0 | Numeric score | | |
| | `feedback` | `string` | β | β | Feedback comments | | |
| #### Example Request | |
| ```json | |
| { | |
| "score": 85, | |
| "feedback": "Well done! Consider improving the conclusion." | |
| } | |
| ``` | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "submissionId": 15, // number β Graded submission ID | |
| "score": 85, // number β Score assigned | |
| "maxScore": 100, // number β Assignment max score | |
| "feedback": "Well done!", // string | undefined | |
| "gradeId": 42 // number β Created grade record ID in central gradebook | |
| } | |
| ``` | |
| > **Side effect**: Also creates a grade record in the central `grades` table with `gradeType = 'assignment'`, `isPublished = true`. | |
| --- | |
| ### 6.11 Upload Assignment Instruction (Google Drive) | |
| ``` | |
| POST /api/assignments/:id/instructions/upload | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin` | |
| **Content-Type**: `multipart/form-data` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `file` | `binary` | β | PDF, DOCX, etc. | The instruction file | | |
| | `title` | `string` | β | Max 255 chars | Instruction title | | |
| | `orderIndex` | `integer` | β | >= 0, default `0` | Sort order | | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "assignmentId": 3, | |
| "driveFile": { | |
| "driveFileId": 123, // number β Internal drive file record ID | |
| "driveId": "1abc...", // string β Google Drive file ID | |
| "fileName": "HW1_Instructions_v1.pdf", | |
| "webViewLink": "https://drive.google.com/file/d/1abc.../view", | |
| "webContentLink": "https://drive.google.com/uc?id=1abc..." | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 6.12 Upload Assignment Submission (Google Drive β Student) | |
| ``` | |
| POST /api/assignments/:id/submissions/upload | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| **Content-Type**: `multipart/form-data` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Assignment ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `file` | `binary` | β | Submission file | | |
| | `submissionText` | `string` | β | Notes/comments | | |
| | `submissionLink` | `string` | β | External link (e.g., GitHub) | | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "submission": { | |
| "id": 16, | |
| "assignmentId": 3, | |
| "userId": 57, | |
| "submissionText": "Completed all tasks.", | |
| "submissionLink": "https://github.com/student/project", | |
| "fileId": null, | |
| "submissionStatus": "submitted", | |
| "isLate": 0, | |
| "attemptNumber": 1, | |
| "submittedAt": "2025-06-10T14:30:00Z", | |
| "score": null, | |
| "feedback": null, | |
| "gradedBy": null, | |
| "gradedAt": null | |
| }, | |
| "driveFile": { | |
| "driveFileId": 124, | |
| "driveId": "1def...", | |
| "fileName": "Assignment_3_Submission_20250610.zip", | |
| "webViewLink": "https://drive.google.com/file/d/1def.../view", | |
| "webContentLink": "https://drive.google.com/uc?id=1def..." | |
| }, | |
| "isLate": false // boolean β Whether submission was late | |
| } | |
| ``` | |
| #### Business Rules | |
| Same as regular submit (section 6.7): must be published, enrolled, respects late submission rules. | |
| --- | |
| ## 7. Labs Module | |
| **Base Path**: `/api/labs` | |
| > **All endpoints require JWT authentication** (`@UseGuards(JwtAuthGuard, RolesGuard)`). | |
| --- | |
| ### 7.1 List Labs | |
| ``` | |
| GET /api/labs | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `courseId` | `integer` | β | β | Filter by course ID | | |
| | `status` | `string` | β | β | Filter by `LabStatus` enum (`draft`, `published`, `closed`, `archived`) | | |
| | `page` | `integer` | β | `1` | Page number (>= 1) | | |
| | `limit` | `integer` | β | `20` | Items per page (1-100) | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "data": [ | |
| { | |
| "id": 1, // number β Lab ID (bigint) | |
| "courseId": 1, // number β FK to courses | |
| "title": "Binary Search Lab", // string β Lab title | |
| "description": "Implement binary...", // string | null | |
| "labNumber": 1, // number | null β Lab sequence number | |
| "dueDate": "2026-04-15T23:59:59Z", // string | null β Due date (ISO 8601) | |
| "availableFrom": "2026-04-01T00:00:00Z", // string | null | |
| "maxScore": 100.00, // number (decimal) β Max score | |
| "weight": 10.00, // number (decimal) β Weight in final grade | |
| "status": "published", // string β LabStatus enum | |
| "createdBy": 5, // number β Creator user ID | |
| "createdAt": "2026-04-01T08:00:00Z", | |
| "updatedAt": "2026-04-01T08:00:00Z", | |
| "course": { | |
| "id": 1, | |
| "departmentId": 3, | |
| "name": "Introduction to CS", | |
| "code": "CS101" | |
| } | |
| } | |
| ], | |
| "meta": { | |
| "total": 8, | |
| "page": 1, | |
| "limit": 20, | |
| "totalPages": 1 | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 7.2 Get Lab by ID | |
| ``` | |
| GET /api/labs/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "id": 1, | |
| "courseId": 1, | |
| "title": "Binary Search Lab", | |
| "description": "Implement binary search in Python", | |
| "labNumber": 1, | |
| "dueDate": "2026-04-15T23:59:59Z", | |
| "availableFrom": "2026-04-01T00:00:00Z", | |
| "maxScore": 100.00, | |
| "weight": 10.00, | |
| "status": "published", | |
| "createdBy": 5, | |
| "createdAt": "2026-04-01T08:00:00Z", | |
| "updatedAt": "2026-04-01T08:00:00Z", | |
| "course": { /* course object */ }, | |
| "instructions": [ | |
| { | |
| "id": 1, // number β Instruction ID | |
| "labId": 1, // number | |
| "fileId": null, // number | null β FK to files | |
| "instructionText": "Step 1: Create a new Python file", // string | null | |
| "orderIndex": 1, // number β Sort order | |
| "createdAt": "2026-04-01T08:00:00Z" | |
| } | |
| ] | |
| } | |
| ``` | |
| --- | |
| ### 7.3 Create Lab | |
| ``` | |
| POST /api/labs | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Request Body | |
| | Field | Type | Required | Default | Constraints | Description | | |
| |---|---|---|---|---|---| | |
| | `courseId` | `integer` | β | β | Must exist | Course ID | | |
| | `title` | `string` | β | β | β | Lab title | | |
| | `description` | `string` | β | `null` | β | Lab description | | |
| | `labNumber` | `integer` | β | `null` | β | Lab sequence number | | |
| | `dueDate` | `string` | β | `null` | ISO 8601 | Due date | | |
| | `availableFrom` | `string` | β | `null` | ISO 8601 | Available from date | | |
| | `maxScore` | `number` | β | `100` | β | Maximum score | | |
| | `weight` | `number` | β | `0` | β | Weight in final grade | | |
| | `status` | `string` | β | `draft` | `LabStatus` enum | Initial status | | |
| #### Example Request | |
| ```json | |
| { | |
| "courseId": 1, | |
| "title": "Binary Search Lab", | |
| "description": "Implement binary search in Python", | |
| "labNumber": 1, | |
| "dueDate": "2026-04-15T23:59:59Z", | |
| "availableFrom": "2026-04-01T00:00:00Z", | |
| "maxScore": 100, | |
| "weight": 10, | |
| "status": "draft" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created lab object. | |
| --- | |
| ### 7.4 Update Lab | |
| ``` | |
| PUT /api/labs/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > **Note**: This endpoint uses **PUT** (full replacement), but since `UpdateLabDto extends PartialType(CreateLabDto)`, all fields are optional. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Request Body | |
| All fields from `CreateLabDto` are **optional** (uses `PartialType`). | |
| #### Response `200 OK` | |
| Returns the updated lab object. | |
| --- | |
| ### 7.5 Delete Lab | |
| ``` | |
| DELETE /api/labs/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin`, `it_admin` | |
| > **Note**: TAs **cannot** delete labs. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `204 No Content` | |
| Empty body. | |
| --- | |
| ### 7.6 Change Lab Status | |
| ``` | |
| PATCH /api/labs/:id/status | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `status` | `string` | β | One of: `draft`, `published`, `closed`, `archived` | | |
| > **Note**: Unlike assignments, lab status changes do **not** enforce strict one-way transitions. Any valid status value can be set. | |
| #### Response `200 OK` | |
| Returns the updated lab object (full Lab entity with relations). | |
| --- | |
| ### 7.7 Get Lab Instructions | |
| ``` | |
| GET /api/labs/:id/instructions | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, // number β Instruction ID | |
| "labId": 1, // number β FK to labs | |
| "fileId": null, // number | null β FK to files table | |
| "instructionText": "Step 1...", // string | null β Markdown text | |
| "orderIndex": 1, // number β Sort order (ascending) | |
| "createdAt": "2026-04-01T08:00:00Z" | |
| }, | |
| { | |
| "id": 2, | |
| "labId": 1, | |
| "fileId": 5, | |
| "instructionText": "See attached PDF", | |
| "orderIndex": 2, | |
| "createdAt": "2026-04-01T08:05:00Z" | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 7.8 Add Instruction to Lab | |
| ``` | |
| POST /api/labs/:id/instructions | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Request Body | |
| | Field | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `instructionText` | `string` | β | `null` | Instruction content (supports markdown) | | |
| | `fileId` | `integer` | β | `null` | FK to files table for attachment | | |
| | `orderIndex` | `integer` | β | `0` | Sort order | | |
| #### Example Request | |
| ```json | |
| { | |
| "instructionText": "Step 1: Create a new Python file called `binary_search.py`", | |
| "orderIndex": 1 | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created instruction object. | |
| --- | |
| ### 7.9 Submit Lab Work (Student) | |
| ``` | |
| POST /api/labs/:id/submit | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `submissionText` | `string` | β | Text/code submission content | | |
| | `fileId` | `integer` | β | File ID from files module | | |
| #### Example Request | |
| ```json | |
| { | |
| "submissionText": "def binary_search(arr, target):\n ...", | |
| "fileId": 10 | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "id": 5, // number β Submission ID | |
| "labId": 1, // number | |
| "userId": 57, // number β From JWT | |
| "submissionText": "def binary...", // string | null | |
| "fileId": 10, // number | null | |
| "submittedAt": "2026-04-10T14:30:00Z", | |
| "isLate": false, // boolean β Auto-detected | |
| "status": "submitted", // string β LabSubmissionStatus | |
| "score": null, // number | null | |
| "feedback": null, // string | null | |
| "gradedBy": null, // number | null | |
| "gradedAt": null // string | null | |
| } | |
| ``` | |
| #### Business Rules | |
| - Auto-detects late submission based on `lab.dueDate` | |
| - Sets `isLate = true` if current time > dueDate | |
| --- | |
| ### 7.10 List Lab Submissions (Instructor/TA/Admin) | |
| ``` | |
| GET /api/labs/:id/submissions | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 5, | |
| "labId": 1, | |
| "userId": 57, | |
| "submissionText": "My solution...", | |
| "fileId": 10, | |
| "submittedAt": "2026-04-10T14:30:00Z", | |
| "isLate": false, | |
| "status": "submitted", | |
| "score": null, | |
| "feedback": null, | |
| "gradedBy": null, | |
| "gradedAt": null, | |
| "user": { | |
| "user_id": 57, | |
| "first_name": "John", | |
| "last_name": "Doe", | |
| "email": "john@example.com" | |
| }, | |
| "file": { | |
| "file_id": 10, | |
| "file_name": "solution.py", | |
| "file_path": "/uploads/...", | |
| "mime_type": "text/x-python" | |
| } | |
| } | |
| ] | |
| ``` | |
| > Sorted by `submittedAt` DESC. | |
| --- | |
| ### 7.11 Get My Lab Submission (Student) | |
| ``` | |
| GET /api/labs/:id/submissions/my | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `200 OK` | |
| Returns an **array** of the student's submissions for this lab (may have multiple), sorted by `submittedAt` DESC. Includes `user` and `file` relations. | |
| ```json | |
| [ | |
| { | |
| "id": 5, | |
| "labId": 1, | |
| "userId": 57, | |
| "submissionText": "...", | |
| "fileId": 10, | |
| "submittedAt": "2026-04-10T14:30:00Z", | |
| "isLate": false, | |
| "status": "graded", | |
| "score": 90.00, | |
| "feedback": "Great work!", | |
| "gradedBy": 5, | |
| "gradedAt": "2026-04-12T10:00:00Z", | |
| "user": { /* student info */ }, | |
| "file": { /* file info or null */ } | |
| } | |
| ] | |
| ``` | |
| > **Note**: Unlike assignments (which returns the latest single submission), labs returns **all** submissions as an array. | |
| --- | |
| ### 7.12 Grade Lab Submission | |
| ``` | |
| PATCH /api/labs/:id/submissions/:subId/grade | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| | `subId` | `integer` | β | Submission ID | | |
| #### Request Body | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `status` | `string` | β | One of: `submitted`, `graded`, `returned`, `resubmit` | Submission status | | |
| | `score` | `number` | β | >= 0 | Score to assign | | |
| | `feedback` | `string` | β | β | Feedback for student | | |
| #### Example Request | |
| ```json | |
| { | |
| "status": "graded", | |
| "score": 85, | |
| "feedback": "Good work! Consider optimizing your code for better performance." | |
| } | |
| ``` | |
| #### Response `200 OK` | |
| Returns the updated submission object with `user`, `file`, and `grader` relations: | |
| ```json | |
| { | |
| "id": 5, | |
| "labId": 1, | |
| "userId": 57, | |
| "submissionText": "...", | |
| "fileId": 10, | |
| "submittedAt": "2026-04-10T14:30:00Z", | |
| "isLate": false, | |
| "status": "graded", | |
| "score": 85.00, | |
| "feedback": "Good work!...", | |
| "gradedBy": 5, | |
| "gradedAt": "2026-04-12T10:00:00Z", | |
| "user": { /* student info */ }, | |
| "file": { /* file info or null */ }, | |
| "grader": { | |
| "user_id": 5, | |
| "first_name": "Prof. Smith", | |
| "last_name": "Smith", | |
| "email": "smith@example.com" | |
| } | |
| } | |
| ``` | |
| > **Side effect**: When `status = 'graded'` and `score` is provided, automatically creates a grade record in the central `grades` table with `gradeType = 'lab'`, `isPublished = true`. | |
| --- | |
| ### 7.13 Mark Lab Attendance | |
| ``` | |
| POST /api/labs/:id/attendance | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `userId` | `integer` | β | Student user ID to mark | | |
| | `attendanceStatus` | `string` | β | One of: `present`, `absent`, `excused`, `late` | | |
| | `notes` | `string` | β | Additional notes | | |
| #### Example Request | |
| ```json | |
| { | |
| "userId": 57, | |
| "attendanceStatus": "present", | |
| "notes": "Arrived on time" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "id": 10, // number β Attendance record ID | |
| "labId": 1, // number | |
| "userId": 57, // number | |
| "attendanceStatus": "present", // string β LabAttendanceStatus | |
| "checkInTime": "2026-04-10T09:00:00Z", // string | null β Auto-set for present/late | |
| "notes": "Arrived on time", // string | null | |
| "markedBy": 5, // number β User who marked (from JWT) | |
| "createdAt": "2026-04-10T09:00:00Z" | |
| } | |
| ``` | |
| #### Business Rules | |
| - If a record already exists for this student+lab, it **updates** the existing record | |
| - `checkInTime` is automatically set when status is `present` or `late` | |
| --- | |
| ### 7.14 Get Lab Attendance | |
| ``` | |
| GET /api/labs/:id/attendance | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 10, | |
| "labId": 1, | |
| "userId": 57, | |
| "attendanceStatus": "present", | |
| "checkInTime": "2026-04-10T09:00:00Z", | |
| "notes": "Arrived on time", | |
| "markedBy": 5, | |
| "createdAt": "2026-04-10T09:00:00Z" | |
| } | |
| ] | |
| ``` | |
| > Sorted by `createdAt` ASC. | |
| --- | |
| ### 7.15 Upload Lab Instruction (Google Drive) | |
| ``` | |
| POST /api/labs/:id/instructions/upload | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| **Content-Type**: `multipart/form-data` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `file` | `binary` | β | PDF, DOCX, etc. | Instruction file | | |
| | `title` | `string` | β | Max 255 chars | Instruction title | | |
| | `orderIndex` | `integer` | β | >= 0, default `0` | Sort order | | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "instruction": { | |
| "id": 3, | |
| "labId": 1, | |
| "instructionText": "Lab 1 - Getting Started Guide", | |
| "orderIndex": 1, | |
| "createdAt": "2026-04-01T08:00:00Z" | |
| }, | |
| "driveFile": { | |
| "driveId": "1abc...", | |
| "fileName": "Lab_1_-_Getting_Started_Guide_v1.pdf", | |
| "webViewLink": "https://drive.google.com/file/d/1abc.../view", | |
| "webContentLink": "https://drive.google.com/uc?id=1abc..." | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 7.16 Upload TA Material (Google Drive) | |
| ``` | |
| POST /api/labs/:id/ta-materials/upload | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| **Content-Type**: `multipart/form-data` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `file` | `binary` | β | β | TA material file | | |
| | `title` | `string` | β | Max 255 chars | Material title | | |
| | `materialType` | `string` | β | One of: `answer_key`, `grading_rubric`, `solution`, `notes` | Type of TA material | | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "driveFile": { | |
| "driveFileId": 125, | |
| "driveId": "1ghi...", | |
| "fileName": "solution_Lab_1_Answer_Key.pdf", | |
| "webViewLink": "https://drive.google.com/file/d/1ghi.../view", | |
| "webContentLink": "https://drive.google.com/uc?id=1ghi..." | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 7.17 Upload Lab Submission (Google Drive β Student) | |
| ``` | |
| POST /api/labs/:id/submissions/upload | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| **Content-Type**: `multipart/form-data` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Lab ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `file` | `binary` | β | Submission file | | |
| | `submissionText` | `string` | β | Notes/comments | | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "submission": { | |
| "id": 6, | |
| "labId": 1, | |
| "userId": 57, | |
| "submissionText": "My implementation uses iterative approach", | |
| "submittedAt": "2026-04-10T14:30:00Z", | |
| "isLate": false, | |
| "status": "submitted", | |
| "score": null, | |
| "feedback": null, | |
| "gradedBy": null, | |
| "gradedAt": null | |
| }, | |
| "driveFile": { | |
| "driveFileId": 126, | |
| "driveId": "1jkl...", | |
| "fileName": "Lab1_Submission_20260410.py", | |
| "webViewLink": "https://drive.google.com/file/d/1jkl.../view", | |
| "webContentLink": "https://drive.google.com/uc?id=1jkl..." | |
| }, | |
| "isLate": false | |
| } | |
| ``` | |
| #### Business Rules | |
| - If a submission already exists for this student+lab, the existing record is **updated** (not duplicated) | |
| - Auto-detects `isLate` based on `lab.dueDate` | |
| --- | |
| ## 8. Role-Based Access Matrix | |
| ### 8.1 Courses | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/courses` (list) | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `GET /api/courses/:id` (details) | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `GET /api/courses/department/:deptId` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `POST /api/courses` (create) | β | β | β | β | β | β | | |
| | `PATCH /api/courses/:id` (update) | β | β | β | β | β | β | | |
| | `DELETE /api/courses/:id` (delete) | β | β | β | β | β | β | | |
| | `GET /api/courses/:id/prerequisites` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `POST /api/courses/:id/prerequisites` | β | β | β | β | β | β | | |
| | `DELETE /api/courses/:id/prerequisites/:id` | β | β | β | β | β | β | | |
| ### 8.2 Course Sections | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/sections/course/:courseId` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `GET /api/sections/:id` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `POST /api/sections` (create) | β | β | β | β | β | β | | |
| | `PATCH /api/sections/:id` (update) | β | β | β | β | β | β | | |
| | `PATCH /api/sections/:id/enrollment` | β | β | β | β | β | β | | |
| ### 8.3 Course Schedules | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/schedules/section/:sectionId` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `GET /api/schedules/:id` | β Public | β Public | β Public | β Public | β Public | β Public | | |
| | `POST /api/schedules/section/:sectionId` | β | β | β | β | β | β | | |
| | `DELETE /api/schedules/:id` | β | β | β | β | β | β | | |
| ### 8.4 Assignments | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/assignments` (list) | β | β | β | β | β | β | | |
| | `GET /api/assignments/:id` (details) | β | β | β | β | β | β | | |
| | `POST /api/assignments` (create) | β | β | β | β | β | β | | |
| | `PATCH /api/assignments/:id` (update) | β | β | β | β | β | β | | |
| | `DELETE /api/assignments/:id` (delete) | β | β | β | β | β | β | | |
| | `PATCH /api/assignments/:id/status` | β | β | β | β | β | β | | |
| | `POST /api/assignments/:id/submit` | β | β | β | β | β | β | | |
| | `GET /api/assignments/:id/submissions/my` | β | β | β | β | β | β | | |
| | `GET /api/assignments/:id/submissions` | β | β | β | β | β | β | | |
| | `PATCH /:id/submissions/:subId/grade` | β | β | β | β | β | β | | |
| | `POST /:id/instructions/upload` | β | β | β | β | β | β | | |
| | `POST /:id/submissions/upload` | β | β | β | β | β | β | | |
| ### 8.5 Labs | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/labs` (list) | β | β | β | β | β | β | | |
| | `GET /api/labs/:id` (details) | β | β | β | β | β | β | | |
| | `POST /api/labs` (create) | β | β | β | β | β | β | | |
| | `PUT /api/labs/:id` (update) | β | β | β | β | β | β | | |
| | `DELETE /api/labs/:id` (delete) | β | β | β | β | β | β | | |
| | `PATCH /api/labs/:id/status` | β | β | β | β | β | β | | |
| | `GET /api/labs/:id/instructions` | β | β | β | β | β | β | | |
| | `POST /api/labs/:id/instructions` | β | β | β | β | β | β | | |
| | `POST /api/labs/:id/submit` | β | β | β | β | β | β | | |
| | `GET /api/labs/:id/submissions` | β | β | β | β | β | β | | |
| | `GET /api/labs/:id/submissions/my` | β | β | β | β | β | β | | |
| | `PATCH /:id/submissions/:subId/grade` | β | β | β | β | β | β | | |
| | `POST /api/labs/:id/attendance` | β | β | β | β | β | β | | |
| | `GET /api/labs/:id/attendance` | β | β | β | β | β | β | | |
| | `POST /:id/instructions/upload` | β | β | β | β | β | β | | |
| | `POST /:id/ta-materials/upload` | β | β | β | β | β | β | | |
| | `POST /:id/submissions/upload` | β | β | β | β | β | β | | |
| ### 8.6 Enrollments | |
| | Endpoint | Student | Instructor | TA | Admin | IT Admin | Dept Head | | |
| |---|:---:|:---:|:---:|:---:|:---:|:---:| | |
| | `GET /api/enrollments/my-courses` | β | β | β | β | β | β | | |
| | `GET /api/enrollments/available` | β | β | β | β | β | β | | |
| | `POST /api/enrollments/register` | β | β | β | β | β | β | | |
| | `DELETE /api/enrollments/:id` | β | β | β | β | β | β | | |
| | `GET /api/enrollments/teaching` | β | β | β | β | β | β | | |
| | `GET /api/enrollments/periods` | β | β | β | β | β | β | | |
| | `GET /api/sections/:sectionId/students` | β | β | β | β | β | β | | |
| | `GET /api/sections/:sectionId/waitlist` | β | β | β | β | β | β | | |
| | `POST /api/enrollments/sections/:sectionId/instructors` | β | β | β | β | β | β | | |
| | `DELETE /api/enrollments/sections/:sectionId/instructors/:id` | β | β | β | β | β | β | | |
| | `GET /api/enrollments/sections/:sectionId/instructors` | β | β | β | β | β | β | | |
| | `POST /api/enrollments/sections/:sectionId/tas` | β | β | β | β | β | β | | |
| | `DELETE /api/enrollments/sections/:sectionId/tas/:id` | β | β | β | β | β | β | | |
| | `GET /api/enrollments/sections/:sectionId/tas` | β | β | β | β | β | β | | |
| > **Note**: `department_head` role currently has **NO access** to courses/assignments/labs features. This role only has access to schedule templates and campus events. | |
| --- | |
| ## 9. Course Enrollment Module | |
| **Base Path**: `/api/enrollments` | |
| > **All endpoints require JWT authentication** (`@UseGuards(JwtAuthGuard, RolesGuard)`). | |
| > This module handles student enrollment in courses, including registration, dropping, available courses with prerequisites validation, and instructor/TA assignment to sections. | |
| ### 9.0 Enrollment Enums | |
| #### EnrollmentStatus | |
| | Value | Description | | |
| |---|---| | |
| | `enrolled` | Student is actively enrolled in the section | | |
| | `waitlisted` | Student is on the waitlist (not currently implemented) | | |
| | `dropped` | Student has dropped/withdrawn from the section | | |
| | `completed` | Student has completed the course with a grade | | |
| | `failed` | Student failed the course | | |
| #### DropReason | |
| | Value | Description | | |
| |---|---| | |
| | `student_request` | Student voluntarily dropped | | |
| | `administrative` | Admin-initiated drop | | |
| | `academic` | Academic reasons (e.g., failed prerequisite) | | |
| | `schedule_conflict` | Schedule conflict detected | | |
| ### 9.0.1 Database Entity | |
| #### `course_enrollments` Table | |
| | Column | DB Type | TS Type | Nullable | Default | Description | | |
| |---|---|---|---|---|---| | |
| | `enrollment_id` | `bigint unsigned` | `number` | β (PK) | Auto | Primary key | | |
| | `user_id` | `bigint unsigned` | `number` | β | β | FK β `users` (student) | | |
| | `section_id` | `bigint unsigned` | `number` | β | β | FK β `course_sections` | | |
| | `program_id` | `bigint unsigned` | `number \| null` | β | `null` | FK β `programs` | | |
| | `enrollment_status` | `enum('enrolled','waitlisted','dropped','completed','failed')` | `EnrollmentStatus` | β | `'enrolled'` | Enrollment status | | |
| | `grade` | `varchar(5)` | `string \| null` | β | `null` | Letter grade (A, A-, B+, etc.) | | |
| | `final_score` | `decimal(5,2)` | `number \| null` | β | `null` | Final numeric score | | |
| | `enrollment_date` | `datetime` | `Date` | β | Auto | When student enrolled | | |
| | `dropped_at` | `datetime` | `Date \| null` | β | `null` | When student dropped | | |
| | `completed_at` | `datetime` | `Date \| null` | β | `null` | When course was completed | | |
| | `updated_at` | `timestamp` | `Date` | β | Auto | Last update timestamp | | |
| **Indexes**: `(user_id, status)`, `(section_id, status)`, unique constraint on `(user_id, section_id)` | |
| --- | |
| ### 9.1 Get My Enrolled Courses (Student) | |
| ``` | |
| GET /api/enrollments/my-courses | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `semester` | `integer` | β | β | Filter by semester ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, // number β Enrollment ID | |
| "userId": 57, // number β Student user ID | |
| "sectionId": 11, // number β Section ID | |
| "status": "enrolled", // string β EnrollmentStatus enum | |
| "grade": null, // string | null β Letter grade | |
| "finalScore": null, // number | null β Final numeric score | |
| "enrollmentDate": "2025-09-01T10:00:00Z", // string β ISO 8601 | |
| "droppedAt": null, // string | null | |
| "completedAt": null, // string | null | |
| "updatedAt": "2025-09-01T10:00:00Z", // string β ISO 8601 | |
| "canDrop": true, // boolean β Whether student can still drop | |
| "dropDeadline": "2025-10-15T23:59:59Z", // string | null β Drop deadline | |
| "course": { | |
| "id": 1, | |
| "name": "Introduction to CS", | |
| "code": "CS101", | |
| "description": "...", | |
| "credits": 3, | |
| "level": "FRESHMAN" | |
| }, | |
| "section": { | |
| "id": 11, | |
| "sectionNumber": "1", | |
| "maxCapacity": 30, | |
| "currentEnrollment": 25, | |
| "location": "Room A101", | |
| "status": "OPEN" | |
| }, | |
| "semester": { | |
| "id": 1, | |
| "name": "Fall 2025", | |
| "startDate": "2025-09-01", | |
| "endDate": "2025-12-15" | |
| }, | |
| "instructor": null, // object | null β Currently disabled | |
| "prerequisites": [ // array β Prerequisites with completion status | |
| { | |
| "id": 1, | |
| "courseId": 1, | |
| "prerequisiteCourseId": 2, | |
| "courseCode": "CS100", | |
| "courseName": "Programming Basics", | |
| "isMandatory": true, | |
| "studentCompleted": true, | |
| "studentGrade": "A" | |
| } | |
| ] | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 9.2 Get Available Courses (Student) | |
| ``` | |
| GET /api/enrollments/available | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| > Returns courses the student **can enroll in**, checking: | |
| > - Prerequisites completed with grade **B- or higher** | |
| > - Section has available seats | |
| > - No schedule conflicts with current enrollments | |
| > - Course is active and not cancelled | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `departmentId` | `integer` | β | β | Filter by department | | |
| | `semesterId` | `integer` | β | β | Filter by semester | | |
| | `search` | `string` | β | β | Search in course name/code | | |
| | `level` | `string` | β | β | Filter by course level | | |
| | `page` | `integer` | β | `1` | Page number | | |
| | `limit` | `integer` | β | `20` | Items per page | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, // number β Course ID | |
| "name": "Introduction to CS", // string | |
| "code": "CS101", // string | |
| "description": "...", // string | null | |
| "credits": 3, // number | |
| "level": "FRESHMAN", // string β CourseLevel enum | |
| "departmentId": 3, // number | |
| "departmentName": "Computer Science", // string | |
| "sections": [ // array β Available sections | |
| { | |
| "id": 11, | |
| "sectionNumber": "1", | |
| "maxCapacity": 30, | |
| "currentEnrollment": 25, | |
| "availableSeats": 5, | |
| "location": "Room A101", | |
| "semesterId": 1, | |
| "semesterName": "Fall 2025" | |
| } | |
| ], | |
| "prerequisites": [ // array β Required prerequisites | |
| { | |
| "id": 1, | |
| "courseId": 1, | |
| "prerequisiteCourseId": 2, | |
| "courseCode": "CS100", | |
| "courseName": "Programming Basics", | |
| "isMandatory": true | |
| } | |
| ], | |
| "canEnroll": true, // boolean β Whether student meets all requirements | |
| "enrollmentStatus": null // string | null β Current enrollment status if already enrolled | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 9.3 Enroll in a Course (Student) | |
| ``` | |
| POST /api/enrollments/register | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` only | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `number` | β | Section ID to enroll in | | |
| #### Example Request | |
| ```json | |
| { | |
| "sectionId": 11 | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns enrollment response object (same shape as items in 9.1 response). | |
| #### Business Rules & Validation | |
| 1. **Already Enrolled**: Cannot enroll if already enrolled in the same section (throws `409`) | |
| 2. **Prerequisites**: All prerequisites must be completed with grade **B- or higher** | |
| 3. **Schedule Conflicts**: No time overlap with current enrollments in the same semester | |
| 4. **Capacity**: If section is full, student is still enrolled (waitlist not implemented) | |
| 5. **Retake Logic**: | |
| - If previously **failed** (grade F): Can retake freely | |
| - If previously **passed** with B- or better and wants to improve: Requires **admin approval** (throws `400` with `RetakeRequiresAdminApprovalException`) | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Prerequisites not met / Schedule conflict / User or section not found | | |
| | `409` | Already enrolled in this section | | |
| --- | |
| ### 9.4 Drop/Withdraw from a Course | |
| ``` | |
| DELETE /api/enrollments/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `student` (own enrollments), `admin` (any enrollment) | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `id` | `integer` | β | Enrollment ID | | |
| #### Request Body (Optional) | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `reason` | `string` | β | Drop reason from `DropReason` enum | | |
| #### Response `200 OK` | |
| Returns the updated enrollment object with `status: "dropped"` and `droppedAt` timestamp. | |
| #### Business Rules | |
| 1. **Permission**: Students can only drop their own enrollments; admins can drop any | |
| 2. **Drop Deadline**: Students can only drop before **50% of semester has elapsed** (admins can override) | |
| 3. **Status Changes**: Enrollment status changes from `enrolled` β `dropped` | |
| 4. **Section Count**: Section's `currentEnrollment` is decremented | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Drop deadline passed / Cannot drop past enrollment | | |
| | `403` | Forbidden β not your enrollment | | |
| | `404` | Enrollment not found | | |
| --- | |
| ### 9.5 Get Teaching Courses (Instructor/TA) | |
| ``` | |
| GET /api/enrollments/teaching | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin` | |
| > Returns all course sections where the user is assigned as an instructor or TA. | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "sectionId": 11, | |
| "courseId": 1, | |
| "course": { | |
| "id": 1, | |
| "name": "Introduction to CS", | |
| "code": "CS101", | |
| "description": "...", | |
| "credits": 3, | |
| "level": "FRESHMAN" | |
| }, | |
| "section": { | |
| "id": 11, | |
| "sectionNumber": "1", | |
| "maxCapacity": 30, | |
| "currentEnrollment": 25, | |
| "location": "Room A101" | |
| }, | |
| "semester": { | |
| "id": 1, | |
| "name": "Fall 2025", | |
| "startDate": "2025-09-01", | |
| "endDate": "2025-12-15" | |
| } | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 9.6 Get Section Students | |
| ``` | |
| GET /api/sections/:sectionId/students | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin` | |
| > Returns all **actively enrolled** students (status = `enrolled`) in a section. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| Returns array of enrollment objects (same shape as 9.1), sorted by `enrollmentDate ASC`. | |
| --- | |
| ### 9.7 Get Section Waitlist | |
| ``` | |
| GET /api/sections/:sectionId/waitlist | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin` | |
| > **Currently Not Implemented**: Returns empty array. Waitlist functionality requires a separate waitlist table. | |
| #### Response `200 OK` | |
| ```json | |
| [] | |
| ``` | |
| --- | |
| ### 9.8 Assign Instructor to Section (Admin) | |
| ``` | |
| POST /api/enrollments/sections/:sectionId/instructors | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Request Body | |
| | Field | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `userId` | `number` | β | β | User ID of instructor (must have `instructor` role) | | |
| | `role` | `string` | β | `primary` | One of: `primary`, `co_instructor`, `guest` | | |
| | `responsibilities` | `string` | β | β | Free-text description | | |
| #### Example Request | |
| ```json | |
| { | |
| "userId": 58, | |
| "role": "primary", | |
| "responsibilities": "Lead lectures, grade assignments" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "id": 1, // number β Instructor assignment ID | |
| "sectionId": 11, | |
| "userId": 58, | |
| "role": "primary", | |
| "responsibilities": "Lead lectures, grade assignments", | |
| "assignedAt": "2025-09-01T10:00:00Z", | |
| "user": { | |
| "userId": 58, | |
| "firstName": "Tarek", | |
| "lastName": "Instructor", | |
| "email": "tarek@example.com" | |
| } | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid user ID or section ID | | |
| | `404` | Section or user not found | | |
| | `409` | Instructor already assigned to this section | | |
| --- | |
| ### 9.9 Get Section Instructors | |
| ``` | |
| GET /api/enrollments/sections/:sectionId/instructors | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `instructor`, `teaching_assistant` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| Returns array of instructor assignment objects (same shape as 9.8 response). | |
| --- | |
| ### 9.10 Remove Instructor from Section (Admin) | |
| ``` | |
| DELETE /api/enrollments/sections/:sectionId/instructors/:assignmentId | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| | `assignmentId` | `integer` | β | Instructor assignment ID | | |
| #### Response `204 No Content` | |
| Empty body. | |
| --- | |
| ### 9.11 Assign TA to Section (Admin) | |
| ``` | |
| POST /api/enrollments/sections/:sectionId/tas | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Request Body | |
| | Field | Type | Required | Description | | |
| |---|---|---|---| | |
| | `userId` | `number` | β | User ID of TA (must have `teaching_assistant` role) | | |
| | `responsibilities` | `string` | β | Free-text description (e.g., "Grading labs, office hours Mon/Wed") | | |
| #### Example Request | |
| ```json | |
| { | |
| "userId": 60, | |
| "responsibilities": "Grade lab submissions, hold office hours on Tuesday" | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "id": 2, // number β TA assignment ID | |
| "sectionId": 11, | |
| "userId": 60, | |
| "responsibilities": "Grade lab submissions, hold office hours on Tuesday", | |
| "assignedAt": "2025-09-01T10:00:00Z", | |
| "user": { | |
| "userId": 60, | |
| "firstName": "John", | |
| "lastName": "TA", | |
| "email": "ta@example.com" | |
| } | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid user ID or section ID | | |
| | `404` | Section or user not found | | |
| | `409` | TA already assigned to this section | | |
| --- | |
| ### 9.12 Get Section TAs | |
| ``` | |
| GET /api/enrollments/sections/:sectionId/tas | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `instructor`, `teaching_assistant` | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| Returns array of TA assignment objects (same shape as 9.11 response). | |
| --- | |
| ### 9.13 Remove TA from Section (Admin) | |
| ``` | |
| DELETE /api/enrollments/sections/:sectionId/tas/:assignmentId | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin` only | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| | `assignmentId` | `integer` | β | TA assignment ID | | |
| #### Response `204 No Content` | |
| Empty body. | |
| --- | |
| ### 9.14 Get Section Instructor Summary | |
| ``` | |
| GET /api/enrollments/section/:sectionId/instructor | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `instructor`, `teaching_assistant`, `student` | |
| > Returns simplified instructor info for a section. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "instructorId": 58, | |
| "instructor": { | |
| "userId": 58, | |
| "fullName": "Tarek Instructor", | |
| "email": "tarek@example.com" | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 9.15 Get Section TA Summaries | |
| ``` | |
| GET /api/enrollments/section/:sectionId/tas | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `admin`, `instructor`, `teaching_assistant`, `student` | |
| > Returns simplified TA info for a section. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `sectionId` | `integer` | β | Section ID | | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "userId": 60, | |
| "fullName": "Tarek TA", | |
| "email": "ta@example.com" | |
| } | |
| ] | |
| ``` | |
| --- | |
| ### 9.16 Get Enrollment Periods | |
| ``` | |
| GET /api/enrollments/periods | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| > Returns semesters with registration date ranges. | |
| #### Response `200 OK` | |
| ```json | |
| [ | |
| { | |
| "id": 1, | |
| "semesterName": "Fall 2025", | |
| "semesterCode": "FA25", | |
| "registrationStart": "2025-08-01T00:00:00Z", | |
| "registrationEnd": "2025-08-25T23:59:59Z", | |
| "semesterStart": "2025-09-01T00:00:00Z", | |
| "semesterEnd": "2025-12-15T23:59:59Z", | |
| "status": "active" | |
| } | |
| ] | |
| ``` | |
| --- | |
| ## 10. Error Handling | |
| ### 9.1 Standard Error Response Shape | |
| ```json | |
| { | |
| "statusCode": 404, | |
| "message": "Assignment not found", | |
| "error": "Not Found" | |
| } | |
| ``` | |
| Or for validation errors (`400`): | |
| ```json | |
| { | |
| "statusCode": 400, | |
| "message": [ | |
| "courseId must be a number", | |
| "title must be longer than or equal to 3 characters" | |
| ], | |
| "error": "Bad Request" | |
| } | |
| ``` | |
| ### 9.2 Common Error Codes | |
| | Status | When | | |
| |---|---| | |
| | `400` | Validation failure, invalid state transition, business rule violation | | |
| | `401` | Missing or invalid JWT token | | |
| | `403` | Insufficient role permissions | | |
| | `404` | Resource not found | | |
| | `409` | Duplicate resource (e.g., course code) | | |
| ### 9.3 Business Rule Errors (400) | |
| | Module | Error | Description | | |
| |---|---|---| | |
| | Courses | Course code exists | `CourseCodeAlreadyExistsException` β code+department must be unique | | |
| | Courses | Circular prerequisite | `CircularPrerequisiteDetectedException` β detected via DFS | | |
| | Courses | Delete with sections | `CannotDeleteCourseWithActiveSectionsException` | | |
| | Assignments | Not published | `AssignmentNotPublishedException` β must be `published` to submit | | |
| | Assignments | Not available yet | `AssignmentNotAvailableYetException` β before `availableFrom` | | |
| | Assignments | Deadline passed | `SubmissionDeadlinePassedException` β past `dueDate` and late not allowed | | |
| | Assignments | Not enrolled | Student not enrolled in the course | | |
| | Assignments | Invalid transition | Status transition not allowed (e.g., `draft` to `closed`) | | |
| | Sections | Section full | `SectionFullException` β enrollment exceeds capacity | | |
| | Sections | Capacity reduction | Cannot reduce capacity below current enrollment | | |
| | Schedules | Invalid time | `InvalidTimeRangeException` β end time <= start time | | |
| | Schedules | Conflict | `ScheduleConflictException` β room/time overlap | | |
| --- | |
| ## 10. Flutter Integration Tips | |
| ### 10.1 HTTP Client Setup | |
| ```dart | |
| // Base configuration | |
| const baseUrl = 'http://your-server:3001'; | |
| // Auth header | |
| final headers = { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': 'Bearer $accessToken', | |
| }; | |
| ``` | |
| ### 10.2 Parsing `lateSubmissionAllowed` | |
| The backend stores this as a MySQL `TINYINT(1)` β it comes back as `0` or `1` (number), not `true`/`false`: | |
| ```dart | |
| final isLateAllowed = (assignment['lateSubmissionAllowed'] as num) == 1; | |
| ``` | |
| ### 10.3 Parsing `allowedFileTypes` | |
| This field is a **JSON string** (not an array). Parse it: | |
| ```dart | |
| final List<String> fileTypes = assignment['allowedFileTypes'] != null | |
| ? List<String>.from(jsonDecode(assignment['allowedFileTypes'])) | |
| : []; | |
| ``` | |
| ### 10.4 Decimal Fields | |
| `maxScore`, `weight`, `latePenaltyPercent`, `score` come back as decimal numbers (may be strings from some DB drivers). Always parse: | |
| ```dart | |
| final maxScore = double.tryParse(assignment['maxScore'].toString()) ?? 100.0; | |
| ``` | |
| ### 10.5 File Uploads (multipart/form-data) | |
| Use `dio` or `http.MultipartRequest`: | |
| ```dart | |
| // Using Dio | |
| final formData = FormData.fromMap({ | |
| 'file': await MultipartFile.fromFile(filePath, filename: fileName), | |
| 'title': 'My submission', | |
| 'submissionText': 'Notes here', | |
| }); | |
| final response = await dio.post( | |
| '$baseUrl/api/assignments/3/submissions/upload', | |
| data: formData, | |
| options: Options(headers: {'Authorization': 'Bearer $token'}), | |
| ); | |
| ``` | |
| ### 10.6 Pagination Helper | |
| ```dart | |
| class PaginatedResponse<T> { | |
| final List<T> data; | |
| final int total; | |
| final int page; | |
| final int limit; | |
| final int totalPages; | |
| bool get hasNextPage => page < totalPages; | |
| bool get hasPreviousPage => page > 1; | |
| } | |
| ``` | |
| ### 10.7 Enum Mapping | |
| ```dart | |
| enum AssignmentStatus { draft, published, closed, archived } | |
| extension AssignmentStatusExt on AssignmentStatus { | |
| String get value => name; // 'draft', 'published', etc. | |
| static AssignmentStatus fromString(String s) => | |
| AssignmentStatus.values.firstWhere((e) => e.name == s); | |
| } | |
| ``` | |
| ### 10.8 Date Handling | |
| All dates are ISO 8601 strings. Parse with: | |
| ```dart | |
| final dueDate = DateTime.parse(assignment['dueDate']); // UTC | |
| final localDueDate = dueDate.toLocal(); // Convert to local timezone | |
| ``` | |
| ### 10.9 Role-Based UI | |
| ```dart | |
| // Show/hide actions based on role | |
| final userRoles = currentUser.roles; // List<String> | |
| final canCreateAssignment = userRoles.any( | |
| (r) => ['instructor', 'admin'].contains(r), | |
| ); | |
| final canSubmit = userRoles.contains('student'); | |
| final canGrade = userRoles.any( | |
| (r) => ['instructor', 'teaching_assistant'].contains(r), | |
| ); | |
| ``` | |
| ### 10.10 Handling `isLate` Differences | |
| | Module | `isLate` Type | Values | | |
| |---|---|---| | |
| | Assignments | `number` (tinyint) | `0` = on-time, `1` = late | | |
| | Labs | `boolean` | `true` / `false` | | |
| ```dart | |
| // Assignments | |
| final isLate = (submission['isLate'] as num) == 1; | |
| // Labs | |
| final isLate = submission['isLate'] as bool; | |
| ``` | |
| --- | |
| ## 11. Course Materials & Video Lectures Module | |
| **Base Path**: `/api/courses/:courseId/materials` | |
| > **All endpoints require JWT authentication** (`@UseGuards(JwtAuthGuard, RolesGuard)`). | |
| > This module handles uploading, viewing, organizing, and managing all course materials β including **video lectures** uploaded to YouTube and **documents** stored on Google Drive. | |
| ### 11.0 Enums Reference (Course Materials) | |
| #### MaterialType | |
| | Value | Description | | |
| |---|---| | |
| | `lecture` | Lecture content (notes, handouts) | | |
| | `slide` | Presentation slides | | |
| | `video` | Video content (YouTube integration) | | |
| | `reading` | Reading material | | |
| | `link` | External link | | |
| | `document` | Generic document | | |
| | `other` | Uncategorized material | | |
| #### OrganizationType (for Course Structure) | |
| | Value | Description | | |
| |---|---| | |
| | `lecture` | Main lecture content | | |
| | `section` | Discussion or tutorial section | | |
| | `lab` | Hands-on lab session | | |
| | `tutorial` | Tutorial session | | |
| ### 11.0.1 Database Entities | |
| #### `course_materials` Table | |
| | Column | DB Type | TS Type | Nullable | Default | Description | | |
| |---|---|---|---|---|---| | |
| | `material_id` | `bigint unsigned` | `number` | β (PK) | Auto | Primary key | | |
| | `course_id` | `bigint unsigned` | `number` | β | β | FK β `courses` | | |
| | `file_id` | `bigint unsigned` | `number \| null` | β | `null` | FK β `files` (local files) | | |
| | `drive_file_id` | `bigint unsigned` | `number \| null` | β | `null` | FK β `drive_files` (Google Drive) | | |
| | `material_type` | `enum('lecture','slide','video','reading','link','document','other')` | `MaterialType` | β | `'document'` | Type of material | | |
| | `title` | `varchar(255)` | `string` | β | β | Material title | | |
| | `description` | `text` | `string \| null` | β | `null` | Description | | |
| | `external_url` | `varchar(500)` | `string \| null` | β | `null` | External URL (YouTube embed URL, Google Drive link, etc.) | | |
| | `youtube_video_id` | `varchar(50)` | `string \| null` | β | `null` | YouTube video ID (for video materials) | | |
| | `order_index` | `int` | `number` | β | `0` | Sort order within week | | |
| | `week_number` | `int` | `number \| null` | β | `null` | Week number for organizing by course week | | |
| | `view_count` | `int` | `number` | β | `0` | Number of times viewed | | |
| | `download_count` | `int` | `number` | β | `0` | Number of times downloaded | | |
| | `uploaded_by` | `bigint unsigned` | `number` | β | β | FK β `users` (creator) | | |
| | `is_published` | `tinyint` | `boolean` | β | `0` | `0` = draft (hidden), `1` = published (visible to students) | | |
| | `published_at` | `timestamp` | `Date \| null` | β | `null` | When material was first published | | |
| | `created_at` | `timestamp` | `Date` | β | Auto | Creation timestamp | | |
| | `updated_at` | `timestamp` | `Date` | β | Auto | Last update timestamp | | |
| **Indexes**: `course_id`, `uploaded_by`, `material_type`, `week_number` | |
| #### `lecture_sections_labs` Table (Course Structure) | |
| | Column | DB Type | TS Type | Nullable | Default | Description | | |
| |---|---|---|---|---|---| | |
| | `organization_id` | `bigint unsigned` | `number` | β (PK) | Auto | Primary key | | |
| | `course_id` | `bigint unsigned` | `number` | β | β | FK β `courses` | | |
| | `material_id` | `bigint unsigned` | `number \| null` | β | `null` | FK β `course_materials` | | |
| | `organization_type` | `enum('lecture','section','lab','tutorial')` | `OrganizationType \| null` | β | `null` | Type of content organization | | |
| | `title` | `varchar(255)` | `string` | β | β | Structure item title | | |
| | `week_number` | `int` | `number \| null` | β | `null` | Week number | | |
| | `order_index` | `int` | `number` | β | `0` | Sort order | | |
| | `description` | `text` | `string \| null` | β | `null` | Description | | |
| | `created_at` | `timestamp` | `Date` | β | Auto | Creation timestamp | | |
| | `updated_at` | `timestamp` | `Date` | β | Auto | Last update timestamp | | |
| **Indexes**: `course_id`, `week_number` | |
| --- | |
| ### 11.1 List Course Materials | |
| ``` | |
| GET /api/courses/:courseId/materials | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users (role-based visibility filtering) | |
| #### Role-Based Visibility | |
| | Role | Can See | | |
| |---|---| | |
| | `student` | Only **published** materials (`is_published = 1`) | | |
| | `instructor` | All materials (including drafts) | | |
| | `teaching_assistant` | All materials (including drafts) | | |
| | `admin` / `it_admin` | All materials (including drafts) | | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `materialType` | `string` | β | β | Filter by `MaterialType` enum (`lecture`, `slide`, `video`, `reading`, `link`, `document`, `other`) | | |
| | `weekNumber` | `integer` | β | β | Filter by week number | | |
| | `isPublished` | `boolean` | β | β | Filter by visibility. Only effective for `instructor`, `ta`, `admin` roles. Ignored for students. | | |
| | `search` | `string` | β | β | Search in title and description (partial match / `LIKE`) | | |
| | `sortBy` | `string` | β | `orderIndex` | Sort field: `createdAt`, `title`, `orderIndex` | | |
| | `sortOrder` | `string` | β | `ASC` | Sort order: `ASC` or `DESC` | | |
| | `page` | `integer` | β | `1` | Page number (1-indexed, min: 1) | | |
| | `limit` | `integer` | β | `10` | Items per page (1-100) | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "data": [ | |
| { | |
| "materialId": 1, // number (bigint) β Material ID (PK) | |
| "courseId": 1, // number β FK to courses | |
| "fileId": null, // number | null β FK to local files | |
| "driveFileId": 123, // number | null β FK to drive_files | |
| "materialType": "video", // string β MaterialType enum | |
| "title": "Lecture 1: Introduction to DS", // string β Material title (max 255) | |
| "description": "Covers basics of...", // string | null β Description | |
| "externalUrl": "https://www.youtube.com/embed/abc123", // string | null β YouTube embed URL or Drive view URL | |
| "youtubeVideoId": "abc123", // string | null β YouTube video ID | |
| "orderIndex": 0, // number β Sort position | |
| "weekNumber": 1, // number | null β Week number | |
| "viewCount": 42, // number β Times viewed | |
| "downloadCount": 10, // number β Times downloaded | |
| "uploadedBy": 5, // number β Creator user ID | |
| "isPublished": true, // boolean β Visibility state (tinyint 0/1 mapped to boolean) | |
| "publishedAt": "2025-06-01T10:00:00Z", // string | null β ISO 8601 timestamp | |
| "createdAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "updatedAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "course": { // object β Joined course relation | |
| "id": 1, | |
| "departmentId": 3, | |
| "name": "Introduction to CS", | |
| "code": "CS101" | |
| }, | |
| "file": null, // object | null β Joined local file relation | |
| "uploader": { // object β Joined uploader user relation | |
| "user_id": 5, | |
| "first_name": "Dr. Jane", | |
| "last_name": "Smith", | |
| "email": "jane@example.com" | |
| } | |
| } | |
| ], | |
| "meta": { | |
| "total": 25, // number β Total matching records | |
| "page": 1, // number β Current page | |
| "limit": 10, // number β Items per page | |
| "totalPages": 3 // number β Total pages | |
| } | |
| } | |
| ``` | |
| --- | |
| ### 11.2 Get Material by ID | |
| ``` | |
| GET /api/courses/:courseId/materials/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| > **Students** can only view materials where `isPublished = true`. Requesting an unpublished material as a student returns `404 Not Found`. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Response `200 OK` | |
| Returns a single material object (same shape as items in 11.1 `data` array, including `course`, `file`, and `uploader` relations). | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `401` | Unauthorized β missing or invalid token | | |
| | `404` | Material not found (or unpublished material accessed by student) | | |
| --- | |
| ### 11.3 Create Course Material | |
| ``` | |
| POST /api/courses/:courseId/materials | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > **Authorization**: Non-admin users must be **assigned to the course** (as instructor of a section or TA of a section). Unassigned instructors/TAs receive `403 Forbidden`. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `title` | `string` | β | Max 255 chars, non-empty | Material title | | |
| | `materialType` | `string` | β | `MaterialType` enum | Type of material | | |
| | `description` | `string` | β | β | Material description | | |
| | `fileId` | `number` | β | Must reference valid file ID | File ID from the Files module | | |
| | `externalUrl` | `string` | β | Max 500 chars | External URL (YouTube link, external resource, etc.) | | |
| | `orderIndex` | `number` | β | Min 0 | Sort order for display | | |
| | `weekNumber` | `number` | β | Min 1 | Week number for content organization | | |
| | `isPublished` | `boolean` | β | β | Publish immediately (`true`) or save as draft (`false`). **Default: `false`** | | |
| #### Example Request | |
| ```json | |
| { | |
| "title": "Lecture 1: Introduction to Programming", | |
| "materialType": "video", | |
| "description": "Introduction to basic programming concepts.", | |
| "externalUrl": "https://www.youtube.com/watch?v=example", | |
| "weekNumber": 1, | |
| "orderIndex": 0, | |
| "isPublished": false | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created material object (same shape as items in 11.1 response without `course`/`file`/`uploader` relations β raw entity). | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data (validation failure) | | |
| | `403` | Forbidden β user not assigned to this course | | |
| --- | |
| ### 11.4 Bulk Create Materials | |
| ``` | |
| POST /api/courses/:courseId/materials/bulk | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > Maximum **50 materials** per request. Non-admin users must be assigned to the course. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `materials` | `CreateMaterialDto[]` | β | 1-50 items, validated individually | Array of material objects (same shape as 11.3 request body) | | |
| #### Example Request | |
| ```json | |
| { | |
| "materials": [ | |
| { | |
| "title": "Lecture 1: Introduction", | |
| "materialType": "lecture", | |
| "description": "Introduction to the course", | |
| "weekNumber": 1 | |
| }, | |
| { | |
| "title": "Lecture 2: Fundamentals", | |
| "materialType": "lecture", | |
| "description": "Fundamentals of the subject", | |
| "weekNumber": 1 | |
| } | |
| ] | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "message": "Successfully created 2 materials", // string β Success message | |
| "count": 2, // number β Count of created items | |
| "data": [ // CourseMaterial[] β Created material objects | |
| { | |
| "materialId": 10, | |
| "courseId": 1, | |
| "title": "Lecture 1: Introduction", | |
| "materialType": "lecture", | |
| "description": "Introduction to the course", | |
| "weekNumber": 1, | |
| "orderIndex": 0, | |
| "isPublished": false, | |
| "uploadedBy": 5, | |
| "createdAt": "2025-06-01T08:00:00Z", | |
| "updatedAt": "2025-06-01T08:00:00Z" | |
| } | |
| ] | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data β minimum 1 material required, maximum 50 allowed | | |
| | `403` | Forbidden β user not assigned to this course | | |
| --- | |
| ### 11.5 Upload Video Material (YouTube) | |
| ``` | |
| POST /api/courses/:courseId/materials/video | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| **Content-Type**: `multipart/form-data` | |
| > **This is the primary endpoint for uploading video lectures.** | |
| > The backend uploads the video to YouTube as **unlisted**, then creates a `course_materials` record with the YouTube embed URL and video ID. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `video` | `binary (file)` | β | Formats: `mp4`, `avi`, `mov`, `webm`, `mkv`, `flv`, `wmv` | Video file to upload | | |
| | `title` | `string` | β | Max 255 chars, non-empty | Video title | | |
| | `description` | `string` | β | β | Video description | | |
| | `tags` | `string[]` | β | Array of strings | Tags for the YouTube video | | |
| | `weekNumber` | `integer` | β | 1β52 | Week to assign the video to | | |
| | `orderIndex` | `integer` | β | Min 0, default: `0` | Sort order within the week | | |
| | `isPublished` | `boolean` | β | Default: `false` | Publish immediately or save as draft | | |
| #### Upload Flow (Internal) | |
| 1. Backend validates user is authorized for this course | |
| 2. Video file buffer is uploaded to **YouTube** (privacy: `unlisted`) | |
| 3. YouTube returns `videoId` and URL | |
| 4. A `course_materials` record is created with: | |
| - `materialType` = `video` | |
| - `externalUrl` = `https://www.youtube.com/embed/{videoId}` | |
| - `youtubeVideoId` = the YouTube video ID | |
| - Other metadata (`weekNumber`, `orderIndex`, `isPublished`) | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "materialId": 1, // number β Created material ID | |
| "courseId": 1, // number β Course ID | |
| "title": "Lecture 1: Introduction", // string β Video title | |
| "materialType": "video", // string β Always "video" | |
| "description": "This covers the basics...", // string | null | |
| "externalUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ", // string β YouTube embed URL | |
| "youtubeVideoId": "dQw4w9WgXcQ", // string β YouTube video ID | |
| "weekNumber": 1, // number | null | |
| "orderIndex": 0, // number | |
| "isPublished": false, // boolean | |
| "publishedAt": null, // string | null | |
| "uploadedBy": 5, // number | |
| "viewCount": 0, // number | |
| "downloadCount": 0, // number | |
| "createdAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "updatedAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "youtubeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // string β Full YouTube watch URL (appended by service) | |
| "embedUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ" // string β Embed URL for iframe (appended by service) | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | No video file provided / YouTube upload failed / Invalid input data | | |
| | `403` | Forbidden β user not assigned to this course | | |
| | `413` | Video file too large (YouTube account limits apply) | | |
| --- | |
| ### 11.6 Upload Document Material (Google Drive) | |
| ``` | |
| POST /api/courses/:courseId/materials/document | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| **Content-Type**: `multipart/form-data` | |
| > Uploads a document to **Google Drive** in the appropriate course folder hierarchy and creates a material record. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `document` | `binary (file)` | β | Formats: `pdf`, `ppt`, `pptx`, `doc`, `docx`, `xls`, `xlsx`, `txt`, `md`, `zip` | Document file to upload | | |
| | `title` | `string` | β | Max 255 chars, non-empty | Document title | | |
| | `description` | `string` | β | β | Document description | | |
| | `materialType` | `string` | β | `MaterialType` enum, default: `document` | Document type: `lecture`, `slide`, `reading`, `document`, `link` | | |
| | `weekNumber` | `integer` | β | 1β52 | Week to assign the document to | | |
| | `orderIndex` | `integer` | β | Min 0, default: `0` | Sort order within the week | | |
| | `isPublished` | `boolean` | β | Default: `false` | Publish immediately or save as draft | | |
| #### Folder Placement Rules | |
| | `materialType` value | Google Drive folder target | | |
| |---|---| | |
| | `lecture` | `Course/Lectures/` | | |
| | `slide` | `Course/Lectures/` (slides grouped with lectures) | | |
| | `reading` | `Course/General/` | | |
| | `document` | `Course/General/` | | |
| | `link` | `Course/General/` | | |
| | _other_ | `Course/General/` | | |
| #### File Naming Convention | |
| Files are renamed following the pattern: | |
| ``` | |
| {WeekNN_}{SafeTitle}_v1.{ext} | |
| ``` | |
| - Example: `Week01_Introduction_to_Data_Structures_v1.pdf` | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "materialId": 2, // number β Created material ID | |
| "courseId": 1, // number | |
| "title": "Week 1 Lecture Notes", // string | |
| "materialType": "lecture", // string β MaterialType enum | |
| "description": "Comprehensive lecture notes covering the basics.", // string | null | |
| "externalUrl": "https://drive.google.com/file/d/abc123/view", // string β Google Drive view URL | |
| "driveFileId": 123, // number β FK to drive_files table | |
| "weekNumber": 1, // number | null | |
| "orderIndex": 0, // number | |
| "isPublished": false, // boolean | |
| "publishedAt": null, // string | null | |
| "uploadedBy": 5, // number | |
| "createdAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "updatedAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "driveId": "abc123", // string β Google Drive file ID (appended) | |
| "driveViewUrl": "https://drive.google.com/file/d/abc123/view", // string β View URL (appended) | |
| "driveDownloadUrl": "https://drive.google.com/uc?id=abc123&export=download", // string β Download URL (appended) | |
| "fileName": "Week01_Week_1_Lecture_Notes_v1.pdf" // string β Generated file name (appended) | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | No document file / Google Drive upload failed / Invalid input data | | |
| | `403` | Forbidden β user not assigned to this course | | |
| --- | |
| ### 11.7 Update Material | |
| ``` | |
| PUT /api/courses/:courseId/materials/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > **Ownership**: Non-admin users can only update materials **they uploaded** (`uploadedBy` must match the requesting user). Admins can update any material. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Request Body (`application/json`) β All fields optional (uses `PartialType` of `CreateMaterialDto`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `title` | `string` | β | Max 255 chars | Updated title | | |
| | `materialType` | `string` | β | `MaterialType` enum | Updated type | | |
| | `description` | `string` | β | β | Updated description | | |
| | `fileId` | `number` | β | Must exist | Updated file reference | | |
| | `externalUrl` | `string` | β | Max 500 chars | Updated external URL | | |
| | `orderIndex` | `number` | β | Min 0 | Updated sort order | | |
| | `weekNumber` | `number` | β | Min 1 | Updated week number | | |
| | `isPublished` | `boolean` | β | β | Updated visibility. If changing from `false` β `true`, `publishedAt` is set automatically. | | |
| #### Response `200 OK` | |
| Returns the updated material object (with `course`, `file`, `uploader` relations). | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `403` | Forbidden β you can only update materials you uploaded (non-admin) | | |
| | `404` | Material not found | | |
| --- | |
| ### 11.8 Delete Material | |
| ``` | |
| DELETE /api/courses/:courseId/materials/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > **Ownership**: Non-admin users can only delete materials **they uploaded**. Admins can delete any material. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "message": "Material deleted successfully" // string | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `403` | Forbidden β you can only delete materials you uploaded (non-admin) | | |
| | `404` | Material not found | | |
| --- | |
| ### 11.9 Toggle Material Visibility | |
| ``` | |
| PATCH /api/courses/:courseId/materials/:id/visibility | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `teaching_assistant`, `admin`, `it_admin` | |
| > Controls whether a material is visible to students. Non-admin users can only toggle visibility of materials **they uploaded**. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `isPublished` | `boolean` | β | β | `true` = visible to students, `false` = draft (hidden) | | |
| #### Example Request | |
| ```json | |
| { | |
| "isPublished": true | |
| } | |
| ``` | |
| #### Response `200 OK` | |
| Returns the updated material object (full entity with `course`, `file`, `uploader` relations). `publishedAt` is set to current timestamp on first publish, preserved on subsequent toggles. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `403` | Forbidden β you can only change visibility of materials you uploaded (non-admin) | | |
| | `404` | Material not found | | |
| --- | |
| ### 11.10 Download Material | |
| ``` | |
| GET /api/courses/:courseId/materials/:id/download | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| > Students can only download **published** materials. Increments `downloadCount` on each successful call. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "material": { // object β Full material entity | |
| "materialId": 1, | |
| "title": "Lecture Notes Week 1", | |
| "materialType": "lecture", | |
| "fileId": 12, | |
| "externalUrl": null, | |
| "viewCount": 42, | |
| "downloadCount": 11 | |
| // ... full material object fields | |
| }, | |
| "file": { // object β Associated file entity | |
| "fileId": 12, | |
| "fileName": "lecture_notes_w1.pdf", | |
| "filePath": "/uploads/materials/lecture_notes_w1.pdf", | |
| "fileSize": 2048576, | |
| "mimeType": "application/pdf" | |
| // ... full file object fields | |
| }, | |
| "downloadUrl": "/uploads/materials/lecture_notes_w1.pdf" // string β Direct download path | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Material does not have a downloadable file (`fileId` is null) | | |
| | `404` | Material or associated file not found | | |
| --- | |
| ### 11.11 Track Material View | |
| ``` | |
| POST /api/courses/:courseId/materials/:id/view | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| > Records that a user viewed a material. Increments `viewCount` counter. Used for analytics and engagement tracking. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Request Body | |
| _None_ (empty body) | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "message": "View tracked successfully", // string | |
| "materialId": 1, // number β Material ID | |
| "viewCount": 43 // number β New total view count | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Material not found | | |
| --- | |
| ### 11.12 Get Embed URL (Video Materials) | |
| ``` | |
| GET /api/courses/:courseId/materials/:id/embed | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| > For **video** materials only. Returns the YouTube embed URL and ready-to-use iframe HTML. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Material ID | | |
| #### Response `200 OK` (YouTube video) | |
| ```json | |
| { | |
| "videoId": "dQw4w9WgXcQ", // string β YouTube video ID | |
| "embedUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ", // string β Embed URL for iframe | |
| "iframeHtml": "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" frameborder=\"0\" allowfullscreen></iframe>" // string β Ready-to-use iframe HTML | |
| } | |
| ``` | |
| #### Response `200 OK` (Non-YouTube external URL) | |
| ```json | |
| { | |
| "externalUrl": "https://example.com/video.mp4" // string β Raw external URL | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Material is not a video (`materialType !== 'video'`) or has no external URL | | |
| | `404` | Material not found | | |
| --- | |
| ## 12. Course Structure Module | |
| **Base Path**: `/api/courses/:courseId/structure` | |
| > **All endpoints require JWT authentication** (`@UseGuards(JwtAuthGuard, RolesGuard)`). | |
| > This module provides the organizational layer for course content β mapping materials into weeks, lectures, sections, and labs. | |
| --- | |
| ### 12.1 Get Course Structure | |
| ``` | |
| GET /api/courses/:courseId/structure | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "data": [ // LectureSectionLab[] β Flat list of all structure items | |
| { | |
| "organizationId": 1, // number β Structure item ID (PK) | |
| "courseId": 1, // number β FK to courses | |
| "materialId": 5, // number | null β FK to course_materials | |
| "organizationType": "lecture", // string | null β OrganizationType enum | |
| "title": "Week 1: Introduction", // string β Item title | |
| "weekNumber": 1, // number | null β Week number | |
| "orderIndex": 0, // number β Sort position | |
| "description": "Overview of course", // string | null | |
| "createdAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "updatedAt": "2025-06-01T08:00:00Z", // string β ISO 8601 | |
| "material": { // object | null β Joined material relation | |
| "materialId": 5, | |
| "title": "Lecture 1 Slides", | |
| "materialType": "slide", | |
| "externalUrl": "https://drive.google.com/...", | |
| "youtubeVideoId": null, | |
| "weekNumber": 1, | |
| "viewCount": 20, | |
| "isPublished": true | |
| } | |
| } | |
| ], | |
| "byWeek": { // Record<number, LectureSectionLab[]> β Grouped by week | |
| "0": [ /* items without a week */ ], | |
| "1": [ /* week 1 items */ ], | |
| "2": [ /* week 2 items */ ] | |
| } | |
| } | |
| ``` | |
| > **Sorting**: Items sorted by `weekNumber ASC`, then `orderIndex ASC`. | |
| --- | |
| ### 12.2 Get Structure Item by ID | |
| ``` | |
| GET /api/courses/:courseId/structure/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: All authenticated users | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Structure item ID | | |
| #### Response `200 OK` | |
| Returns a single structure item with `course` and `material` relations joined. | |
| ```json | |
| { | |
| "organizationId": 1, | |
| "courseId": 1, | |
| "materialId": 5, | |
| "organizationType": "lecture", | |
| "title": "Week 1: Introduction", | |
| "weekNumber": 1, | |
| "orderIndex": 0, | |
| "description": "Overview of the course", | |
| "createdAt": "2025-06-01T08:00:00Z", | |
| "updatedAt": "2025-06-01T08:00:00Z", | |
| "course": { | |
| "id": 1, | |
| "name": "Introduction to CS", | |
| "code": "CS101" | |
| }, | |
| "material": { | |
| "materialId": 5, | |
| "title": "Lecture 1 Slides", | |
| "materialType": "slide" | |
| } | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Structure item not found | | |
| --- | |
| ### 12.3 Create Structure Item | |
| ``` | |
| POST /api/courses/:courseId/structure | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin`, `it_admin` | |
| > **Note**: TAs **cannot** create structure items. Non-admin instructors must be assigned to the course. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `title` | `string` | β | Max 255 chars, non-empty | Structure item title | | |
| | `organizationType` | `string` | β | `OrganizationType` enum (`lecture`, `section`, `lab`, `tutorial`) | Type of content | | |
| | `description` | `string` | β | β | Description of this content section | | |
| | `weekNumber` | `number` | β | Min 1 | Week number for this content | | |
| | `orderIndex` | `number` | β | Min 0. **Auto-calculated** if omitted (max existing + 1 within the week). | Sort order | | |
| | `materialId` | `number` | β | Must reference valid material ID | Link to an existing material | | |
| #### Example Request | |
| ```json | |
| { | |
| "title": "Week 1: Introduction to Data Structures", | |
| "organizationType": "lecture", | |
| "description": "Introduction to the course and basic concepts", | |
| "weekNumber": 1, | |
| "materialId": 5 | |
| } | |
| ``` | |
| #### Response `201 Created` | |
| Returns the created structure item entity. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data | | |
| | `403` | Forbidden β only instructors/admins; must be assigned to course | | |
| --- | |
| ### 12.4 Update Structure Item | |
| ``` | |
| PUT /api/courses/:courseId/structure/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin`, `it_admin` | |
| > All fields from `CreateStructureDto` are optional (`PartialType`). TAs cannot update structure. Non-admin instructors must be assigned to the course. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Structure item ID | | |
| #### Request Body (`application/json`) β All fields optional | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `title` | `string` | β | Max 255 chars | Updated title | | |
| | `organizationType` | `string` | β | `OrganizationType` enum | Updated type | | |
| | `description` | `string` | β | β | Updated description | | |
| | `weekNumber` | `number` | β | Min 1 | Updated week number | | |
| | `orderIndex` | `number` | β | Min 0 | Updated sort order | | |
| | `materialId` | `number` | β | Valid material ID | Updated material link | | |
| #### Response `200 OK` | |
| Returns the updated structure item entity. | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `403` | Forbidden β only instructors/admins; must be assigned | | |
| | `404` | Structure item not found | | |
| --- | |
| ### 12.5 Delete Structure Item | |
| ``` | |
| DELETE /api/courses/:courseId/structure/:id | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin`, `it_admin` | |
| > **Important**: Deleting a structure item does **NOT** delete associated materials. The material remains in the `course_materials` table. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| | `id` | `integer` | β | Structure item ID | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "message": "Structure item deleted successfully" // string | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `403` | Forbidden β only instructors/admins; must be assigned | | |
| | `404` | Structure item not found | | |
| --- | |
| ### 12.6 Reorder Structure Items | |
| ``` | |
| PATCH /api/courses/:courseId/structure/reorder | |
| ``` | |
| **Auth Required**: β Yes | |
| **Roles**: `instructor`, `admin`, `it_admin` | |
| > Reorders multiple structure items in a single request. All IDs must belong to the specified course. | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `courseId` | `integer` | β | Course ID | | |
| #### Request Body (`application/json`) | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `orderIds` | `number[]` | β | Non-empty array of existing structure item IDs | Array of item IDs in the desired new order | | |
| #### Example Request | |
| ```json | |
| { | |
| "orderIds": [3, 1, 2, 4] | |
| } | |
| ``` | |
| > This would set: | |
| > - ID 3 β `orderIndex: 0` | |
| > - ID 1 β `orderIndex: 1` | |
| > - ID 2 β `orderIndex: 2` | |
| > - ID 4 β `orderIndex: 3` | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "message": "Structure reordered successfully" // string | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid input data (empty array, non-number values) | | |
| | `403` | Forbidden β only instructors/admins; must be assigned | | |
| | `404` | Some structure items not found or do not belong to this course | | |
| --- | |
| ## 13. YouTube Integration Module | |
| **Base Path**: `/youtube` | |
| > This module provides **standalone YouTube API integration** for OAuth authentication, video upload, search, and video details retrieval. | |
| > β οΈ These endpoints are primarily used internally by the Course Materials module (section 11.5) but are also exposed directly for advanced use cases. | |
| --- | |
| ### 13.1 Get YouTube Auth URL | |
| ``` | |
| GET /youtube/auth | |
| ``` | |
| **Auth Required**: β No (Public endpoint) | |
| **Roles**: None | |
| > Returns the Google OAuth2 authorization URL. The user must visit this URL to grant YouTube upload permission. | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "authUrl": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.upload&response_type=code&client_id=xxx&redirect_uri=xxx&prompt=consent" | |
| // string β Full OAuth2 URL to redirect the user to | |
| } | |
| ``` | |
| --- | |
| ### 13.2 Handle YouTube OAuth Callback | |
| ``` | |
| GET /youtube/callback | |
| ``` | |
| **Auth Required**: β No (Called by Google redirect) | |
| **Roles**: None | |
| > Exchanges the authorization code (received from Google after user consent) for access/refresh tokens. | |
| #### Query Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `code` | `string` | β | Authorization code from Google OAuth redirect | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "message": "Authentication successful", // string | |
| "refreshToken": "ya29.a0AfB_byC..." // string β Refresh token for future API calls | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid or expired authorization code | | |
| --- | |
| ### 13.3 Upload Video to YouTube (Standalone) | |
| ``` | |
| POST /youtube/upload | |
| ``` | |
| **Auth Required**: β Requires YouTube OAuth configured (refresh token in environment) | |
| **Roles**: Any user with YouTube credentials | |
| **Content-Type**: `multipart/form-data` | |
| > **Note**: For uploading course lecture videos, prefer the dedicated endpoint `POST /api/courses/:courseId/materials/video` (section 11.5) which also creates the material record. | |
| #### Form Data Fields | |
| | Field | Type | Required | Constraints | Description | | |
| |---|---|---|---|---| | |
| | `video` | `binary (file)` | β | MP4, AVI, MOV, WMV, FLV, WebM | Video file | | |
| | `title` | `string` | β | β | Video title | | |
| | `description` | `string` | β | β | Video description | | |
| | `tags` | `string` | β | Comma-separated string | Tags (e.g. `"education,course,lecture"`) | | |
| > **Note**: `tags` here is a **comma-separated string**, not an array (unlike the Course Materials video endpoint). | |
| #### Response `201 Created` | |
| ```json | |
| { | |
| "success": true, // boolean | |
| "videoId": "dQw4w9WgXcQ", // string β YouTube video ID | |
| "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // string β Full watch URL | |
| "data": { // object β Full YouTube API response data | |
| "kind": "youtube#video", | |
| "etag": "...", | |
| "id": "dQw4w9WgXcQ", | |
| "snippet": { | |
| "title": "My Course Lecture", | |
| "description": "Lecture about...", | |
| "tags": ["education", "course", "lecture"], | |
| "categoryId": "22" | |
| }, | |
| "status": { | |
| "privacyStatus": "unlisted" | |
| } | |
| } | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | No video file or invalid format | | |
| | `401` | YouTube authentication required (no valid refresh token) | | |
| | `413` | Video file too large | | |
| --- | |
| ### 13.4 Search YouTube Videos | |
| ``` | |
| GET /youtube/search | |
| ``` | |
| **Auth Required**: β No (Uses service account OAuth2) | |
| **Roles**: None | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `query` | `string` | β | β | Search terms | | |
| | `maxResults` | `integer` | β | `10` | Max number of results (capped at 50) | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "success": true, // boolean | |
| "items": [ // array β Search results | |
| { | |
| "videoId": "dQw4w9WgXcQ", // string β YouTube video ID | |
| "title": "Example Video Title", // string β Video title | |
| "description": "Video description...", // string β Truncated description | |
| "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", // string β HQ thumbnail URL | |
| "channelTitle": "Channel Name", // string β Channel name | |
| "publishedAt": "2023-01-15T10:30:00Z", // string β ISO 8601 publish date | |
| "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // string β Watch URL | |
| "embedUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ" // string β Embed URL | |
| } | |
| ], | |
| "totalResults": 1000000, // number β Total matching results | |
| "resultsPerPage": 10 // number β Results returned | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `400` | Invalid query parameters (missing `query`) | | |
| --- | |
| ### 13.5 Get Video Details by ID | |
| ``` | |
| GET /youtube/videos/:videoId | |
| ``` | |
| **Auth Required**: β No (Uses service account OAuth2) | |
| **Roles**: None | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `videoId` | `string` | β | YouTube video ID (e.g. `dQw4w9WgXcQ`) | | |
| #### Response `200 OK` (Found) | |
| ```json | |
| { | |
| "success": true, // boolean | |
| "video": { // object β Complete video info | |
| "videoId": "dQw4w9WgXcQ", // string β YouTube ID | |
| "title": "Example Video", // string β Title | |
| "description": "Full video description...", // string β Full description | |
| "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", // string β Thumbnail | |
| "channelId": "UC1234567890", // string β Channel ID | |
| "channelTitle": "Channel Name", // string β Channel name | |
| "publishedAt": "2023-01-15T10:30:00Z", // string β Publish date | |
| "duration": "PT4M33S", // string β ISO 8601 duration | |
| "viewCount": "1000000", // string β View count | |
| "likeCount": "50000", // string β Like count | |
| "commentCount": "2000", // string β Comment count | |
| "privacyStatus": "public", // string β "public" | "private" | "unlisted" | |
| "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // string β Watch URL | |
| "embedUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ" // string β Embed URL | |
| } | |
| } | |
| ``` | |
| #### Response `200 OK` (Not Found) | |
| ```json | |
| { | |
| "success": false, // boolean | |
| "message": "Video not found" // string | |
| } | |
| ``` | |
| > **Note**: Returns `200` with `success: false` when video doesn't exist β not a `404`. | |
| --- | |
| ### 13.6 Get Channel Videos | |
| ``` | |
| GET /youtube/channel/:channelId/videos | |
| ``` | |
| **Auth Required**: β No (Uses service account OAuth2) | |
| **Roles**: None | |
| #### Path Parameters | |
| | Parameter | Type | Required | Description | | |
| |---|---|---|---| | |
| | `channelId` | `string` | β | YouTube channel ID (e.g. `UC1234567890`) | | |
| #### Query Parameters | |
| | Parameter | Type | Required | Default | Description | | |
| |---|---|---|---|---| | |
| | `maxResults` | `integer` | β | `10` | Max number of results (capped at 50) | | |
| #### Response `200 OK` | |
| ```json | |
| { | |
| "success": true, // boolean | |
| "items": [ // array β Channel videos (newest first) | |
| { | |
| "videoId": "dQw4w9WgXcQ", // string β Video ID | |
| "title": "Latest Video", // string β Title | |
| "description": "Video description...", // string | |
| "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg", // string | |
| "publishedAt": "2023-01-15T10:30:00Z", // string β ISO 8601 | |
| "videoUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", // string | |
| "embedUrl": "https://www.youtube.com/embed/dQw4w9WgXcQ" // string | |
| } | |
| ], | |
| "totalResults": 150, // number β Total channel videos | |
| "resultsPerPage": 10 // number β Results returned | |
| } | |
| ``` | |
| #### Error Responses | |
| | Status | Description | | |
| |---|---| | |
| | `404` | Channel not found | | |
| --- | |
| ## 14. Course Materials Role-Based Access Matrix | |
| | Endpoint | Student | Instructor (assigned) | TA (assigned) | Admin / IT Admin | | |
| |---|---|---|---|---| | |
| | `GET .../materials` | β (published only) | β (all) | β (all) | β (all) | | |
| | `GET .../materials/:id` | β (published only) | β | β | β | | |
| | `POST .../materials` | β | β | β | β | | |
| | `POST .../materials/bulk` | β | β | β | β | | |
| | `POST .../materials/video` | β | β | β | β | | |
| | `POST .../materials/document` | β | β | β | β | | |
| | `PUT .../materials/:id` | β | β (own only) | β (own only) | β (any) | | |
| | `DELETE .../materials/:id` | β | β (own only) | β (own only) | β (any) | | |
| | `PATCH .../materials/:id/visibility` | β | β (own only) | β (own only) | β (any) | | |
| | `GET .../materials/:id/download` | β (published only) | β | β | β | | |
| | `POST .../materials/:id/view` | β | β | β | β | | |
| | `GET .../materials/:id/embed` | β | β | β | β | | |
| | `GET .../structure` | β | β | β | β | | |
| | `GET .../structure/:id` | β | β | β | β | | |
| | `POST .../structure` | β | β | β | β | | |
| | `PUT .../structure/:id` | β | β | β | β | | |
| | `DELETE .../structure/:id` | β | β | β | β | | |
| | `PATCH .../structure/reorder` | β | β | β | β | | |
| --- | |
| ## 15. Video Lecture Viewing β End-to-End Flow | |
| ### 15.1 Instructor Uploads a Video Lecture | |
| ```mermaid | |
| sequenceDiagram | |
| participant I as Instructor | |
| participant API as Backend API | |
| participant YT as YouTube API | |
| participant DB as Database | |
| I->>API: POST /api/courses/1/materials/video (multipart: video + metadata) | |
| API->>API: Validate user is assigned to course | |
| API->>YT: Upload video buffer (unlisted) | |
| YT-->>API: Return { videoId, videoUrl } | |
| API->>DB: INSERT INTO course_materials (materialType='video', youtubeVideoId, externalUrl=embed URL) | |
| DB-->>API: Return saved material entity | |
| API-->>I: 201 { materialId, youtubeVideoId, embedUrl, youtubeUrl } | |
| ``` | |
| ### 15.2 Student Views a Video Lecture | |
| ```mermaid | |
| sequenceDiagram | |
| participant S as Student | |
| participant API as Backend API | |
| participant DB as Database | |
| S->>API: GET /api/courses/1/materials?materialType=video | |
| API->>DB: SELECT WHERE course_id=1 AND material_type='video' AND is_published=1 | |
| DB-->>API: Return published video materials | |
| API-->>S: 200 { data: [...], meta: { total, page, limit, totalPages } } | |
| Note over S: Student selects a video | |
| S->>API: POST /api/courses/1/materials/5/view | |
| API->>DB: UPDATE viewCount = viewCount + 1 | |
| API-->>S: 200 { message, materialId, viewCount } | |
| S->>API: GET /api/courses/1/materials/5/embed | |
| API-->>S: 200 { videoId, embedUrl, iframeHtml } | |
| Note over S: Flutter renders YouTube video using embedUrl in WebView/iframe | |
| ``` | |
| ### 15.3 Flutter Integration Example (Video Player) | |
| ```dart | |
| // Fetch video material | |
| final response = await dio.get( | |
| '$baseUrl/api/courses/$courseId/materials/$materialId/embed', | |
| options: Options(headers: {'Authorization': 'Bearer $token'}), | |
| ); | |
| final embedUrl = response.data['embedUrl'] as String; | |
| final videoId = response.data['videoId'] as String; | |
| // Option 1: Use WebView for embedded YouTube | |
| WebViewWidget(controller: WebViewController() | |
| ..loadRequest(Uri.parse(embedUrl))); | |
| // Option 2: Use youtube_player_flutter package | |
| YoutubePlayer( | |
| controller: YoutubePlayerController( | |
| initialVideoId: videoId, | |
| flags: YoutubePlayerFlags(autoPlay: false), | |
| ), | |
| ); | |
| ``` | |
| --- | |
| ## 16. Course Details Handling β What Each Role Sees | |
| ### 16.1 Course Visibility & Access Per Role | |
| #### **Student View** | |
| - **Course Catalog**: Can browse all courses (public endpoints, no auth required) | |
| - **Course Details** (`GET /api/courses/:id`): | |
| - Full course information (name, code, description, credits, level) | |
| - Prerequisites list | |
| - Available sections count | |
| - **Cannot see**: Other students' information, unpublished materials | |
| - **Course Materials**: | |
| - Only **published** materials (`isPublished = true`) | |
| - Can view videos, download documents, track views | |
| - **Cannot see**: Draft materials, other students' submissions | |
| - **Assignments**: | |
| - Only **published** assignments | |
| - Can submit if enrolled in the course section | |
| - Can view own submissions and grades | |
| - **Cannot see**: Other students' submissions | |
| - **Labs**: | |
| - Only **published** labs | |
| - Can submit if enrolled (currently **no enrollment check** for labs β missing validation) | |
| - Can view own submissions and grades | |
| - Can view lab instructions | |
| - **Sections & Schedules**: Can view all sections and schedules (public) | |
| #### **Instructor View** | |
| - **Teaching Courses** (`GET /api/enrollments/teaching`): | |
| - All sections where instructor is assigned | |
| - Course details, section info, semester dates | |
| - **Course Details**: | |
| - Same as student view PLUS: | |
| - Can create/update/delete courses (if authorized) | |
| - Can manage sections and schedules | |
| - **Course Materials**: | |
| - **All materials** (including drafts) for courses they teach | |
| - Can upload videos to YouTube, documents to Google Drive | |
| - Can organize materials by weeks using course structure | |
| - Can update/delete **own** materials only (admins can manage any) | |
| - **Assignments**: | |
| - Can create/update/publish/close assignments | |
| - Can view **all submissions** for assignments in their courses | |
| - Can grade submissions (creates central grade record) | |
| - Can upload instruction files to Google Drive | |
| - **Labs**: | |
| - Can create/update/publish/close labs | |
| - Can add instructions with file attachments | |
| - Can view **all submissions** and grade them | |
| - Can mark student attendance | |
| - Can upload TA materials (answer keys, rubrics) | |
| - **Can delete** labs | |
| #### **Teaching Assistant (TA) View** | |
| - **Teaching Courses**: Same as instructor (sections where assigned as TA) | |
| - **Course Materials**: | |
| - **All materials** (including drafts) for courses they're assigned to | |
| - Can upload/create materials | |
| - Can update/delete **own** materials only | |
| - **Cannot** create/update course structure (weeks, lectures organization) | |
| - **Assignments**: | |
| - Can create/update/delete assignments (same as instructor) | |
| - Can view all submissions and grade them | |
| - Can upload instruction files | |
| - **Labs**: | |
| - Can create/update labs | |
| - Can add instructions and upload files | |
| - Can view all submissions and grade them | |
| - Can mark attendance | |
| - Can upload TA materials | |
| - **Cannot delete** labs (only instructors/admins) | |
| - **Sections & Students**: | |
| - Can view enrolled students in their sections | |
| - Can view instructor/TA assignments | |
| #### **Admin / IT Admin View** | |
| - **Full Access** to all courses, sections, schedules, materials, assignments, labs | |
| - Can manage any material (not just own) | |
| - Can manage course structure | |
| - Can assign instructors/TAs to sections | |
| - Can manage student enrollments | |
| - Can delete any resource | |
| - IT Admin additionally has system-level access (backup, monitoring, security) | |
| #### **Department Head View** | |
| - **Currently NO specific access** to courses/assignments/labs features | |
| - Only has access to: | |
| - Schedule templates (`/api/schedule-templates`) | |
| - Campus events (`/api/campus-events`) | |
| - **Enhancement Needed**: Would need additional endpoints to view department courses overview, instructor assignments, etc. | |
| ### 16.2 Course Details Response Fields | |
| When calling `GET /api/courses/:id`, the response includes: | |
| ```json | |
| { | |
| "id": 1, | |
| "departmentId": 3, | |
| "name": "Introduction to CS", | |
| "code": "CS101", | |
| "description": "Fundamentals of programming...", | |
| "credits": 3, | |
| "level": "FRESHMAN", | |
| "syllabusUrl": "https://example.com/syllabus.pdf", | |
| "instructorId": 5, // β Primary instructor ID (may be null) | |
| "taIds": [1, 2], // β Array of TA user IDs (may be null) | |
| "status": "ACTIVE", | |
| "createdAt": "2025-01-15T10:00:00Z", | |
| "updatedAt": "2025-01-15T10:00:00Z", | |
| "department": { | |
| "id": 3, | |
| "name": "Computer Science", | |
| "code": "CS" | |
| }, | |
| "prerequisites": [], // Array of prerequisite objects | |
| "sections": [], // Array of section objects | |
| "prerequisitesCount": 0, // β Convenience field | |
| "sectionsCount": 2 // β Convenience field | |
| } | |
| ``` | |
| > **Note**: `instructorId` and `taIds` are stored in the `courses` table but actual section-level instructor/TA assignments are managed separately in `course_instructors` and `course_tas` tables via the enrollments module. | |
| --- | |
| ## 17. Business Logic Details | |
| ### 17.1 Assignment Submission β Enrollment Check | |
| **Critical**: Assignment submission (`POST /api/assignments/:id/submit` and `POST /api/assignments/:id/submissions/upload`) **requires** the student to be enrolled in the assignment's course. | |
| ```typescript | |
| // Enrollment validation in assignments.service.ts | |
| const enrollment = await this.enrollmentRepo | |
| .createQueryBuilder('enrollment') | |
| .innerJoin('course_sections', 'section', 'section.section_id = enrollment.section_id') | |
| .where('enrollment.user_id = :userId', { userId }) | |
| .andWhere('section.course_id = :courseId', { courseId: assignment.courseId }) | |
| .andWhere('enrollment.enrollment_status = :status', { status: 'enrolled' }) | |
| .getOne(); | |
| if (!enrollment) { | |
| throw new BadRequestException('Student is not enrolled in this course'); | |
| } | |
| ``` | |
| **Business Rules**: | |
| 1. Assignment must be in `published` status | |
| 2. If `availableFrom` is set, current time must be after it | |
| 3. If past `dueDate`: | |
| - If `lateSubmissionAllowed = false`: Throws `SubmissionDeadlinePassedException` | |
| - If `lateSubmissionAllowed = true`: Sets `isLate = 1` | |
| 4. **Student must be enrolled** in the course's section with status `enrolled` | |
| 5. **UPSERT Logic**: | |
| - If latest submission is `graded`: Creates new attempt (increment `attemptNumber`) | |
| - If latest submission is not graded: Updates existing submission | |
| ### 17.2 Lab Submission β Missing Enrollment Check | |
| **Important**: Lab submission (`POST /api/labs/:id/submit` and `POST /api/labs/:id/submissions/upload`) currently **does NOT check** if the student is enrolled in the course. | |
| ```typescript | |
| // labs.service.ts β submit() method | |
| // β οΈ NO enrollment validation found | |
| async submit(labId: number, userId: number, dto: SubmitLabDto): Promise<LabSubmission> { | |
| const lab = await this.findById(labId); | |
| let isLate = false; | |
| if (lab.dueDate && new Date() > new Date(lab.dueDate)) { | |
| isLate = true; | |
| } | |
| // UPSERT logic... | |
| } | |
| ``` | |
| **Recommendation**: Add enrollment check similar to assignments for consistency. | |
| ### 17.3 Assignment vs Lab Submission Differences | |
| | Feature | Assignments | Labs | | |
| |---|---|---| | |
| | **Late Detection** | `isLate: number` (0 or 1) | `isLate: boolean` (true/false) | | |
| | **Attempt Tracking** | `attemptNumber` auto-incremented | No attempt tracking | | |
| | **UPSERT Behavior** | Updates if not graded, new attempt if graded | Updates existing or creates new | | |
| | **Enrollment Check** | β Required | β Missing (should be added) | | |
| | **Get My Submission** | Returns single latest submission | Returns **array** of all submissions | | |
| | **Submission Types** | `file`, `text`, `link`, `multiple` | `text`, `file` only | | |
| | **Grade Integration** | Creates central grade record | Creates central grade record (only if status=`graded`) | | |
| ### 17.4 Grade Integration | |
| Both assignments and labs automatically create records in the central `grades` table when graded: | |
| **Assignment Grading** (`PATCH /api/assignments/:id/submissions/:subId/grade`): | |
| ```typescript | |
| await this.gradesService.createGrade({ | |
| userId: submission.userId, | |
| courseId: assignment.courseId, | |
| gradeType: GradeType.ASSIGNMENT, | |
| assignmentId: assignmentId, | |
| score: dto.score, | |
| maxScore: Number(assignment.maxScore), | |
| feedback: dto.feedback, | |
| isPublished: true, // Immediately visible | |
| }, graderId); | |
| ``` | |
| **Lab Grading** (`PATCH /api/labs/:id/submissions/:subId/grade`): | |
| ```typescript | |
| // Only creates grade if status is 'graded' and score provided | |
| if (dto.score !== undefined && dto.status === 'graded') { | |
| await this.gradesService.createGrade({ | |
| userId: submission.userId, | |
| courseId: lab.courseId, | |
| gradeType: GradeType.LAB, | |
| labId: labId, | |
| score: dto.score, | |
| maxScore: Number(lab.maxScore), | |
| feedback: dto.feedback, | |
| isPublished: true, | |
| }, graderId); | |
| } | |
| ``` | |
| ### 17.5 Google Drive File Organization | |
| **Assignment Instructions**: | |
| - Folder: `EduVerse/Courses/{CourseCode}/Assignments/Assignment_{ID}/Instructions/` | |
| - File naming: `{Title}_v1.{ext}` | |
| **Assignment Submissions**: | |
| - Folder: `EduVerse/Courses/{CourseCode}/Assignments/Assignment_{ID}/Submissions/User_{UserID}/` | |
| - File naming: `Assignment_{ID}_Submission_{YYYYMMDD}.{ext}` | |
| **Lab Instructions**: | |
| - Folder: `EduVerse/Courses/{CourseCode}/Labs/Lab_{LabNumber}/Instructions/` | |
| - File naming: `{Title}_v1.{ext}` | |
| **Lab TA Materials**: | |
| - Folder: `EduVerse/Courses/{CourseCode}/Labs/Lab_{LabNumber}/TA_Materials/` | |
| - File naming: `{Type}_{Title}.{ext}` (e.g., `solution_Lab_1_Answer_Key.pdf`) | |
| **Lab Submissions**: | |
| - Folder: `EduVerse/Courses/{CourseCode}/Labs/Lab_{LabNumber}/Submissions/User_{UserID}/` | |
| - File naming: `Lab{LabNumber}_Submission_{YYYYMMDD}.{ext}` | |
| ### 17.6 Course Section Status Auto-Calculation | |
| Section status is automatically calculated based on enrollment: | |
| ```typescript | |
| private calculateSectionStatus(maxCapacity: number, currentEnrollment: number): SectionStatus { | |
| if (currentEnrollment >= maxCapacity) { | |
| return SectionStatus.FULL; | |
| } | |
| return SectionStatus.OPEN; | |
| } | |
| ``` | |
| - `OPEN`: Enrollment < Capacity | |
| - `FULL`: Enrollment >= Capacity | |
| - `CLOSED`: Manually set by instructor/admin | |
| - `CANCELLED`: Manually set by instructor/admin | |
| --- | |
| ## 18. End-to-End Flow Diagrams | |
| ### 18.1 Student Course Enrollment Flow | |
| ```mermaid | |
| sequenceDiagram | |
| participant S as Student | |
| participant API as Backend API | |
| participant DB as Database | |
| S->>API: GET /api/enrollments/available | |
| API->>DB: Query active courses with sections | |
| DB-->>API: Return courses | |
| API->>API: Check prerequisites, capacity, conflicts | |
| API-->>S: 200 { courses with canEnroll flag } | |
| Note over S: Student selects a section | |
| S->>API: POST /api/enrollments/register { sectionId } | |
| API->>DB: Check prerequisites (grade B- or higher) | |
| API->>DB: Check schedule conflicts | |
| API->>DB: Check existing enrollment | |
| API->>DB: INSERT course_enrollments | |
| API->>DB: UPDATE sections SET currentEnrollment + 1 | |
| API-->>S: 201 { enrollment details } | |
| ``` | |
| ### 18.2 Assignment Submission Flow (with Enrollment Check) | |
| ```mermaid | |
| sequenceDiagram | |
| participant S as Student | |
| participant API as Backend API | |
| participant DB as Database | |
| participant GD as Google Drive | |
| S->>API: GET /api/assignments/:id | |
| API->>DB: Get assignment with course info | |
| API-->>S: 200 { assignment details } | |
| Note over S: Student prepares submission | |
| S->>API: POST /api/assignments/:id/submissions/upload (multipart) | |
| API->>DB: Check assignment status = published | |
| API->>DB: Check availableFrom <= now | |
| API->>DB: Check dueDate (late detection) | |
| API->>DB: CHECK enrollment in course section β | |
| alt Not enrolled | |
| API-->>S: 400 "Student is not enrolled in this course" | |
| else Enrolled | |
| API->>GD: Upload file to student folder | |
| GD-->>API: Return driveFileId, URLs | |
| API->>DB: UPSERT submission (check if graded) | |
| API-->>S: 201 { submission + driveFile } | |
| end | |
| ``` | |
| ### 18.3 Lab Submission Flow (without Enrollment Check) | |
| ```mermaid | |
| sequenceDiagram | |
| participant S as Student | |
| participant API as Backend API | |
| participant DB as Database | |
| participant GD as Google Drive | |
| S->>API: GET /api/labs/:id | |
| API->>DB: Get lab with instructions | |
| API-->>S: 200 { lab + instructions } | |
| Note over S: Student prepares submission | |
| S->>API: POST /api/labs/:id/submissions/upload (multipart) | |
| API->>DB: Check lab exists | |
| API->>DB: Check dueDate (late detection) | |
| API->>DB: β οΈ NO enrollment check | |
| API->>GD: Upload file to student folder | |
| GD-->>API: Return driveFileId, URLs | |
| API->>DB: UPSERT submission | |
| API-->>S: 201 { submission + driveFile } | |
| ``` | |
| ### 18.4 Grading Flow (Central Gradebook Integration) | |
| ```mermaid | |
| sequenceDiagram | |
| participant I as Instructor/TA | |
| participant API as Backend API | |
| participant DB as Database | |
| participant GB as Grades Module | |
| I->>API: PATCH /api/assignments/:id/submissions/:subId/grade | |
| API->>DB: Get submission + assignment | |
| API->>DB: UPDATE submission (status=graded, score, feedback) | |
| API->>GB: createGrade({ assignment, score, maxScore }) | |
| GB->>DB: INSERT grades (gradeType='assignment', isPublished=true) | |
| GB-->>API: Return gradeId | |
| API-->>I: 200 { submissionId, score, maxScore, gradeId } | |
| ``` | |
| --- | |
| ## 19. UI Shape for 5 Roles | |
| This section describes the **expected UI structure** for each role based on the backend API capabilities. | |
| ### 19.1 Student UI | |
| #### **Dashboard / Home** | |
| - **My Courses**: Grid/list of enrolled courses with: | |
| - Course name, code, instructor | |
| - Current grade (if available) | |
| - Quick links to materials, assignments, labs | |
| - **Upcoming Deadlines**: Assignments and labs due soon | |
| - **Recent Grades**: Latest graded submissions | |
| #### **Course Catalog** (Public) | |
| - Browse all courses by department, level, search | |
| - View course details: | |
| - Description, credits, prerequisites | |
| - Available sections with schedules | |
| - Enroll button (if `canEnroll = true`) | |
| #### **Course Details Page** (Per Course) | |
| - **Tabs**: | |
| 1. **Overview**: Description, syllabus, instructor/TA info | |
| 2. **Materials**: Published videos, documents (organized by weeks) | |
| - Video player (YouTube embed) | |
| - Document viewer/download | |
| - View tracking (analytics for instructor) | |
| 3. **Assignments**: Published assignments list | |
| - Title, due date, status | |
| - Submit button β Modal with: | |
| - Text editor | |
| - File upload | |
| - Link input | |
| - Late warning if past due | |
| - View submission history with grades/feedback | |
| 4. **Labs**: Published labs list | |
| - Title, due date, lab number | |
| - View instructions (with file attachments) | |
| - Submit button β Modal with: | |
| - Text/code editor | |
| - File upload | |
| - View submission history with grades/feedback | |
| 5. **Grades**: All graded work with scores, feedback, overall grade | |
| 6. **Schedule**: Section schedule (days, times, rooms) | |
| #### **Enrollment Management** | |
| - **My Enrollments**: List of current/past courses | |
| - **Available Courses**: Browse and register (with prerequisites check) | |
| - **Drop Course**: Button with deadline warning | |
| --- | |
| ### 19.2 Instructor UI | |
| #### **Dashboard / Home** | |
| - **Teaching Courses**: Grid of assigned sections with: | |
| - Course name, section number | |
| - Enrollment count | |
| - Quick stats: pending submissions, upcoming deadlines | |
| - **Recent Activity**: New submissions, recent grading | |
| #### **Course Management** (Per Assigned Section) | |
| - **Tabs**: | |
| 1. **Overview**: | |
| - Course details (edit if assigned) | |
| - Section info (capacity, location) | |
| - Instructor/TA assignments (view only, admin manages) | |
| - Enrolled students list (with contact info) | |
| 2. **Materials**: | |
| - **All materials** (including drafts) | |
| - Upload buttons: | |
| - πΉ **Upload Video** β YouTube upload modal (title, description, tags, week) | |
| - π **Upload Document** β Google Drive upload (title, type, week) | |
| - Organize by weeks (course structure): | |
| - Create/edit weeks, lectures, sections, labs | |
| - Drag-and-drop reorder | |
| - Toggle visibility (publish/draft) | |
| - View analytics (view count, download count) | |
| 3. **Assignments**: | |
| - List all assignments (draft/published/closed/archived) | |
| - **Create Assignment** modal: | |
| - Title, description, instructions | |
| - Submission type (file/text/link/multiple) | |
| - Due date, available from date | |
| - Late submission settings | |
| - Max score, weight | |
| - Allowed file types, max size | |
| - **View Submissions**: | |
| - Table of all students with: | |
| - Name, submission time, late flag | |
| - Status (submitted/graded/returned) | |
| - Attempt number | |
| - Grade input (score, feedback) | |
| - Bulk actions: download all, grade selected | |
| - **Grade Submission**: | |
| - Side-by-side view: | |
| - Left: Student submission (file viewer, text, link) | |
| - Right: Grade input (score, feedback, status) | |
| 4. **Labs**: | |
| - List all labs (draft/published/closed/archived) | |
| - **Create Lab** modal: | |
| - Title, description, lab number | |
| - Due date, available from | |
| - Max score, weight | |
| - **Manage Instructions**: | |
| - Add steps (markdown text, file attachments) | |
| - Reorder instructions | |
| - Upload instruction files to Drive | |
| - **View Submissions**: Same as assignments | |
| - **Grade Submissions**: Same as assignments | |
| - **Attendance**: | |
| - Student list with attendance status | |
| - Mark present/absent/excused/late | |
| - Export attendance | |
| 5. **Grades**: | |
| - Gradebook view with all graded work | |
| - Assignment grades, lab grades | |
| - Calculate overall grades | |
| - Export grades | |
| 6. **Schedule**: | |
| - Section schedules (days, times, rooms) | |
| - Create/edit schedules with conflict detection | |
| 7. **Analytics** (if available): | |
| - Student engagement (material views, downloads) | |
| - Grade distribution | |
| - Submission patterns | |
| --- | |
| ### 19.3 Teaching Assistant (TA) UI | |
| **Similar to Instructor UI with these differences**: | |
| #### **Restrictions**: | |
| - β **Cannot** delete labs | |
| - β **Cannot** manage course structure (weeks, lectures organization) | |
| - β **Cannot** assign/remove instructors or TAs | |
| - β **Can** create/edit/delete assignments | |
| - β **Can** create/edit labs (but not delete) | |
| - β **Can** grade all submissions | |
| - β **Can** upload instructions and TA materials | |
| - β **Can** mark lab attendance | |
| - β **Can** view all materials (including drafts) | |
| #### **Additional TA-Specific Features**: | |
| - **TA Materials Tab** (in Labs): | |
| - Upload answer keys, grading rubrics, solutions | |
| - Visible only to instructors and TAs (not students) | |
| - **Responsibilities View**: | |
| - Shows assigned duties (e.g., "Grading labs, office hours Mon/Wed") | |
| --- | |
| ### 19.4 Admin UI | |
| #### **Full System Access** | |
| - All instructor/TA capabilities **without restrictions** | |
| - Can manage **any** material (not just own) | |
| - Can manage course structure | |
| - Can delete any resource | |
| #### **Admin-Specific Features**: | |
| 1. **Course Catalog Management**: | |
| - Create/edit/delete courses | |
| - Manage prerequisites (with circular dependency detection) | |
| - Assign departments, instructors, TAs at course level | |
| 2. **Section Management**: | |
| - Create/edit/delete sections | |
| - Set capacity, location, status | |
| - Override enrollment counts | |
| 3. **Instructor/TA Assignment**: | |
| - Assign instructors to sections (primary, co_instructor, guest) | |
| - Assign TAs to sections | |
| - View all assignments across sections | |
| 4. **Enrollment Management**: | |
| - View all student enrollments | |
| - Manually enroll/drop students | |
| - Override drop deadlines | |
| - Approve retake requests | |
| 5. **System Administration**: | |
| - Manage departments, semesters, programs | |
| - User management (create, assign roles) | |
| - System settings, integrations | |
| --- | |
| ### 19.5 IT Admin UI | |
| **All Admin capabilities PLUS**: | |
| 1. **System Monitoring**: | |
| - Server health, performance metrics | |
| - Error tracking, alerts | |
| - SSL certificate status | |
| 2. **Database Management**: | |
| - Backup/restore | |
| - Database migrations | |
| - Query performance | |
| 3. **Security**: | |
| - Audit logs | |
| - IP blocking/unblocking | |
| - Security settings | |
| 4. **Integrations**: | |
| - Google Drive configuration | |
| - YouTube API setup | |
| - Email service configuration | |
| --- | |
| ### 19.6 Department Head UI | |
| **Current State**: **Minimal access** to courses/assignments/labs | |
| **Currently Available**: | |
| - Schedule templates (create/manage templates) | |
| - Campus events (create/manage events) | |
| **Recommended Enhancements** (not yet implemented): | |
| - Department courses overview | |
| - Instructor assignments per course | |
| - Enrollment statistics | |
| - Department-level analytics | |
| --- | |
| ## 20. Missing Endpoints & Future Enhancements | |
| ### 20.1 Currently Missing Endpoints | |
| 1. **Lab Submission Enrollment Check**: Labs should validate student enrollment like assignments do | |
| 2. **Department Head Course Access**: Endpoints for department heads to view/manage department courses | |
| 3. **Waitlist Management**: Full waitlist table and enrollment logic | |
| 4. **Course Copy/Duplicate**: Clone a course with all materials, assignments, labs | |
| 5. **Bulk Enrollment**: Admin enroll multiple students at once | |
| 6. **Grade Export**: Export grades to CSV/Excel | |
| 7. **Assignment/Lab Cloning**: Duplicate for reuse in future semesters | |
| 8. **Discussion Forums**: Per-course discussion boards | |
| 9. **Announcements**: Course-wide announcements | |
| 10. **Notifications**: Push/email notifications for deadlines, grades | |
| ### 20.2 Recommended Improvements | |
| 1. **Add enrollment check to lab submission** endpoints for consistency | |
| 2. **Implement department_head role** permissions for course oversight | |
| 3. **Add soft delete** to labs (currently hard delete) | |
| 4. **Add attempt tracking** to lab submissions (like assignments) | |
| 5. **Implement waitlist** with auto-enrollment when seats open | |
| 6. **Add grade curves** and grading policies | |
| 7. **Add plagiarism detection** integration | |
| 8. **Implement real-time notifications** (WebSockets) | |
| 9. **Add course templates** for quick setup | |
| 10. **Add analytics dashboard** for instructors (engagement, grade distribution) | |
| --- | |
| > **Last Updated**: April 2026 | |
| > **Backend Framework**: NestJS (TypeScript) | |
| > **Database**: MySQL with TypeORM | |
| > **File Storage**: Google Drive API | |
| > **Video Storage**: YouTube API (unlisted uploads) | |
| > **Authentication**: JWT with role-based access control | |