Merge branch 'dev' into feat/pagina-overzicht-klassen-voor-student
This commit is contained in:
		
						commit
						7cba7f1fef
					
				
					 173 changed files with 3158 additions and 762 deletions
				
			
		
							
								
								
									
										5
									
								
								.github/workflows/deployment.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/deployment.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -13,7 +13,10 @@ jobs: | ||||||
|       - |       - | ||||||
|         name: Checkout |         name: Checkout | ||||||
|         uses: actions/checkout@v4 |         uses: actions/checkout@v4 | ||||||
|  |       - | ||||||
|  |         name: Copy environment variables to correct file | ||||||
|  |         run: cp /home/dev/.backend.env backend/.env | ||||||
|       - |       - | ||||||
|         name: Start docker |         name: Start docker | ||||||
|         run: docker compose -f compose.yml -f compose.prod.yml up --build -d |         run: docker compose -f compose.production.yml up --build -d | ||||||
|          |          | ||||||
							
								
								
									
										4
									
								
								.github/workflows/lint-action.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/lint-action.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -43,6 +43,6 @@ jobs: | ||||||
|               with: |               with: | ||||||
|                   auto_fix: true |                   auto_fix: true | ||||||
|                   eslint: true |                   eslint: true | ||||||
|                   eslint_args: '--config eslint.config.ts' |                   eslint_args: "--config eslint.config.ts --ignore-pattern '**/prettier.config.js'" | ||||||
|                   prettier: true |                   prettier: true | ||||||
|                   commit_message: 'style: fix linting issues met ${linter}' |                   commit_message: 'style: fix linting issues met ${linter}' | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -737,4 +737,4 @@ flycheck_*.el | ||||||
| # network security | # network security | ||||||
| /network-security.data | /network-security.data | ||||||
| 
 | 
 | ||||||
| 
 | docs/.venv | ||||||
|  |  | ||||||
|  | @ -35,12 +35,8 @@ Om de applicatie lokaal te draaien als kant-en-klare Docker-containers: | ||||||
| ```bash | ```bash | ||||||
| docker compose version | docker compose version | ||||||
| git clone https://github.com/SELab-2/Dwengo-1.git | git clone https://github.com/SELab-2/Dwengo-1.git | ||||||
| cd Dwengo-1/backend |  | ||||||
| cp .env.example .env |  | ||||||
| # Pas .env aan |  | ||||||
| nano .env |  | ||||||
| cd .. |  | ||||||
| docker compose -f compose.staging.yml up --build | docker compose -f compose.staging.yml up --build | ||||||
|  | # Gebruikt backend/.env.staging | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Handmatige installatie en ontwikkeling | ### Handmatige installatie en ontwikkeling | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								backend/.env.staging
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								backend/.env.staging
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | PORT=3000 | ||||||
|  | DWENGO_DB_HOST=db | ||||||
|  | DWENGO_DB_PORT=5432 | ||||||
|  | DWENGO_DB_USERNAME=postgres | ||||||
|  | DWENGO_DB_PASSWORD=postgres | ||||||
|  | DWENGO_DB_UPDATE=false | ||||||
|  | 
 | ||||||
|  | DWENGO_AUTH_STUDENT_URL=http://localhost/idp/realms/student | ||||||
|  | DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo | ||||||
|  | DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://idp:7080/idp/realms/student/protocol/openid-connect/certs | ||||||
|  | DWENGO_AUTH_TEACHER_URL=http://localhost/idp/realms/teacher | ||||||
|  | DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo | ||||||
|  | DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://idp:7080/idp/realms/teacher/protocol/openid-connect/certs | ||||||
|  | 
 | ||||||
|  | # Allow Vite dev-server to access the backend (for testing purposes). Don't forget to remove this in production! | ||||||
|  | #DWENGO_CORS_ALLOWED_ORIGINS=http://localhost/,127.0.0.1:80,http://127.0.0.1,http://localhost:80,http://127.0.0.1:80,localhost | ||||||
|  | DWENGO_CORS_ALLOWED_ORIGINS=http://localhost/*,http://idp:7080,https://idp:7080 | ||||||
|  | 
 | ||||||
|  | # Logging and monitoring | ||||||
|  | 
 | ||||||
|  | LOKI_HOST=http://logging:3102 | ||||||
|  | @ -1,3 +1,13 @@ | ||||||
| PORT=3000 | # | ||||||
| DWENGO_DB_UPDATE=true | # Test environment configuration | ||||||
|  | # | ||||||
|  | # Should not need to be modified. | ||||||
|  | # See .env.example for more information. | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | ### Dwengo ### | ||||||
|  | 
 | ||||||
|  | DWENGO_PORT=3000 | ||||||
|  | 
 | ||||||
| DWENGO_DB_NAME=":memory:" | DWENGO_DB_NAME=":memory:" | ||||||
|  | DWENGO_DB_UPDATE=true | ||||||
|  |  | ||||||
|  | @ -1,13 +0,0 @@ | ||||||
| # |  | ||||||
| # Test environment configuration |  | ||||||
| # |  | ||||||
| # Should not need to be modified. |  | ||||||
| # See .env.example for more information. |  | ||||||
| # |  | ||||||
| 
 |  | ||||||
| ### Dwengo ### |  | ||||||
| 
 |  | ||||||
| DWENGO_PORT=3000 |  | ||||||
| 
 |  | ||||||
| DWENGO_DB_NAME=":memory:" |  | ||||||
| DWENGO_DB_UPDATE=true |  | ||||||
|  | @ -1,38 +1,51 @@ | ||||||
| FROM node:22 AS build-stage | FROM node:22 AS build-stage | ||||||
| 
 | 
 | ||||||
| WORKDIR /app | WORKDIR /app/dwengo | ||||||
| 
 | 
 | ||||||
| # Install dependencies | # Install dependencies | ||||||
| 
 | 
 | ||||||
| COPY package*.json ./ | COPY package*.json ./ | ||||||
| COPY backend/package.json ./backend/ | COPY backend/package.json ./backend/ | ||||||
|  | # Backend depends on common | ||||||
|  | COPY common/package.json ./common/ | ||||||
| 
 | 
 | ||||||
| RUN npm install --silent | RUN npm install --silent | ||||||
| 
 | 
 | ||||||
| # Build the backend | # Build the backend | ||||||
| 
 | 
 | ||||||
| # Root tsconfig.json | # Root tsconfig.json | ||||||
| COPY tsconfig.json ./ | COPY tsconfig.json tsconfig.build.json ./ | ||||||
| 
 | 
 | ||||||
| WORKDIR /app/backend | COPY backend ./backend | ||||||
| 
 | COPY common ./common | ||||||
| COPY backend ./ | COPY docs ./docs | ||||||
| COPY docs /app/docs |  | ||||||
| 
 | 
 | ||||||
| RUN npm run build | RUN npm run build | ||||||
| 
 | 
 | ||||||
| FROM node:22 AS production-stage | FROM node:22 AS production-stage | ||||||
| 
 | 
 | ||||||
| WORKDIR /app | WORKDIR /app/dwengo | ||||||
| 
 | 
 | ||||||
| COPY package-lock.json backend/package.json ./ | # Copy static files | ||||||
|  | 
 | ||||||
|  | COPY ./backend/i18n ./i18n | ||||||
|  | 
 | ||||||
|  | # Copy built files | ||||||
|  | 
 | ||||||
|  | COPY --from=build-stage /app/dwengo/common/dist ./common/dist | ||||||
|  | COPY --from=build-stage /app/dwengo/backend/dist ./backend/dist | ||||||
|  | 
 | ||||||
|  | COPY package*.json ./ | ||||||
|  | COPY backend/package.json ./backend/ | ||||||
|  | # Backend depends on common | ||||||
|  | COPY common/package.json ./common/ | ||||||
| 
 | 
 | ||||||
| RUN npm install --silent --only=production | RUN npm install --silent --only=production | ||||||
| 
 | 
 | ||||||
| COPY ./docs /docs | COPY ./docs ./docs | ||||||
| COPY ./backend/i18n /app/i18n | COPY ./backend/i18n ./backend/i18n | ||||||
| COPY --from=build-stage /app/backend/dist ./dist/ | COPY ./backend/.env ./backend/.env | ||||||
| 
 | 
 | ||||||
| EXPOSE 3000 | EXPOSE 3000 | ||||||
| 
 | 
 | ||||||
| CMD ["node", "--env-file=.env", "dist/app.js"] | CMD ["node", "--env-file=/app/dwengo/backend/.env", "/app/dwengo/backend/dist/app.js"] | ||||||
|  |  | ||||||
|  | @ -34,7 +34,9 @@ npm run test:unit | ||||||
| 
 | 
 | ||||||
| ```shell | ```shell | ||||||
| # Omgevingsvariabelen | # Omgevingsvariabelen | ||||||
| cp .env.development.example .env | cp .env.example .env | ||||||
|  | # Configureer de .env file met de juiste waarden! | ||||||
|  | nano .env | ||||||
| 
 | 
 | ||||||
| npm run build | npm run build | ||||||
| npm run start | npm run start | ||||||
|  |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| // Can be placed in dotenv but found it redundant
 |  | ||||||
| // Import dotenv from "dotenv";
 |  | ||||||
| // Load .env file
 |  | ||||||
| // Dotenv.config();
 |  | ||||||
| export const DWENGO_API_BASE = 'https://dwengo.org/backend/api'; |  | ||||||
| export const FALLBACK_LANG = 'nl'; |  | ||||||
| export const FALLBACK_SEQ_NUM = 1; |  | ||||||
|  | @ -1,16 +1,18 @@ | ||||||
| { | { | ||||||
|     "name": "dwengo-1-backend", |     "name": "@dwengo-1/backend", | ||||||
|     "version": "0.1.1", |     "version": "0.1.1", | ||||||
|     "description": "Backend for Dwengo-1", |     "description": "Backend for Dwengo-1", | ||||||
|     "private": true, |     "private": true, | ||||||
|     "type": "module", |     "type": "module", | ||||||
|  |     "main": "dist/app.js", | ||||||
|     "scripts": { |     "scripts": { | ||||||
|         "build": "cross-env NODE_ENV=production tsc --project tsconfig.json", |         "build": "cross-env NODE_ENV=production tsc --build", | ||||||
|         "dev": "cross-env NODE_ENV=development tsx watch --env-file=.env.development.local src/app.ts", |         "dev": "cross-env NODE_ENV=development tsx watch --env-file=.env.development.local src/app.ts", | ||||||
|         "start": "cross-env NODE_ENV=production node --env-file=.env dist/app.js", |         "start": "cross-env NODE_ENV=production node --env-file=.env dist/app.js", | ||||||
|         "format": "prettier --write src/", |         "format": "prettier --write src/", | ||||||
|         "format-check": "prettier --check src/", |         "format-check": "prettier --check src/", | ||||||
|         "lint": "eslint . --fix", |         "lint": "eslint . --fix", | ||||||
|  |         "pretest:unit": "npm run build", | ||||||
|         "test:unit": "vitest --run" |         "test:unit": "vitest --run" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|  | @ -24,6 +26,7 @@ | ||||||
|         "cross": "^1.0.0", |         "cross": "^1.0.0", | ||||||
|         "cross-env": "^7.0.3", |         "cross-env": "^7.0.3", | ||||||
|         "dotenv": "^16.4.7", |         "dotenv": "^16.4.7", | ||||||
|  |         "dwengo-1-common": "^0.1.1", | ||||||
|         "express": "^5.0.1", |         "express": "^5.0.1", | ||||||
|         "express-jwt": "^8.5.1", |         "express-jwt": "^8.5.1", | ||||||
|         "gift-pegjs": "^1.0.2", |         "gift-pegjs": "^1.0.2", | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; | import { createAssignment, getAllAssignments, getAssignment, getAssignmentsSubmissions } from '../services/assignments.js'; | ||||||
| import { AssignmentDTO } from '../interfaces/assignment.js'; | import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||||
| 
 | 
 | ||||||
| // Typescript is annoying with parameter forwarding from class.ts
 | // Typescript is annoying with parameter forwarding from class.ts
 | ||||||
| interface AssignmentParams { | interface AssignmentParams { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/classes.js'; | import { createClass, getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/classes.js'; | ||||||
| import { ClassDTO } from '../interfaces/class.js'; | import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
| 
 | 
 | ||||||
| export async function getAllClassesHandler(req: Request, res: Response): Promise<void> { | export async function getAllClassesHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								backend/src/controllers/error-helper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								backend/src/controllers/error-helper.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | import { BadRequestException } from '../exceptions/bad-request-exception.js'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Checks for the presence of required fields and throws a BadRequestException | ||||||
|  |  * if any are missing. | ||||||
|  |  * | ||||||
|  |  * @param fields - An object with key-value pairs to validate. | ||||||
|  |  */ | ||||||
|  | export function requireFields(fields: Record<string, unknown>): void { | ||||||
|  |     const missing = Object.entries(fields) | ||||||
|  |         .filter(([_, value]) => value === undefined || value === null || value === '') | ||||||
|  |         .map(([key]) => key); | ||||||
|  | 
 | ||||||
|  |     if (missing.length > 0) { | ||||||
|  |         const message = `Missing required field${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`; | ||||||
|  |         throw new BadRequestException(message); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; | import { createGroup, getAllGroups, getGroup, getGroupSubmissions } from '../services/groups.js'; | ||||||
| import { GroupDTO } from '../interfaces/group.js'; | import { GroupDTO } from '@dwengo-1/common/interfaces/group'; | ||||||
| 
 | 
 | ||||||
| // Typescript is annoywith with parameter forwarding from class.ts
 | // Typescript is annoywith with parameter forwarding from class.ts
 | ||||||
| interface GroupParams { | interface GroupParams { | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../interfaces/learning-content.js'; |  | ||||||
| import learningObjectService from '../services/learning-objects/learning-object-service.js'; | import learningObjectService from '../services/learning-objects/learning-object-service.js'; | ||||||
| import { envVars, getEnvVar } from '../util/envVars.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Language } from '../entities/content/language.js'; |  | ||||||
| import attachmentService from '../services/learning-objects/attachment-service.js'; | import attachmentService from '../services/learning-objects/attachment-service.js'; | ||||||
| import { NotFoundError } from '@mikro-orm/core'; |  | ||||||
| import { BadRequestException } from '../exceptions/bad-request-exception.js'; | import { BadRequestException } from '../exceptions/bad-request-exception.js'; | ||||||
|  | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
|  | import { envVars, getEnvVar } from '../util/envVars.js'; | ||||||
|  | import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { | function getLearningObjectIdentifierFromRequest(req: Request): LearningObjectIdentifier { | ||||||
|     if (!req.params.hruid) { |     if (!req.params.hruid) { | ||||||
|  | @ -47,6 +47,11 @@ export async function getLearningObject(req: Request, res: Response): Promise<vo | ||||||
|     const learningObjectId = getLearningObjectIdentifierFromRequest(req); |     const learningObjectId = getLearningObjectIdentifierFromRequest(req); | ||||||
| 
 | 
 | ||||||
|     const learningObject = await learningObjectService.getLearningObjectById(learningObjectId); |     const learningObject = await learningObjectService.getLearningObjectById(learningObjectId); | ||||||
|  | 
 | ||||||
|  |     if (!learningObject) { | ||||||
|  |         throw new NotFoundException('Learning object not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     res.json(learningObject); |     res.json(learningObject); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -63,7 +68,7 @@ export async function getAttachment(req: Request, res: Response): Promise<void> | ||||||
|     const attachment = await attachmentService.getAttachment(learningObjectId, name); |     const attachment = await attachmentService.getAttachment(learningObjectId, name); | ||||||
| 
 | 
 | ||||||
|     if (!attachment) { |     if (!attachment) { | ||||||
|         throw new NotFoundError(`Attachment ${name} not found`); |         throw new NotFoundException(`Attachment ${name} not found`); | ||||||
|     } |     } | ||||||
|     res.setHeader('Content-Type', attachment.mimeType).send(attachment.content); |     res.setHeader('Content-Type', attachment.mimeType).send(attachment.content); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { Request, Response } from 'express'; | ||||||
| import { themes } from '../data/themes.js'; | import { themes } from '../data/themes.js'; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| import learningPathService from '../services/learning-paths/learning-path-service.js'; | import learningPathService from '../services/learning-paths/learning-path-service.js'; | ||||||
| import { Language } from '../entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { | import { | ||||||
|     PersonalizationTarget, |     PersonalizationTarget, | ||||||
|     personalizedForGroup, |     personalizedForGroup, | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createQuestion, deleteQuestion, getAllQuestions, getAnswersByQuestion, getQuestion } from '../services/questions.js'; | import { createQuestion, deleteQuestion, getAllQuestions, getAnswersByQuestion, getQuestion } from '../services/questions.js'; | ||||||
| import { QuestionDTO, QuestionId } from '../interfaces/question.js'; |  | ||||||
| import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; | import { FALLBACK_LANG, FALLBACK_SEQ_NUM } from '../config.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { Language } from '../entities/content/language.js'; | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { | function getObjectId(req: Request, res: Response): LearningObjectIdentifier | null { | ||||||
|     const { hruid, version } = req.params; |     const { hruid, version } = req.params; | ||||||
|  |  | ||||||
|  | @ -1,99 +1,67 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { | import { | ||||||
|  |     createClassJoinRequest, | ||||||
|     createStudent, |     createStudent, | ||||||
|  |     deleteClassJoinRequest, | ||||||
|     deleteStudent, |     deleteStudent, | ||||||
|     getAllStudents, |     getAllStudents, | ||||||
|  |     getJoinRequestByStudentClass, | ||||||
|  |     getJoinRequestsByStudent, | ||||||
|     getStudent, |     getStudent, | ||||||
|     getStudentAssignments, |     getStudentAssignments, | ||||||
|     getStudentClasses, |     getStudentClasses, | ||||||
|     getStudentGroups, |     getStudentGroups, | ||||||
|  |     getStudentQuestions, | ||||||
|     getStudentSubmissions, |     getStudentSubmissions, | ||||||
| } from '../services/students.js'; | } from '../services/students.js'; | ||||||
| import { StudentDTO } from '../interfaces/student.js'; | import { requireFields } from './error-helper.js'; | ||||||
|  | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
| 
 | 
 | ||||||
| // TODO: accept arguments (full, ...)
 |  | ||||||
| // TODO: endpoints
 |  | ||||||
| export async function getAllStudentsHandler(req: Request, res: Response): Promise<void> { | export async function getAllStudentsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 | 
 | ||||||
|     const students = await getAllStudents(full); |     const students: StudentDTO[] | string[] = await getAllStudents(full); | ||||||
| 
 | 
 | ||||||
|     if (!students) { |     res.json({ students }); | ||||||
|         res.status(404).json({ error: `Student not found.` }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json({ students: students }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentHandler(req: Request, res: Response): Promise<void> { | export async function getStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const student = await getStudent(username); | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const user = await getStudent(username); |     res.json({ student }); | ||||||
| 
 |  | ||||||
|     if (!user) { |  | ||||||
|         res.status(404).json({ |  | ||||||
|             error: `User with username '${username}' not found.`, |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json(user); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createStudentHandler(req: Request, res: Response): Promise<void> { | export async function createStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.body.username; | ||||||
|  |     const firstName = req.body.firstName; | ||||||
|  |     const lastName = req.body.lastName; | ||||||
|  |     requireFields({ username, firstName, lastName }); | ||||||
|  | 
 | ||||||
|     const userData = req.body as StudentDTO; |     const userData = req.body as StudentDTO; | ||||||
| 
 | 
 | ||||||
|     if (!userData.username || !userData.firstName || !userData.lastName) { |     const student = await createStudent(userData); | ||||||
|         res.status(400).json({ |     res.json({ student }); | ||||||
|             error: 'Missing required fields: username, firstName, lastName', |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const newUser = await createStudent(userData); |  | ||||||
| 
 |  | ||||||
|     if (!newUser) { |  | ||||||
|         res.status(500).json({ |  | ||||||
|             error: 'Something went wrong while creating student', |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(201).json(newUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteStudentHandler(req: Request, res: Response): Promise<void> { | export async function deleteStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const student = await deleteStudent(username); | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |     res.json({ student }); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const deletedUser = await deleteStudent(username); |  | ||||||
|     if (!deletedUser) { |  | ||||||
|         res.status(404).json({ |  | ||||||
|             error: `User with username '${username}' not found.`, |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(200).json(deletedUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentClassesHandler(req: Request, res: Response): Promise<void> { | export async function getStudentClassesHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.id; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     const classes = await getStudentClasses(username, full); |     const classes = await getStudentClasses(username, full); | ||||||
| 
 | 
 | ||||||
|     res.json({ classes: classes }); |     res.json({ classes }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO
 | // TODO
 | ||||||
|  | @ -102,33 +70,75 @@ export async function getStudentClassesHandler(req: Request, res: Response): Pro | ||||||
| // Have this assignment.
 | // Have this assignment.
 | ||||||
| export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.id; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     const assignments = getStudentAssignments(username, full); |     const assignments = getStudentAssignments(username, full); | ||||||
| 
 | 
 | ||||||
|     res.json({ |     res.json({ assignments }); | ||||||
|         assignments: assignments, |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentGroupsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentGroupsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|     const username = req.params.id; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     const groups = await getStudentGroups(username, full); |     const groups = await getStudentGroups(username, full); | ||||||
| 
 | 
 | ||||||
|     res.json({ |     res.json({ groups }); | ||||||
|         groups: groups, |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise<void> { | export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.id; |     const username = req.params.username; | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     const submissions = await getStudentSubmissions(username, full); |     const submissions = await getStudentSubmissions(username, full); | ||||||
| 
 | 
 | ||||||
|     res.json({ |     res.json({ submissions }); | ||||||
|         submissions: submissions, | } | ||||||
|     }); | 
 | ||||||
|  | export async function getStudentQuestionsHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const full = req.query.full === 'true'; | ||||||
|  |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
|  | 
 | ||||||
|  |     const questions = await getStudentQuestions(username, full); | ||||||
|  | 
 | ||||||
|  |     res.json({ questions }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     const classId = req.body.classId; | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     const request = await createClassJoinRequest(username, classId); | ||||||
|  |     res.json({ request }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentRequestsHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
|  | 
 | ||||||
|  |     const requests = await getJoinRequestsByStudent(username); | ||||||
|  |     res.json({ requests }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     const request = await getJoinRequestByStudentClass(username, classId); | ||||||
|  |     res.json({ request }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteClassJoinRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ username, classId }); | ||||||
|  | 
 | ||||||
|  |     const request = await deleteClassJoinRequest(username, classId); | ||||||
|  |     res.json({ request }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; | import { createSubmission, deleteSubmission, getSubmission } from '../services/submissions.js'; | ||||||
| import { Language, languageMap } from '../entities/content/language.js'; | import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { SubmissionDTO } from '../interfaces/submission'; | import { Language, languageMap } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| interface SubmissionParams { | interface SubmissionParams { | ||||||
|     hruid: string; |     hruid: string; | ||||||
|  |  | ||||||
|  | @ -4,137 +4,97 @@ import { | ||||||
|     deleteTeacher, |     deleteTeacher, | ||||||
|     getAllTeachers, |     getAllTeachers, | ||||||
|     getClassesByTeacher, |     getClassesByTeacher, | ||||||
|     getQuestionsByTeacher, |     getJoinRequestsByClass, | ||||||
|     getStudentsByTeacher, |     getStudentsByTeacher, | ||||||
|     getTeacher, |     getTeacher, | ||||||
|  |     getTeacherQuestions, | ||||||
|  |     updateClassJoinRequestStatus, | ||||||
| } from '../services/teachers.js'; | } from '../services/teachers.js'; | ||||||
| import { TeacherDTO } from '../interfaces/teacher.js'; | import { requireFields } from './error-helper.js'; | ||||||
|  | import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachersHandler(req: Request, res: Response): Promise<void> { | export async function getAllTeachersHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 | 
 | ||||||
|     const teachers = await getAllTeachers(full); |     const teachers: TeacherDTO[] | string[] = await getAllTeachers(full); | ||||||
| 
 | 
 | ||||||
|     if (!teachers) { |     res.json({ teachers }); | ||||||
|         res.status(404).json({ error: `Teacher not found.` }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json({ teachers: teachers }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const teacher = await getTeacher(username); | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const user = await getTeacher(username); |     res.json({ teacher }); | ||||||
| 
 |  | ||||||
|     if (!user) { |  | ||||||
|         res.status(404).json({ |  | ||||||
|             error: `Teacher '${username}' not found.`, |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json(user); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createTeacherHandler(req: Request, res: Response): Promise<void> { | export async function createTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.body.username; | ||||||
|  |     const firstName = req.body.firstName; | ||||||
|  |     const lastName = req.body.lastName; | ||||||
|  |     requireFields({ username, firstName, lastName }); | ||||||
|  | 
 | ||||||
|     const userData = req.body as TeacherDTO; |     const userData = req.body as TeacherDTO; | ||||||
| 
 | 
 | ||||||
|     if (!userData.username || !userData.firstName || !userData.lastName) { |     const teacher = await createTeacher(userData); | ||||||
|         res.status(400).json({ |     res.json({ teacher }); | ||||||
|             error: 'Missing required fields: username, firstName, lastName', |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const newUser = await createTeacher(userData); |  | ||||||
| 
 |  | ||||||
|     if (!newUser) { |  | ||||||
|         res.status(400).json({ error: 'Failed to create teacher' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(201).json(newUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteTeacherHandler(req: Request, res: Response): Promise<void> { | export async function deleteTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const teacher = await deleteTeacher(username); | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |     res.json({ teacher }); | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const deletedUser = await deleteTeacher(username); |  | ||||||
|     if (!deletedUser) { |  | ||||||
|         res.status(404).json({ |  | ||||||
|             error: `User '${username}' not found.`, |  | ||||||
|         }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.status(200).json(deletedUser); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classes = await getClassesByTeacher(username, full); |     const classes = await getClassesByTeacher(username, full); | ||||||
| 
 | 
 | ||||||
|     if (!classes) { |     res.json({ classes }); | ||||||
|         res.status(404).json({ error: 'Teacher not found' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json({ classes: classes }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacherStudentHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
| 
 |     requireFields({ username }); | ||||||
|     if (!username) { |  | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const students = await getStudentsByTeacher(username, full); |     const students = await getStudentsByTeacher(username, full); | ||||||
| 
 | 
 | ||||||
|     if (!students) { |     res.json({ students }); | ||||||
|         res.status(404).json({ error: 'Teacher not found' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     res.json({ students: students }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacherQuestionHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherQuestionHandler(req: Request, res: Response): Promise<void> { | ||||||
|     const username = req.params.username; |     const username = req.params.username; | ||||||
|     const full = req.query.full === 'true'; |     const full = req.query.full === 'true'; | ||||||
|  |     requireFields({ username }); | ||||||
| 
 | 
 | ||||||
|     if (!username) { |     const questions = await getTeacherQuestions(username, full); | ||||||
|         res.status(400).json({ error: 'Missing required field: username' }); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const questions = await getQuestionsByTeacher(username, full); |     res.json({ questions }); | ||||||
| 
 | } | ||||||
|     if (!questions) { | 
 | ||||||
|         res.status(404).json({ error: 'Teacher not found' }); | export async function getStudentJoinRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|         return; |     const username = req.query.username as string; | ||||||
|     } |     const classId = req.params.classId; | ||||||
| 
 |     requireFields({ username, classId }); | ||||||
|     res.json({ questions: questions }); | 
 | ||||||
|  |     const joinRequests = await getJoinRequestsByClass(classId); | ||||||
|  |     res.json({ joinRequests }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateStudentJoinRequestHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const studentUsername = req.query.studentUsername as string; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     const accepted = req.body.accepted !== 'false'; // Default = true
 | ||||||
|  |     requireFields({ studentUsername, classId }); | ||||||
|  | 
 | ||||||
|  |     const request = await updateClassJoinRequestStatus(studentUsername, classId, accepted); | ||||||
|  |     res.json({ request }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,13 +2,17 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { Class } from '../../entities/classes/class.entity.js'; | import { Class } from '../../entities/classes/class.entity.js'; | ||||||
| import { ClassJoinRequest } from '../../entities/classes/class-join-request.entity.js'; | import { ClassJoinRequest } from '../../entities/classes/class-join-request.entity.js'; | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
|  | import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export class ClassJoinRequestRepository extends DwengoEntityRepository<ClassJoinRequest> { | export class ClassJoinRequestRepository extends DwengoEntityRepository<ClassJoinRequest> { | ||||||
|     public async findAllRequestsBy(requester: Student): Promise<ClassJoinRequest[]> { |     public async findAllRequestsBy(requester: Student): Promise<ClassJoinRequest[]> { | ||||||
|         return this.findAll({ where: { requester: requester } }); |         return this.findAll({ where: { requester: requester } }); | ||||||
|     } |     } | ||||||
|     public async findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> { |     public async findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> { | ||||||
|         return this.findAll({ where: { class: clazz } }); |         return this.findAll({ where: { class: clazz, status: ClassJoinRequestStatus.Open } }); // TODO check if works like this
 | ||||||
|  |     } | ||||||
|  |     public async findByStudentAndClass(requester: Student, clazz: Class): Promise<ClassJoinRequest | null> { | ||||||
|  |         return this.findOne({ requester, class: clazz }); | ||||||
|     } |     } | ||||||
|     public async deleteBy(requester: Student, clazz: Class): Promise<void> { |     public async deleteBy(requester: Student, clazz: Class): Promise<void> { | ||||||
|         return this.deleteWhere({ requester: requester, class: clazz }); |         return this.deleteWhere({ requester: requester, class: clazz }); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { Attachment } from '../../entities/content/attachment.entity.js'; | import { Attachment } from '../../entities/content/attachment.entity.js'; | ||||||
| import { Language } from '../../entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier'; | ||||||
| 
 | 
 | ||||||
| export class AttachmentRepository extends DwengoEntityRepository<Attachment> { | export class AttachmentRepository extends DwengoEntityRepository<Attachment> { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { LearningObject } from '../../entities/content/learning-object.entity.js'; | import { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||||
| import { Language } from '../../entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Teacher } from '../../entities/users/teacher.entity.js'; | import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
| 
 | 
 | ||||||
| export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { LearningPath } from '../../entities/content/learning-path.entity.js'; | import { LearningPath } from '../../entities/content/learning-path.entity.js'; | ||||||
| import { Language } from '../../entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | export class LearningPathRepository extends DwengoEntityRepository<LearningPath> { | ||||||
|     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { |     public async findByHruidAndLanguage(hruid: string, language: Language): Promise<LearningPath | null> { | ||||||
|  |  | ||||||
|  | @ -54,4 +54,11 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|             orderBy: { timestamp: 'ASC' }, |             orderBy: { timestamp: 'ASC' }, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public async findAllByAuthor(author: Student): Promise<Question[]> { | ||||||
|  |         return this.findAll({ | ||||||
|  |             where: { author }, | ||||||
|  |             orderBy: { timestamp: 'DESC' }, // New to old
 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,4 @@ | ||||||
| export interface Theme { | import { Theme } from '@dwengo-1/common/interfaces/theme'; | ||||||
|     title: string; |  | ||||||
|     hruids: string[]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export const themes: Theme[] = [ | export const themes: Theme[] = [ | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Class } from '../classes/class.entity.js'; | import { Class } from '../classes/class.entity.js'; | ||||||
| import { Group } from './group.entity.js'; | import { Group } from './group.entity.js'; | ||||||
| import { Language } from '../content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { AssignmentRepository } from '../../data/assignments/assignment-repository.js'; | import { AssignmentRepository } from '../../data/assignments/assignment-repository.js'; | ||||||
| 
 | 
 | ||||||
| @Entity({ | @Entity({ | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { Student } from '../users/student.entity.js'; | import { Student } from '../users/student.entity.js'; | ||||||
| import { Group } from './group.entity.js'; | import { Group } from './group.entity.js'; | ||||||
| import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; | import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Language } from '../content/language.js'; |  | ||||||
| import { SubmissionRepository } from '../../data/assignments/submission-repository.js'; | import { SubmissionRepository } from '../../data/assignments/submission-repository.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| @Entity({ repository: () => SubmissionRepository }) | @Entity({ repository: () => SubmissionRepository }) | ||||||
| export class Submission { | export class Submission { | ||||||
|  |  | ||||||
|  | @ -2,12 +2,7 @@ import { Entity, Enum, ManyToOne } from '@mikro-orm/core'; | ||||||
| import { Student } from '../users/student.entity.js'; | import { Student } from '../users/student.entity.js'; | ||||||
| import { Class } from './class.entity.js'; | import { Class } from './class.entity.js'; | ||||||
| import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; | import { ClassJoinRequestRepository } from '../../data/classes/class-join-request-repository.js'; | ||||||
| 
 | import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| export enum ClassJoinRequestStatus { |  | ||||||
|     Open = 'open', |  | ||||||
|     Accepted = 'accepted', |  | ||||||
|     Declined = 'declined', |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| @Entity({ | @Entity({ | ||||||
|     repository: () => ClassJoinRequestRepository, |     repository: () => ClassJoinRequestRepository, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { Language } from './language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| export class LearningObjectIdentifier { | export class LearningObjectIdentifier { | ||||||
|     constructor( |     constructor( | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import { Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | import { Embedded, Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Language } from './language.js'; |  | ||||||
| import { Attachment } from './attachment.entity.js'; | import { Attachment } from './attachment.entity.js'; | ||||||
| import { Teacher } from '../users/teacher.entity.js'; | import { Teacher } from '../users/teacher.entity.js'; | ||||||
| import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; | import { DwengoContentType } from '../../services/learning-objects/processing/content-type.js'; | ||||||
|  | @ -7,6 +6,7 @@ import { v4 } from 'uuid'; | ||||||
| import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; | import { LearningObjectRepository } from '../../data/content/learning-object-repository.js'; | ||||||
| import { EducationalGoal } from './educational-goal.entity.js'; | import { EducationalGoal } from './educational-goal.entity.js'; | ||||||
| import { ReturnValue } from './return-value.entity.js'; | import { ReturnValue } from './return-value.entity.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| @Entity({ repository: () => LearningObjectRepository }) | @Entity({ repository: () => LearningObjectRepository }) | ||||||
| export class LearningObject { | export class LearningObject { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; | import { Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property, Rel } from '@mikro-orm/core'; | ||||||
| import { Language } from './language.js'; |  | ||||||
| import { LearningPath } from './learning-path.entity.js'; | import { LearningPath } from './learning-path.entity.js'; | ||||||
| import { LearningPathTransition } from './learning-path-transition.entity.js'; | import { LearningPathTransition } from './learning-path-transition.entity.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| @Entity() | @Entity() | ||||||
| export class LearningPathNode { | export class LearningPathNode { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | import { Entity, Enum, ManyToMany, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Language } from './language.js'; |  | ||||||
| import { Teacher } from '../users/teacher.entity.js'; | import { Teacher } from '../users/teacher.entity.js'; | ||||||
| import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; | import { LearningPathRepository } from '../../data/content/learning-path-repository.js'; | ||||||
| import { LearningPathNode } from './learning-path-node.entity.js'; | import { LearningPathNode } from './learning-path-node.entity.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| @Entity({ repository: () => LearningPathRepository }) | @Entity({ repository: () => LearningPathRepository }) | ||||||
| export class LearningPath { | export class LearningPath { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; | import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'; | ||||||
| import { Language } from '../content/language.js'; |  | ||||||
| import { Student } from '../users/student.entity.js'; | import { Student } from '../users/student.entity.js'; | ||||||
| import { QuestionRepository } from '../../data/questions/question-repository.js'; | import { QuestionRepository } from '../../data/questions/question-repository.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| @Entity({ repository: () => QuestionRepository }) | @Entity({ repository: () => QuestionRepository }) | ||||||
| export class Question { | export class Question { | ||||||
|  |  | ||||||
|  | @ -1,14 +1,7 @@ | ||||||
| import { mapToUserDTO, UserDTO } from './user.js'; | import { mapToUserDTO } from './user.js'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from './question.js'; | import { mapToQuestionDTO, mapToQuestionDTOId } from './question.js'; | ||||||
| import { Answer } from '../entities/questions/answer.entity.js'; | import { Answer } from '../entities/questions/answer.entity.js'; | ||||||
| 
 | import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; | ||||||
| export interface AnswerDTO { |  | ||||||
|     author: UserDTO; |  | ||||||
|     toQuestion: QuestionDTO; |  | ||||||
|     sequenceNumber: number; |  | ||||||
|     timestamp: string; |  | ||||||
|     content: string; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Convert a Question entity to a DTO format. |  * Convert a Question entity to a DTO format. | ||||||
|  | @ -23,16 +16,10 @@ export function mapToAnswerDTO(answer: Answer): AnswerDTO { | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface AnswerId { | export function mapToAnswerDTOId(answer: Answer): AnswerId { | ||||||
|     author: string; |  | ||||||
|     toQuestion: QuestionId; |  | ||||||
|     sequenceNumber: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function mapToAnswerId(answer: AnswerDTO): AnswerId { |  | ||||||
|     return { |     return { | ||||||
|         author: answer.author.username, |         author: answer.author.username, | ||||||
|         toQuestion: mapToQuestionId(answer.toQuestion), |         toQuestion: mapToQuestionDTOId(answer.toQuestion), | ||||||
|         sequenceNumber: answer.sequenceNumber, |         sequenceNumber: answer.sequenceNumber!, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,19 +1,9 @@ | ||||||
|  | import { languageMap } from '@dwengo-1/common/util/language'; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| import { Assignment } from '../entities/assignments/assignment.entity.js'; | import { Assignment } from '../entities/assignments/assignment.entity.js'; | ||||||
| import { Class } from '../entities/classes/class.entity.js'; | import { Class } from '../entities/classes/class.entity.js'; | ||||||
| import { languageMap } from '../entities/content/language.js'; |  | ||||||
| import { GroupDTO } from './group.js'; |  | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
| 
 | import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||||
| export interface AssignmentDTO { |  | ||||||
|     id: number; |  | ||||||
|     class: string; // Id of class 'within'
 |  | ||||||
|     title: string; |  | ||||||
|     description: string; |  | ||||||
|     learningPath: string; |  | ||||||
|     language: string; |  | ||||||
|     groups?: GroupDTO[] | string[]; // TODO
 |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { | export function mapToAssignmentDTOId(assignment: Assignment): AssignmentDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -2,14 +2,7 @@ import { Collection } from '@mikro-orm/core'; | ||||||
| import { Class } from '../entities/classes/class.entity.js'; | import { Class } from '../entities/classes/class.entity.js'; | ||||||
| import { Student } from '../entities/users/student.entity.js'; | import { Student } from '../entities/users/student.entity.js'; | ||||||
| import { Teacher } from '../entities/users/teacher.entity.js'; | import { Teacher } from '../entities/users/teacher.entity.js'; | ||||||
| 
 | import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
| export interface ClassDTO { |  | ||||||
|     id: string; |  | ||||||
|     displayName: string; |  | ||||||
|     teachers: string[]; |  | ||||||
|     students: string[]; |  | ||||||
|     joinRequests: string[]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToClassDTO(cls: Class): ClassDTO { | export function mapToClassDTO(cls: Class): ClassDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,12 +1,7 @@ | ||||||
| import { Group } from '../entities/assignments/group.entity.js'; | import { Group } from '../entities/assignments/group.entity.js'; | ||||||
| import { AssignmentDTO, mapToAssignmentDTO } from './assignment.js'; | import { mapToAssignmentDTO } from './assignment.js'; | ||||||
| import { mapToStudentDTO, StudentDTO } from './student.js'; | import { mapToStudentDTO } from './student.js'; | ||||||
| 
 | import { GroupDTO } from '@dwengo-1/common/interfaces/group'; | ||||||
| export interface GroupDTO { |  | ||||||
|     assignment: number | AssignmentDTO; |  | ||||||
|     groupNumber: number; |  | ||||||
|     members: string[] | StudentDTO[]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToGroupDTO(group: Group): GroupDTO { | export function mapToGroupDTO(group: Group): GroupDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,24 +1,21 @@ | ||||||
| import { Question } from '../entities/questions/question.entity.js'; | import { Question } from '../entities/questions/question.entity.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { mapToStudentDTO } from './student.js'; | ||||||
| import { mapToStudentDTO, StudentDTO } from './student.js'; | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
|  | import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| export interface QuestionDTO { | function getLearningObjectIdentifier(question: Question): LearningObjectIdentifier { | ||||||
|     learningObjectIdentifier: LearningObjectIdentifier; |     return { | ||||||
|     sequenceNumber?: number; |         hruid: question.learningObjectHruid, | ||||||
|     author: StudentDTO; |         language: question.learningObjectLanguage, | ||||||
|     timestamp?: string; |         version: question.learningObjectVersion, | ||||||
|     content: string; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Convert a Question entity to a DTO format. |  * Convert a Question entity to a DTO format. | ||||||
|  */ |  */ | ||||||
| export function mapToQuestionDTO(question: Question): QuestionDTO { | export function mapToQuestionDTO(question: Question): QuestionDTO { | ||||||
|     const learningObjectIdentifier = { |     const learningObjectIdentifier = getLearningObjectIdentifier(question); | ||||||
|         hruid: question.learningObjectHruid, |  | ||||||
|         language: question.learningObjectLanguage, |  | ||||||
|         version: question.learningObjectVersion, |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|         learningObjectIdentifier, |         learningObjectIdentifier, | ||||||
|  | @ -29,14 +26,11 @@ export function mapToQuestionDTO(question: Question): QuestionDTO { | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface QuestionId { | export function mapToQuestionDTOId(question: Question): QuestionId { | ||||||
|     learningObjectIdentifier: LearningObjectIdentifier; |     const learningObjectIdentifier = getLearningObjectIdentifier(question); | ||||||
|     sequenceNumber: number; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToQuestionId(question: QuestionDTO): QuestionId { |  | ||||||
|     return { |     return { | ||||||
|         learningObjectIdentifier: question.learningObjectIdentifier, |         learningObjectIdentifier, | ||||||
|         sequenceNumber: question.sequenceNumber!, |         sequenceNumber: question.sequenceNumber!, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								backend/src/interfaces/student-request.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								backend/src/interfaces/student-request.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | import { mapToStudentDTO } from './student.js'; | ||||||
|  | import { ClassJoinRequest } from '../entities/classes/class-join-request.entity.js'; | ||||||
|  | import { getClassJoinRequestRepository } from '../data/repositories.js'; | ||||||
|  | import { Student } from '../entities/users/student.entity.js'; | ||||||
|  | import { Class } from '../entities/classes/class.entity.js'; | ||||||
|  | import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | ||||||
|  | import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
|  | 
 | ||||||
|  | export function mapToStudentRequestDTO(request: ClassJoinRequest): ClassJoinRequestDTO { | ||||||
|  |     return { | ||||||
|  |         requester: mapToStudentDTO(request.requester), | ||||||
|  |         class: request.class.classId!, | ||||||
|  |         status: request.status, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function mapToStudentRequest(student: Student, cls: Class): ClassJoinRequest { | ||||||
|  |     return getClassJoinRequestRepository().create({ | ||||||
|  |         requester: student, | ||||||
|  |         class: cls, | ||||||
|  |         status: ClassJoinRequestStatus.Open, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | @ -1,18 +1,6 @@ | ||||||
| import { Student } from '../entities/users/student.entity.js'; | import { Student } from '../entities/users/student.entity.js'; | ||||||
| import { getStudentRepository } from '../data/repositories.js'; | import { getStudentRepository } from '../data/repositories.js'; | ||||||
| 
 | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
| export interface StudentDTO { |  | ||||||
|     id: string; |  | ||||||
|     username: string; |  | ||||||
|     firstName: string; |  | ||||||
|     lastName: string; |  | ||||||
|     endpoints?: { |  | ||||||
|         classes: string; |  | ||||||
|         questions: string; |  | ||||||
|         invitations: string; |  | ||||||
|         groups: string; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToStudentDTO(student: Student): StudentDTO { | export function mapToStudentDTO(student: Student): StudentDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,26 +1,7 @@ | ||||||
| import { Submission } from '../entities/assignments/submission.entity.js'; | import { Submission } from '../entities/assignments/submission.entity.js'; | ||||||
| import { Language } from '../entities/content/language.js'; | import { mapToGroupDTO } from './group.js'; | ||||||
| import { GroupDTO, mapToGroupDTO } from './group.js'; | import { mapToStudent, mapToStudentDTO } from './student.js'; | ||||||
| import { mapToStudent, mapToStudentDTO, StudentDTO } from './student.js'; | import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { LearningObjectIdentifier } from './learning-content.js'; |  | ||||||
| 
 |  | ||||||
| export interface SubmissionDTO { |  | ||||||
|     learningObjectIdentifier: LearningObjectIdentifier; |  | ||||||
| 
 |  | ||||||
|     submissionNumber?: number; |  | ||||||
|     submitter: StudentDTO; |  | ||||||
|     time?: Date; |  | ||||||
|     group?: GroupDTO; |  | ||||||
|     content: string; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface SubmissionDTOId { |  | ||||||
|     learningObjectHruid: string; |  | ||||||
|     learningObjectLanguage: Language; |  | ||||||
|     learningObjectVersion: number; |  | ||||||
| 
 |  | ||||||
|     submissionNumber?: number; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { | export function mapToSubmissionDTO(submission: Submission): SubmissionDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,12 +1,7 @@ | ||||||
| import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; | import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; | ||||||
| import { ClassDTO, mapToClassDTO } from './class.js'; | import { mapToClassDTO } from './class.js'; | ||||||
| import { mapToUserDTO, UserDTO } from './user.js'; | import { mapToUserDTO } from './user.js'; | ||||||
| 
 | import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
| export interface TeacherInvitationDTO { |  | ||||||
|     sender: string | UserDTO; |  | ||||||
|     receiver: string | UserDTO; |  | ||||||
|     class: string | ClassDTO; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,18 +1,6 @@ | ||||||
| import { Teacher } from '../entities/users/teacher.entity.js'; | import { Teacher } from '../entities/users/teacher.entity.js'; | ||||||
| import { getTeacherRepository } from '../data/repositories.js'; | import { getTeacherRepository } from '../data/repositories.js'; | ||||||
| 
 | import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||||
| export interface TeacherDTO { |  | ||||||
|     id: string; |  | ||||||
|     username: string; |  | ||||||
|     firstName: string; |  | ||||||
|     lastName: string; |  | ||||||
|     endpoints?: { |  | ||||||
|         classes: string; |  | ||||||
|         questions: string; |  | ||||||
|         invitations: string; |  | ||||||
|         groups: string; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { | export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -1,17 +1,5 @@ | ||||||
| import { User } from '../entities/users/user.entity.js'; | import { User } from '../entities/users/user.entity.js'; | ||||||
| 
 | import { UserDTO } from '@dwengo-1/common/interfaces/user'; | ||||||
| export interface UserDTO { |  | ||||||
|     id?: string; |  | ||||||
|     username: string; |  | ||||||
|     firstName: string; |  | ||||||
|     lastName: string; |  | ||||||
|     endpoints?: { |  | ||||||
|         self: string; |  | ||||||
|         classes: string; |  | ||||||
|         questions: string; |  | ||||||
|         invitations: string; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export function mapToUserDTO(user: User): UserDTO { | export function mapToUserDTO(user: User): UserDTO { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								backend/src/routes/student-join-requests.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								backend/src/routes/student-join-requests.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | import express from 'express'; | ||||||
|  | import { | ||||||
|  |     createStudentRequestHandler, | ||||||
|  |     deleteClassJoinRequestHandler, | ||||||
|  |     getStudentRequestHandler, | ||||||
|  |     getStudentRequestsHandler, | ||||||
|  | } from '../controllers/students.js'; | ||||||
|  | 
 | ||||||
|  | const router = express.Router({ mergeParams: true }); | ||||||
|  | 
 | ||||||
|  | router.get('/', getStudentRequestsHandler); | ||||||
|  | 
 | ||||||
|  | router.post('/', createStudentRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.get('/:classId', getStudentRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.delete('/:classId', deleteClassJoinRequestHandler); | ||||||
|  | 
 | ||||||
|  | export default router; | ||||||
|  | @ -7,8 +7,10 @@ import { | ||||||
|     getStudentClassesHandler, |     getStudentClassesHandler, | ||||||
|     getStudentGroupsHandler, |     getStudentGroupsHandler, | ||||||
|     getStudentHandler, |     getStudentHandler, | ||||||
|  |     getStudentQuestionsHandler, | ||||||
|     getStudentSubmissionsHandler, |     getStudentSubmissionsHandler, | ||||||
| } from '../controllers/students.js'; | } from '../controllers/students.js'; | ||||||
|  | import joinRequestRouter from './student-join-requests.js'; | ||||||
| 
 | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -17,30 +19,26 @@ router.get('/', getAllStudentsHandler); | ||||||
| 
 | 
 | ||||||
| router.post('/', createStudentHandler); | router.post('/', createStudentHandler); | ||||||
| 
 | 
 | ||||||
| router.delete('/', deleteStudentHandler); |  | ||||||
| 
 |  | ||||||
| router.delete('/:username', deleteStudentHandler); | router.delete('/:username', deleteStudentHandler); | ||||||
| 
 | 
 | ||||||
| // Information about a student's profile
 | // Information about a student's profile
 | ||||||
| router.get('/:username', getStudentHandler); | router.get('/:username', getStudentHandler); | ||||||
| 
 | 
 | ||||||
| // The list of classes a student is in
 | // The list of classes a student is in
 | ||||||
| router.get('/:id/classes', getStudentClassesHandler); | router.get('/:username/classes', getStudentClassesHandler); | ||||||
| 
 | 
 | ||||||
| // The list of submissions a student has made
 | // The list of submissions a student has made
 | ||||||
| router.get('/:id/submissions', getStudentSubmissionsHandler); | router.get('/:username/submissions', getStudentSubmissionsHandler); | ||||||
| 
 | 
 | ||||||
| // The list of assignments a student has
 | // The list of assignments a student has
 | ||||||
| router.get('/:id/assignments', getStudentAssignmentsHandler); | router.get('/:username/assignments', getStudentAssignmentsHandler); | ||||||
| 
 | 
 | ||||||
| // The list of groups a student is in
 | // The list of groups a student is in
 | ||||||
| router.get('/:id/groups', getStudentGroupsHandler); | router.get('/:username/groups', getStudentGroupsHandler); | ||||||
| 
 | 
 | ||||||
| // A list of questions a user has created
 | // A list of questions a user has created
 | ||||||
| router.get('/:id/questions', (_req, res) => { | router.get('/:username/questions', getStudentQuestionsHandler); | ||||||
|     res.json({ | 
 | ||||||
|         questions: ['0'], | router.use('/:username/joinRequests', joinRequestRouter); | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| export default router; | export default router; | ||||||
|  |  | ||||||
|  | @ -3,10 +3,12 @@ import { | ||||||
|     createTeacherHandler, |     createTeacherHandler, | ||||||
|     deleteTeacherHandler, |     deleteTeacherHandler, | ||||||
|     getAllTeachersHandler, |     getAllTeachersHandler, | ||||||
|  |     getStudentJoinRequestHandler, | ||||||
|     getTeacherClassHandler, |     getTeacherClassHandler, | ||||||
|     getTeacherHandler, |     getTeacherHandler, | ||||||
|     getTeacherQuestionHandler, |     getTeacherQuestionHandler, | ||||||
|     getTeacherStudentHandler, |     getTeacherStudentHandler, | ||||||
|  |     updateStudentJoinRequestHandler, | ||||||
| } from '../controllers/teachers.js'; | } from '../controllers/teachers.js'; | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -15,8 +17,6 @@ router.get('/', getAllTeachersHandler); | ||||||
| 
 | 
 | ||||||
| router.post('/', createTeacherHandler); | router.post('/', createTeacherHandler); | ||||||
| 
 | 
 | ||||||
| router.delete('/', deleteTeacherHandler); |  | ||||||
| 
 |  | ||||||
| router.get('/:username', getTeacherHandler); | router.get('/:username', getTeacherHandler); | ||||||
| 
 | 
 | ||||||
| router.delete('/:username', deleteTeacherHandler); | router.delete('/:username', deleteTeacherHandler); | ||||||
|  | @ -27,6 +27,10 @@ router.get('/:username/students', getTeacherStudentHandler); | ||||||
| 
 | 
 | ||||||
| router.get('/:username/questions', getTeacherQuestionHandler); | router.get('/:username/questions', getTeacherQuestionHandler); | ||||||
| 
 | 
 | ||||||
|  | router.get('/:username/joinRequests/:classId', getStudentJoinRequestHandler); | ||||||
|  | 
 | ||||||
|  | router.put('/:username/joinRequests/:classId/:studentUsername', updateStudentJoinRequestHandler); | ||||||
|  | 
 | ||||||
| // Invitations to other classes a teacher received
 | // Invitations to other classes a teacher received
 | ||||||
| router.get('/:id/invitations', (_req, res) => { | router.get('/:id/invitations', (_req, res) => { | ||||||
|     res.json({ |     res.json({ | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| import { getAssignmentRepository, getClassRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; | import { getAssignmentRepository, getClassRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; | ||||||
| import { AssignmentDTO, mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; | import { mapToAssignment, mapToAssignmentDTO, mapToAssignmentDTOId } from '../interfaces/assignment.js'; | ||||||
| import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; | import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; | ||||||
|  | import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||||
|  | import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
| 
 | 
 | ||||||
| export async function getAllAssignments(classid: string, full: boolean): Promise<AssignmentDTO[]> { | export async function getAllAssignments(classid: string, full: boolean): Promise<AssignmentDTO[]> { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,27 @@ | ||||||
| import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; | import { getClassRepository, getStudentRepository, getTeacherInvitationRepository, getTeacherRepository } from '../data/repositories.js'; | ||||||
| import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; | import { mapToClassDTO } from '../interfaces/class.js'; | ||||||
| import { mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; | import { mapToStudentDTO } from '../interfaces/student.js'; | ||||||
| import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from '../interfaces/teacher-invitation.js'; | import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds } from '../interfaces/teacher-invitation.js'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
|  | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
|  | import { Class } from '../entities/classes/class.entity.js'; | ||||||
|  | import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
|  | import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
|  | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
| 
 | 
 | ||||||
| const logger = getLogger(); | const logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  | export async function fetchClass(classId: string): Promise<Class> { | ||||||
|  |     const classRepository = getClassRepository(); | ||||||
|  |     const cls = await classRepository.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls) { | ||||||
|  |         throw new NotFoundException('Class with id not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return cls; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> { | export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> { | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.find({}, { populate: ['students', 'teachers'] }); |     const classes = await classRepository.find({}, { populate: ['students', 'teachers'] }); | ||||||
|  |  | ||||||
|  | @ -6,8 +6,10 @@ import { | ||||||
|     getSubmissionRepository, |     getSubmissionRepository, | ||||||
| } from '../data/repositories.js'; | } from '../data/repositories.js'; | ||||||
| import { Group } from '../entities/assignments/group.entity.js'; | import { Group } from '../entities/assignments/group.entity.js'; | ||||||
| import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; | import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; | ||||||
| import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; | import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; | ||||||
|  | import { GroupDTO } from '@dwengo-1/common/interfaces/group'; | ||||||
|  | import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
| 
 | 
 | ||||||
| export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise<GroupDTO | null> { | export async function getGroup(classId: string, assignmentNumber: number, groupNumber: number, full: boolean): Promise<GroupDTO | null> { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,12 @@ | ||||||
| import { DWENGO_API_BASE } from '../config.js'; | import { DWENGO_API_BASE } from '../config.js'; | ||||||
| import { fetchWithLogging } from '../util/api-helper.js'; | import { fetchWithLogging } from '../util/api-helper.js'; | ||||||
| import { FilteredLearningObject, LearningObjectMetadata, LearningObjectNode, LearningPathResponse } from '../interfaces/learning-content.js'; | 
 | ||||||
|  | import { | ||||||
|  |     FilteredLearningObject, | ||||||
|  |     LearningObjectMetadata, | ||||||
|  |     LearningObjectNode, | ||||||
|  |     LearningPathResponse, | ||||||
|  | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { getLogger } from '../logging/initalize.js'; | ||||||
| 
 | 
 | ||||||
| function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { | function filterData(data: LearningObjectMetadata, htmlUrl: string): FilteredLearningObject { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { getAttachmentRepository } from '../../data/repositories.js'; | import { getAttachmentRepository } from '../../data/repositories.js'; | ||||||
| import { Attachment } from '../../entities/content/attachment.entity.js'; | import { Attachment } from '../../entities/content/attachment.entity.js'; | ||||||
| import { LearningObjectIdentifier } from '../../interfaces/learning-content.js'; | 
 | ||||||
|  | import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| const attachmentService = { | const attachmentService = { | ||||||
|     async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> { |     async getAttachment(learningObjectId: LearningObjectIdentifier, attachmentName: string): Promise<Attachment | null> { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import { LearningObjectProvider } from './learning-object-provider.js'; | import { LearningObjectProvider } from './learning-object-provider.js'; | ||||||
| import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; |  | ||||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories.js'; | import { getLearningObjectRepository, getLearningPathRepository } from '../../data/repositories.js'; | ||||||
| import { LearningObject } from '../../entities/content/learning-object.entity.js'; | import { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||||
| import { getUrlStringForLearningObject } from '../../util/links.js'; | import { getUrlStringForLearningObject } from '../../util/links.js'; | ||||||
|  | @ -7,6 +6,7 @@ import processingService from './processing/processing-service.js'; | ||||||
| import { NotFoundError } from '@mikro-orm/core'; | import { NotFoundError } from '@mikro-orm/core'; | ||||||
| import learningObjectService from './learning-object-service.js'; | import learningObjectService from './learning-object-service.js'; | ||||||
| import { getLogger, Logger } from '../../logging/initalize.js'; | import { getLogger, Logger } from '../../logging/initalize.js'; | ||||||
|  | import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,8 @@ | ||||||
| import { DWENGO_API_BASE } from '../../config.js'; | import { DWENGO_API_BASE } from '../../config.js'; | ||||||
| import { fetchWithLogging } from '../../util/api-helper.js'; | import { fetchWithLogging } from '../../util/api-helper.js'; | ||||||
|  | import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js'; | ||||||
|  | import { LearningObjectProvider } from './learning-object-provider.js'; | ||||||
|  | import { getLogger, Logger } from '../../logging/initalize.js'; | ||||||
| import { | import { | ||||||
|     FilteredLearningObject, |     FilteredLearningObject, | ||||||
|     LearningObjectIdentifier, |     LearningObjectIdentifier, | ||||||
|  | @ -7,10 +10,7 @@ import { | ||||||
|     LearningObjectNode, |     LearningObjectNode, | ||||||
|     LearningPathIdentifier, |     LearningPathIdentifier, | ||||||
|     LearningPathResponse, |     LearningPathResponse, | ||||||
| } from '../../interfaces/learning-content.js'; | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import dwengoApiLearningPathProvider from '../learning-paths/dwengo-api-learning-path-provider.js'; |  | ||||||
| import { LearningObjectProvider } from './learning-object-provider.js'; |  | ||||||
| import { getLogger, Logger } from '../../logging/initalize.js'; |  | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  | @ -66,12 +66,13 @@ async function fetchLearningObjects(learningPathId: LearningPathIdentifier, full | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const objects = await Promise.all( |         const objects = await Promise.all( | ||||||
|             nodes.map(async (node) => |             nodes.map(async (node) => { | ||||||
|                 dwengoApiLearningObjectProvider.getLearningObjectById({ |                 const learningObjectId: LearningObjectIdentifier = { | ||||||
|                     hruid: node.learningobject_hruid, |                     hruid: node.learningobject_hruid, | ||||||
|                     language: learningPathId.language, |                     language: learningPathId.language, | ||||||
|                 }) |                 }; | ||||||
|             ) |                 return dwengoApiLearningObjectProvider.getLearningObjectById(learningObjectId); | ||||||
|  |             }) | ||||||
|         ); |         ); | ||||||
|         return objects.filter((obj): obj is FilteredLearningObject => obj !== null); |         return objects.filter((obj): obj is FilteredLearningObject => obj !== null); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|  | @ -90,7 +91,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { | ||||||
|             metadataUrl, |             metadataUrl, | ||||||
|             `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, |             `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, | ||||||
|             { |             { | ||||||
|                 params: id, |                 params: { ...id }, | ||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  | @ -123,7 +124,7 @@ const dwengoApiLearningObjectProvider: LearningObjectProvider = { | ||||||
|     async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { |     async getLearningObjectHTML(id: LearningObjectIdentifier): Promise<string | null> { | ||||||
|         const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw`; |         const htmlUrl = `${DWENGO_API_BASE}/learningObject/getRaw`; | ||||||
|         const html = await fetchWithLogging<string>(htmlUrl, `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, { |         const html = await fetchWithLogging<string>(htmlUrl, `Metadata for Learning Object HRUID "${id.hruid}" (language ${id.language})`, { | ||||||
|             params: id, |             params: { ...id }, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         if (!html) { |         if (!html) { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; | import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| export interface LearningObjectProvider { | export interface LearningObjectProvider { | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '../../interfaces/learning-content.js'; |  | ||||||
| import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider.js'; | import dwengoApiLearningObjectProvider from './dwengo-api-learning-object-provider.js'; | ||||||
| import { LearningObjectProvider } from './learning-object-provider.js'; | import { LearningObjectProvider } from './learning-object-provider.js'; | ||||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||||
| import databaseLearningObjectProvider from './database-learning-object-provider.js'; | import databaseLearningObjectProvider from './database-learning-object-provider.js'; | ||||||
|  | import { FilteredLearningObject, LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| function getProvider(id: LearningObjectIdentifier): LearningObjectProvider { | function getProvider(id: LearningObjectIdentifier): LearningObjectProvider { | ||||||
|     if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { |     if (id.hruid.startsWith(getEnvVar(envVars.UserContentPrefix))) { | ||||||
|  |  | ||||||
|  | @ -8,13 +8,12 @@ import InlineImageProcessor from '../image/inline-image-processor.js'; | ||||||
| import * as marked from 'marked'; | import * as marked from 'marked'; | ||||||
| import { getUrlStringForLearningObjectHTML, isValidHttpUrl } from '../../../../util/links.js'; | import { getUrlStringForLearningObjectHTML, isValidHttpUrl } from '../../../../util/links.js'; | ||||||
| import { ProcessingError } from '../processing-error.js'; | import { ProcessingError } from '../processing-error.js'; | ||||||
| import { LearningObjectIdentifier } from '../../../../interfaces/learning-content.js'; |  | ||||||
| import { Language } from '../../../../entities/content/language.js'; |  | ||||||
| 
 |  | ||||||
| import Image = marked.Tokens.Image; | import Image = marked.Tokens.Image; | ||||||
| import Heading = marked.Tokens.Heading; | import Heading = marked.Tokens.Heading; | ||||||
| import Link = marked.Tokens.Link; | import Link = marked.Tokens.Link; | ||||||
| import RendererObject = marked.RendererObject; | import RendererObject = marked.RendererObject; | ||||||
|  | import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| const prefixes = { | const prefixes = { | ||||||
|     learningObject: '@learning-object', |     learningObject: '@learning-object', | ||||||
|  |  | ||||||
|  | @ -13,9 +13,9 @@ import GiftProcessor from './gift/gift-processor.js'; | ||||||
| import { LearningObject } from '../../../entities/content/learning-object.entity.js'; | import { LearningObject } from '../../../entities/content/learning-object.entity.js'; | ||||||
| import Processor from './processor.js'; | import Processor from './processor.js'; | ||||||
| import { DwengoContentType } from './content-type.js'; | import { DwengoContentType } from './content-type.js'; | ||||||
| import { LearningObjectIdentifier } from '../../../interfaces/learning-content.js'; |  | ||||||
| import { Language } from '../../../entities/content/language.js'; |  | ||||||
| import { replaceAsync } from '../../../util/async.js'; | import { replaceAsync } from '../../../util/async.js'; | ||||||
|  | import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = /<learning-object hruid="([^"]+)" language="([^"]+)" version="([^"]+)"\/>/g; | const EMBEDDED_LEARNING_OBJECT_PLACEHOLDER = /<learning-object hruid="([^"]+)" language="([^"]+)" version="([^"]+)"\/>/g; | ||||||
| const LEARNING_OBJECT_DOES_NOT_EXIST = "<div class='non-existing-learning-object' />"; | const LEARNING_OBJECT_DOES_NOT_EXIST = "<div class='non-existing-learning-object' />"; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,18 @@ | ||||||
| import { LearningPathProvider } from './learning-path-provider.js'; | import { LearningPathProvider } from './learning-path-provider.js'; | ||||||
| import { FilteredLearningObject, LearningObjectNode, LearningPath, LearningPathResponse, Transition } from '../../interfaces/learning-content.js'; |  | ||||||
| import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js'; | import { LearningPath as LearningPathEntity } from '../../entities/content/learning-path.entity.js'; | ||||||
| import { getLearningPathRepository } from '../../data/repositories.js'; | import { getLearningPathRepository } from '../../data/repositories.js'; | ||||||
| import { Language } from '../../entities/content/language.js'; |  | ||||||
| import learningObjectService from '../learning-objects/learning-object-service.js'; | import learningObjectService from '../learning-objects/learning-object-service.js'; | ||||||
| import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; | import { LearningPathNode } from '../../entities/content/learning-path-node.entity.js'; | ||||||
| import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; | import { LearningPathTransition } from '../../entities/content/learning-path-transition.entity.js'; | ||||||
| import { getLastSubmissionForCustomizationTarget, isTransitionPossible, PersonalizationTarget } from './learning-path-personalization-util.js'; | import { getLastSubmissionForCustomizationTarget, isTransitionPossible, PersonalizationTarget } from './learning-path-personalization-util.js'; | ||||||
|  | import { | ||||||
|  |     FilteredLearningObject, | ||||||
|  |     LearningObjectNode, | ||||||
|  |     LearningPath, | ||||||
|  |     LearningPathResponse, | ||||||
|  |     Transition, | ||||||
|  | } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its |  * Fetches the corresponding learning object for each of the nodes and creates a map that maps each node to its | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { fetchWithLogging } from '../../util/api-helper.js'; | import { fetchWithLogging } from '../../util/api-helper.js'; | ||||||
| import { DWENGO_API_BASE } from '../../config.js'; | import { DWENGO_API_BASE } from '../../config.js'; | ||||||
| import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; |  | ||||||
| import { LearningPathProvider } from './learning-path-provider.js'; | import { LearningPathProvider } from './learning-path-provider.js'; | ||||||
| import { getLogger, Logger } from '../../logging/initalize.js'; | import { getLogger, Logger } from '../../logging/initalize.js'; | ||||||
|  | import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; | import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| import { Language } from '../../entities/content/language.js'; |  | ||||||
| import { PersonalizationTarget } from './learning-path-personalization-util.js'; | import { PersonalizationTarget } from './learning-path-personalization-util.js'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Generic interface for a service which provides access to learning paths from a data source. |  * Generic interface for a service which provides access to learning paths from a data source. | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { LearningPath, LearningPathResponse } from '../../interfaces/learning-content.js'; |  | ||||||
| import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | import dwengoApiLearningPathProvider from './dwengo-api-learning-path-provider.js'; | ||||||
| import databaseLearningPathProvider from './database-learning-path-provider.js'; | import databaseLearningPathProvider from './database-learning-path-provider.js'; | ||||||
| import { envVars, getEnvVar } from '../../util/envVars.js'; | import { envVars, getEnvVar } from '../../util/envVars.js'; | ||||||
| import { Language } from '../../entities/content/language.js'; |  | ||||||
| import { PersonalizationTarget } from './learning-path-personalization-util.js'; | import { PersonalizationTarget } from './learning-path-personalization-util.js'; | ||||||
|  | import { LearningPath, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| const userContentPrefix = getEnvVar(envVars.UserContentPrefix); | const userContentPrefix = getEnvVar(envVars.UserContentPrefix); | ||||||
| const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; | const allProviders = [dwengoApiLearningPathProvider, databaseLearningPathProvider]; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| import { getAnswerRepository, getQuestionRepository } from '../data/repositories.js'; | import { getAnswerRepository, getQuestionRepository } from '../data/repositories.js'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js'; | ||||||
| import { Question } from '../entities/questions/question.entity.js'; | import { Question } from '../entities/questions/question.entity.js'; | ||||||
| import { Answer } from '../entities/questions/answer.entity.js'; | import { Answer } from '../entities/questions/answer.entity.js'; | ||||||
| import { AnswerDTO, AnswerId, mapToAnswerDTO, mapToAnswerId } from '../interfaces/answer.js'; | import { mapToAnswerDTO, mapToAnswerDTOId } from '../interfaces/answer.js'; | ||||||
| import { QuestionRepository } from '../data/questions/question-repository.js'; | import { QuestionRepository } from '../data/questions/question-repository.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { mapToStudent } from '../interfaces/student.js'; | import { mapToStudent } from '../interfaces/student.js'; | ||||||
|  | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
|  | import { AnswerDTO, AnswerId } from '@dwengo-1/common/interfaces/answer'; | ||||||
| 
 | 
 | ||||||
| export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | export async function getAllQuestions(id: LearningObjectIdentifier, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|     const questionRepository: QuestionRepository = getQuestionRepository(); |     const questionRepository: QuestionRepository = getQuestionRepository(); | ||||||
|  | @ -15,13 +17,11 @@ export async function getAllQuestions(id: LearningObjectIdentifier, full: boolea | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const questionsDTO: QuestionDTO[] = questions.map(mapToQuestionDTO); |  | ||||||
| 
 |  | ||||||
|     if (full) { |     if (full) { | ||||||
|         return questionsDTO; |         return questions.map(mapToQuestionDTO); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return questionsDTO.map(mapToQuestionId); |     return questions.map(mapToQuestionDTOId); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function fetchQuestion(questionId: QuestionId): Promise<Question | null> { | async function fetchQuestion(questionId: QuestionId): Promise<Question | null> { | ||||||
|  | @ -59,13 +59,11 @@ export async function getAnswersByQuestion(questionId: QuestionId, full: boolean | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const answersDTO = answers.map(mapToAnswerDTO); |  | ||||||
| 
 |  | ||||||
|     if (full) { |     if (full) { | ||||||
|         return answersDTO; |         return answers.map(mapToAnswerDTO); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return answersDTO.map(mapToAnswerId); |     return answers.map(mapToAnswerDTOId); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createQuestion(questionDTO: QuestionDTO): Promise<QuestionDTO | null> { | export async function createQuestion(questionDTO: QuestionDTO): Promise<QuestionDTO | null> { | ||||||
|  | @ -73,9 +71,14 @@ export async function createQuestion(questionDTO: QuestionDTO): Promise<Question | ||||||
| 
 | 
 | ||||||
|     const author = mapToStudent(questionDTO.author); |     const author = mapToStudent(questionDTO.author); | ||||||
| 
 | 
 | ||||||
|  |     const loId: LearningObjectIdentifier = { | ||||||
|  |         ...questionDTO.learningObjectIdentifier, | ||||||
|  |         version: questionDTO.learningObjectIdentifier.version ?? 1, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     try { |     try { | ||||||
|         await questionRepository.createQuestion({ |         await questionRepository.createQuestion({ | ||||||
|             loId: questionDTO.learningObjectIdentifier, |             loId, | ||||||
|             author, |             author, | ||||||
|             content: questionDTO.content, |             content: questionDTO.content, | ||||||
|         }); |         }); | ||||||
|  | @ -95,8 +98,13 @@ export async function deleteQuestion(questionId: QuestionId): Promise<QuestionDT | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const loId: LearningObjectIdentifier = { | ||||||
|  |         ...questionId.learningObjectIdentifier, | ||||||
|  |         version: questionId.learningObjectIdentifier.version ?? 1, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     try { |     try { | ||||||
|         await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(questionId.learningObjectIdentifier, questionId.sequenceNumber); |         await questionRepository.removeQuestionByLearningObjectAndSequenceNumber(loId, questionId.sequenceNumber); | ||||||
|     } catch (_) { |     } catch (_) { | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,63 +1,75 @@ | ||||||
| import { getClassRepository, getGroupRepository, getStudentRepository, getSubmissionRepository } from '../data/repositories.js'; | import { | ||||||
| import { AssignmentDTO } from '../interfaces/assignment.js'; |     getClassJoinRequestRepository, | ||||||
| import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; |     getClassRepository, | ||||||
| import { GroupDTO, mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; |     getGroupRepository, | ||||||
| import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student.js'; |     getQuestionRepository, | ||||||
| import { mapToSubmissionDTO, mapToSubmissionDTOId, SubmissionDTO, SubmissionDTOId } from '../interfaces/submission.js'; |     getStudentRepository, | ||||||
|  |     getSubmissionRepository, | ||||||
|  | } from '../data/repositories.js'; | ||||||
|  | import { mapToClassDTO } from '../interfaces/class.js'; | ||||||
|  | import { mapToGroupDTO, mapToGroupDTOId } from '../interfaces/group.js'; | ||||||
|  | import { mapToStudent, mapToStudentDTO } from '../interfaces/student.js'; | ||||||
|  | import { mapToSubmissionDTO, mapToSubmissionDTOId } from '../interfaces/submission.js'; | ||||||
| import { getAllAssignments } from './assignments.js'; | import { getAllAssignments } from './assignments.js'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js'; | ||||||
|  | import { mapToStudentRequest, mapToStudentRequestDTO } from '../interfaces/student-request.js'; | ||||||
|  | import { Student } from '../entities/users/student.entity.js'; | ||||||
|  | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
|  | import { fetchClass } from './classes.js'; | ||||||
|  | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
|  | import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
|  | import { AssignmentDTO } from '@dwengo-1/common/interfaces/assignment'; | ||||||
|  | import { GroupDTO } from '@dwengo-1/common/interfaces/group'; | ||||||
|  | import { SubmissionDTO, SubmissionDTOId } from '@dwengo-1/common/interfaces/submission'; | ||||||
|  | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
|  | import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | export async function getAllStudents(full: boolean): Promise<StudentDTO[] | string[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|     const students = await studentRepository.findAll(); |     const users = await studentRepository.findAll(); | ||||||
| 
 | 
 | ||||||
|     if (full) { |     if (full) { | ||||||
|         return students.map(mapToStudentDTO); |         return users.map(mapToStudentDTO); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return students.map((student) => student.username); |     return users.map((user) => user.username); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudent(username: string): Promise<StudentDTO | null> { | export async function fetchStudent(username: string): Promise<Student> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
|     const user = await studentRepository.findByUsername(username); |     const user = await studentRepository.findByUsername(username); | ||||||
|     return user ? mapToStudentDTO(user) : null; | 
 | ||||||
|  |     if (!user) { | ||||||
|  |         throw new NotFoundException('Student with username not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return user; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createStudent(userData: StudentDTO): Promise<StudentDTO | null> { | export async function getStudent(username: string): Promise<StudentDTO> { | ||||||
|  |     const user = await fetchStudent(username); | ||||||
|  |     return mapToStudentDTO(user); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createStudent(userData: StudentDTO): Promise<StudentDTO> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
| 
 | 
 | ||||||
|     const newStudent = mapToStudent(userData); |     const newStudent = mapToStudent(userData); | ||||||
|     await studentRepository.save(newStudent, { preventOverwrite: true }); |     await studentRepository.save(newStudent, { preventOverwrite: true }); | ||||||
|     return mapToStudentDTO(newStudent); |     return userData; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteStudent(username: string): Promise<StudentDTO | null> { | export async function deleteStudent(username: string): Promise<StudentDTO> { | ||||||
|     const studentRepository = getStudentRepository(); |     const studentRepository = getStudentRepository(); | ||||||
| 
 | 
 | ||||||
|     const user = await studentRepository.findByUsername(username); |     const student = await fetchStudent(username); // Throws error if it does not exist
 | ||||||
| 
 | 
 | ||||||
|     if (!user) { |     await studentRepository.deleteByUsername(username); | ||||||
|         return null; |     return mapToStudentDTO(student); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|         await studentRepository.deleteByUsername(username); |  | ||||||
| 
 |  | ||||||
|         return mapToStudentDTO(user); |  | ||||||
|     } catch (e) { |  | ||||||
|         getLogger().error(e); |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentClasses(username: string, full: boolean): Promise<ClassDTO[] | string[]> { | export async function getStudentClasses(username: string, full: boolean): Promise<ClassDTO[] | string[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.findByStudent(student); |     const classes = await classRepository.findByStudent(student); | ||||||
|  | @ -70,12 +82,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> { | export async function getStudentAssignments(username: string, full: boolean): Promise<AssignmentDTO[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.findByStudent(student); |     const classes = await classRepository.findByStudent(student); | ||||||
|  | @ -84,12 +91,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> { | export async function getStudentGroups(username: string, full: boolean): Promise<GroupDTO[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const groupRepository = getGroupRepository(); |     const groupRepository = getGroupRepository(); | ||||||
|     const groups = await groupRepository.findAllGroupsWithStudent(student); |     const groups = await groupRepository.findAllGroupsWithStudent(student); | ||||||
|  | @ -102,12 +104,7 @@ export async function getStudentGroups(username: string, full: boolean): Promise | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getStudentSubmissions(username: string, full: boolean): Promise<SubmissionDTO[] | SubmissionDTOId[]> { | export async function getStudentSubmissions(username: string, full: boolean): Promise<SubmissionDTO[] | SubmissionDTOId[]> { | ||||||
|     const studentRepository = getStudentRepository(); |     const student = await fetchStudent(username); | ||||||
|     const student = await studentRepository.findByUsername(username); |  | ||||||
| 
 |  | ||||||
|     if (!student) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
|     const submissions = await submissionRepository.findAllSubmissionsForStudent(student); |     const submissions = await submissionRepository.findAllSubmissionsForStudent(student); | ||||||
|  | @ -118,3 +115,66 @@ export async function getStudentSubmissions(username: string, full: boolean): Pr | ||||||
| 
 | 
 | ||||||
|     return submissions.map(mapToSubmissionDTOId); |     return submissions.map(mapToSubmissionDTOId); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|  |     const student = await fetchStudent(username); | ||||||
|  | 
 | ||||||
|  |     const questionRepository = getQuestionRepository(); | ||||||
|  |     const questions = await questionRepository.findAllByAuthor(student); | ||||||
|  | 
 | ||||||
|  |     if (full) { | ||||||
|  |         return questions.map(mapToQuestionDTO); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return questions.map(mapToQuestionDTOId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createClassJoinRequest(username: string, classId: string): Promise<ClassJoinRequestDTO> { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(username); // Throws error if student not found
 | ||||||
|  |     const cls = await fetchClass(classId); | ||||||
|  | 
 | ||||||
|  |     const request = mapToStudentRequest(student, cls); | ||||||
|  |     await requestRepo.save(request, { preventOverwrite: true }); | ||||||
|  |     return mapToStudentRequestDTO(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getJoinRequestsByStudent(username: string): Promise<ClassJoinRequestDTO[]> { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(username); | ||||||
|  | 
 | ||||||
|  |     const requests = await requestRepo.findAllRequestsBy(student); | ||||||
|  |     return requests.map(mapToStudentRequestDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getJoinRequestByStudentClass(username: string, classId: string): Promise<ClassJoinRequestDTO> { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(username); | ||||||
|  |     const cls = await fetchClass(classId); | ||||||
|  | 
 | ||||||
|  |     const request = await requestRepo.findByStudentAndClass(student, cls); | ||||||
|  |     if (!request) { | ||||||
|  |         throw new NotFoundException('Join request not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return mapToStudentRequestDTO(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteClassJoinRequest(username: string, classId: string): Promise<ClassJoinRequestDTO> { | ||||||
|  |     const requestRepo = getClassJoinRequestRepository(); | ||||||
|  | 
 | ||||||
|  |     const student = await fetchStudent(username); | ||||||
|  |     const cls = await fetchClass(classId); | ||||||
|  | 
 | ||||||
|  |     const request = await requestRepo.findByStudentAndClass(student, cls); | ||||||
|  | 
 | ||||||
|  |     if (!request) { | ||||||
|  |         throw new NotFoundException('Join request not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await requestRepo.deleteBy(student, cls); | ||||||
|  |     return mapToStudentRequestDTO(request); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import { getSubmissionRepository } from '../data/repositories.js'; | import { getSubmissionRepository } from '../data/repositories.js'; | ||||||
| import { Language } from '../entities/content/language.js'; |  | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { mapToSubmission, mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | ||||||
|  | import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| export async function getSubmission( | export async function getSubmission( | ||||||
|     learningObjectHruid: string, |     learningObjectHruid: string, | ||||||
|  |  | ||||||
|  | @ -1,134 +1,165 @@ | ||||||
| import { getClassRepository, getLearningObjectRepository, getQuestionRepository, getTeacherRepository } from '../data/repositories.js'; | import { | ||||||
| import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; |     getClassJoinRequestRepository, | ||||||
|  |     getClassRepository, | ||||||
|  |     getLearningObjectRepository, | ||||||
|  |     getQuestionRepository, | ||||||
|  |     getTeacherRepository, | ||||||
|  | } from '../data/repositories.js'; | ||||||
|  | import { mapToClassDTO } from '../interfaces/class.js'; | ||||||
|  | import { mapToQuestionDTO, mapToQuestionDTOId } from '../interfaces/question.js'; | ||||||
|  | import { mapToTeacher, mapToTeacherDTO } from '../interfaces/teacher.js'; | ||||||
|  | import { Teacher } from '../entities/users/teacher.entity.js'; | ||||||
|  | import { fetchStudent } from './students.js'; | ||||||
|  | import { ClassJoinRequest } from '../entities/classes/class-join-request.entity.js'; | ||||||
|  | import { mapToStudentRequestDTO } from '../interfaces/student-request.js'; | ||||||
|  | import { TeacherRepository } from '../data/users/teacher-repository.js'; | ||||||
|  | import { ClassRepository } from '../data/classes/class-repository.js'; | ||||||
|  | import { Class } from '../entities/classes/class.entity.js'; | ||||||
|  | import { LearningObjectRepository } from '../data/content/learning-object-repository.js'; | ||||||
|  | import { LearningObject } from '../entities/content/learning-object.entity.js'; | ||||||
|  | import { QuestionRepository } from '../data/questions/question-repository.js'; | ||||||
|  | import { Question } from '../entities/questions/question.entity.js'; | ||||||
|  | import { ClassJoinRequestRepository } from '../data/classes/class-join-request-repository.js'; | ||||||
|  | import { Student } from '../entities/users/student.entity.js'; | ||||||
|  | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
| import { getClassStudents } from './classes.js'; | import { getClassStudents } from './classes.js'; | ||||||
| import { StudentDTO } from '../interfaces/student.js'; | import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||||
| import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question.js'; | import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
| import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
| import { getLogger } from '../logging/initalize.js'; | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
|  | import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | ||||||
|  | import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||||
|     const teachers = await teacherRepository.findAll(); |     const users: Teacher[] = await teacherRepository.findAll(); | ||||||
| 
 | 
 | ||||||
|     if (full) { |     if (full) { | ||||||
|         return teachers.map(mapToTeacherDTO); |         return users.map(mapToTeacherDTO); | ||||||
|  |     } | ||||||
|  |     return users.map((user) => user.username); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function fetchTeacher(username: string): Promise<Teacher> { | ||||||
|  |     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||||
|  |     const user: Teacher | null = await teacherRepository.findByUsername(username); | ||||||
|  | 
 | ||||||
|  |     if (!user) { | ||||||
|  |         throw new NotFoundException('Teacher with username not found'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return teachers.map((teacher) => teacher.username); |     return user; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getTeacher(username: string): Promise<TeacherDTO | null> { | export async function getTeacher(username: string): Promise<TeacherDTO> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const user: Teacher = await fetchTeacher(username); | ||||||
|     const user = await teacherRepository.findByUsername(username); |     return mapToTeacherDTO(user); | ||||||
|     return user ? mapToTeacherDTO(user) : null; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO | null> { | export async function createTeacher(userData: TeacherDTO): Promise<TeacherDTO> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||||
| 
 | 
 | ||||||
|     const newTeacher = mapToTeacher(userData); |     const newTeacher = mapToTeacher(userData); | ||||||
|     await teacherRepository.save(newTeacher, { preventOverwrite: true }); |     await teacherRepository.save(newTeacher, { preventOverwrite: true }); | ||||||
| 
 |  | ||||||
|     return mapToTeacherDTO(newTeacher); |     return mapToTeacherDTO(newTeacher); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function deleteTeacher(username: string): Promise<TeacherDTO | null> { | export async function deleteTeacher(username: string): Promise<TeacherDTO> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository: TeacherRepository = getTeacherRepository(); | ||||||
| 
 | 
 | ||||||
|     const user = await teacherRepository.findByUsername(username); |     const teacher = await fetchTeacher(username); // Throws error if it does not exist
 | ||||||
| 
 | 
 | ||||||
|     if (!user) { |     await teacherRepository.deleteByUsername(username); | ||||||
|         return null; |     return mapToTeacherDTO(teacher); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|         await teacherRepository.deleteByUsername(username); |  | ||||||
| 
 |  | ||||||
|         return mapToTeacherDTO(user); |  | ||||||
|     } catch (e) { |  | ||||||
|         getLogger().error(e); |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchClassesByTeacher(username: string): Promise<ClassDTO[] | null> { | async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacher: Teacher = await fetchTeacher(username); | ||||||
|     const teacher = await teacherRepository.findByUsername(username); |  | ||||||
|     if (!teacher) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const classRepository = getClassRepository(); |     const classRepository: ClassRepository = getClassRepository(); | ||||||
|     const classes = await classRepository.findByTeacher(teacher); |     const classes: Class[] = await classRepository.findByTeacher(teacher); | ||||||
|     return classes.map(mapToClassDTO); |     return classes.map(mapToClassDTO); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getClassesByTeacher(username: string, full: boolean): Promise<ClassDTO[] | string[] | null> { | export async function getClassesByTeacher(username: string, full: boolean): Promise<ClassDTO[] | string[]> { | ||||||
|     const classes = await fetchClassesByTeacher(username); |     const classes: ClassDTO[] = await fetchClassesByTeacher(username); | ||||||
| 
 |  | ||||||
|     if (!classes) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (full) { |     if (full) { | ||||||
|         return classes; |         return classes; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     return classes.map((cls) => cls.id); |     return classes.map((cls) => cls.id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchStudentsByTeacher(username: string): Promise<StudentDTO[] | null> { | export async function getStudentsByTeacher(username: string, full: boolean): Promise<StudentDTO[] | string[]> { | ||||||
|     const classes = (await getClassesByTeacher(username, false)) as string[]; |     const classes: ClassDTO[] = await fetchClassesByTeacher(username); | ||||||
| 
 | 
 | ||||||
|     if (!classes) { |     if (!classes || classes.length === 0) { | ||||||
|         return null; |         return []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); |     const classIds: string[] = classes.map((cls) => cls.id); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function getStudentsByTeacher(username: string, full: boolean): Promise<StudentDTO[] | string[] | null> { |  | ||||||
|     const students = await fetchStudentsByTeacher(username); |  | ||||||
| 
 |  | ||||||
|     if (!students) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|  |     const students: StudentDTO[] = (await Promise.all(classIds.map(async (id) => getClassStudents(id)))).flat(); | ||||||
|     if (full) { |     if (full) { | ||||||
|         return students; |         return students; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     return students.map((student) => student.username); |     return students.map((student) => student.username); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchTeacherQuestions(username: string): Promise<QuestionDTO[] | null> { | export async function getTeacherQuestions(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[]> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacher: Teacher = await fetchTeacher(username); | ||||||
|     const teacher = await teacherRepository.findByUsername(username); |  | ||||||
|     if (!teacher) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     // Find all learning objects that this teacher manages
 |     // Find all learning objects that this teacher manages
 | ||||||
|     const learningObjectRepository = getLearningObjectRepository(); |     const learningObjectRepository: LearningObjectRepository = getLearningObjectRepository(); | ||||||
|     const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); |     const learningObjects: LearningObject[] = await learningObjectRepository.findAllByTeacher(teacher); | ||||||
|  | 
 | ||||||
|  |     if (!learningObjects || learningObjects.length === 0) { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Fetch all questions related to these learning objects
 |     // Fetch all questions related to these learning objects
 | ||||||
|     const questionRepository = getQuestionRepository(); |     const questionRepository: QuestionRepository = getQuestionRepository(); | ||||||
|     const questions = await questionRepository.findAllByLearningObjects(learningObjects); |     const questions: Question[] = await questionRepository.findAllByLearningObjects(learningObjects); | ||||||
| 
 |  | ||||||
|     return questions.map(mapToQuestionDTO); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function getQuestionsByTeacher(username: string, full: boolean): Promise<QuestionDTO[] | QuestionId[] | null> { |  | ||||||
|     const questions = await fetchTeacherQuestions(username); |  | ||||||
| 
 |  | ||||||
|     if (!questions) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (full) { |     if (full) { | ||||||
|         return questions; |         return questions.map(mapToQuestionDTO); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return questions.map(mapToQuestionId); |     return questions.map(mapToQuestionDTOId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getJoinRequestsByClass(classId: string): Promise<ClassJoinRequestDTO[]> { | ||||||
|  |     const classRepository: ClassRepository = getClassRepository(); | ||||||
|  |     const cls: Class | null = await classRepository.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls) { | ||||||
|  |         throw new NotFoundException('Class with id not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository(); | ||||||
|  |     const requests: ClassJoinRequest[] = await requestRepo.findAllOpenRequestsTo(cls); | ||||||
|  |     return requests.map(mapToStudentRequestDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateClassJoinRequestStatus(studentUsername: string, classId: string, accepted = true): Promise<ClassJoinRequestDTO> { | ||||||
|  |     const requestRepo: ClassJoinRequestRepository = getClassJoinRequestRepository(); | ||||||
|  |     const classRepo: ClassRepository = getClassRepository(); | ||||||
|  | 
 | ||||||
|  |     const student: Student = await fetchStudent(studentUsername); | ||||||
|  |     const cls: Class | null = await classRepo.findById(classId); | ||||||
|  | 
 | ||||||
|  |     if (!cls) { | ||||||
|  |         throw new NotFoundException('Class not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const request: ClassJoinRequest | null = await requestRepo.findByStudentAndClass(student, cls); | ||||||
|  | 
 | ||||||
|  |     if (!request) { | ||||||
|  |         throw new NotFoundException('Join request not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; | ||||||
|  | 
 | ||||||
|  |     await requestRepo.save(request); | ||||||
|  |     return mapToStudentRequestDTO(request); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import axios, { AxiosRequestConfig } from 'axios'; | import axios, { AxiosRequestConfig } from 'axios'; | ||||||
| import { getLogger, Logger } from '../logging/initalize.js'; | import { getLogger, Logger } from '../logging/initalize.js'; | ||||||
| import { LearningObjectIdentifier } from '../interfaces/learning-content.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| 
 | 
 | ||||||
| const logger: Logger = getLogger(); | const logger: Logger = getLogger(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { LearningObjectIdentifier } from '../interfaces/learning-content'; | import { LearningObjectIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| export function isValidHttpUrl(url: string): boolean { | export function isValidHttpUrl(url: string): boolean { | ||||||
|     try { |     try { | ||||||
|  |  | ||||||
							
								
								
									
										232
									
								
								backend/tests/controllers/students.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								backend/tests/controllers/students.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,232 @@ | ||||||
|  | import { setupTestApp } from '../setup-tests.js'; | ||||||
|  | import { describe, it, expect, beforeAll, beforeEach, vi, Mock } from 'vitest'; | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import { | ||||||
|  |     getAllStudentsHandler, | ||||||
|  |     getStudentHandler, | ||||||
|  |     createStudentHandler, | ||||||
|  |     deleteStudentHandler, | ||||||
|  |     getStudentClassesHandler, | ||||||
|  |     getStudentGroupsHandler, | ||||||
|  |     getStudentSubmissionsHandler, | ||||||
|  |     getStudentQuestionsHandler, | ||||||
|  |     createStudentRequestHandler, | ||||||
|  |     getStudentRequestsHandler, | ||||||
|  |     deleteClassJoinRequestHandler, | ||||||
|  |     getStudentRequestHandler, | ||||||
|  | } from '../../src/controllers/students.js'; | ||||||
|  | import { TEST_STUDENTS } from '../test_assets/users/students.testdata.js'; | ||||||
|  | import { NotFoundException } from '../../src/exceptions/not-found-exception.js'; | ||||||
|  | import { BadRequestException } from '../../src/exceptions/bad-request-exception.js'; | ||||||
|  | import { ConflictException } from '../../src/exceptions/conflict-exception.js'; | ||||||
|  | import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; | ||||||
|  | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
|  | 
 | ||||||
|  | describe('Student controllers', () => { | ||||||
|  |     let req: Partial<Request>; | ||||||
|  |     let res: Partial<Response>; | ||||||
|  | 
 | ||||||
|  |     let jsonMock: Mock; | ||||||
|  | 
 | ||||||
|  |     beforeAll(async () => { | ||||||
|  |         await setupTestApp(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     beforeEach(() => { | ||||||
|  |         jsonMock = vi.fn(); | ||||||
|  |         res = { | ||||||
|  |             json: jsonMock, | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get student', async () => { | ||||||
|  |         req = { params: { username: 'DireStraits' } }; | ||||||
|  | 
 | ||||||
|  |         await getStudentHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ student: expect.anything() })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student not found', async () => { | ||||||
|  |         req = { params: { username: 'doesnotexist' } }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => getStudentHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('No username', async () => { | ||||||
|  |         req = { params: {} }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => getStudentHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create and delete student', async () => { | ||||||
|  |         const student = { | ||||||
|  |             id: 'coolstudent', | ||||||
|  |             username: 'coolstudent', | ||||||
|  |             firstName: 'New', | ||||||
|  |             lastName: 'Student', | ||||||
|  |         } as StudentDTO; | ||||||
|  |         req = { | ||||||
|  |             body: student, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await createStudentHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ student: expect.objectContaining(student) })); | ||||||
|  | 
 | ||||||
|  |         req = { params: { username: 'coolstudent' } }; | ||||||
|  | 
 | ||||||
|  |         await deleteStudentHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ student: expect.objectContaining(student) })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create duplicate student', async () => { | ||||||
|  |         req = { | ||||||
|  |             body: { | ||||||
|  |                 username: 'DireStraits', | ||||||
|  |                 firstName: 'dupe', | ||||||
|  |                 lastName: 'dupe', | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => createStudentHandler(req as Request, res as Response)).rejects.toThrowError(EntityAlreadyExistsException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create student no body', async () => { | ||||||
|  |         req = { body: {} }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => createStudentHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student list', async () => { | ||||||
|  |         req = { query: { full: 'true' } }; | ||||||
|  | 
 | ||||||
|  |         await getAllStudentsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ students: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  | 
 | ||||||
|  |         // Check is DireStraits is part of the student list
 | ||||||
|  |         const studentUsernames = result.students.map((s: StudentDTO) => s.username); | ||||||
|  |         expect(studentUsernames).toContain('DireStraits'); | ||||||
|  | 
 | ||||||
|  |         // Check length, +1 because of create
 | ||||||
|  |         expect(result.students).toHaveLength(TEST_STUDENTS.length); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student classes', async () => { | ||||||
|  |         req = { params: { username: 'DireStraits' }, query: {} }; | ||||||
|  | 
 | ||||||
|  |         await getStudentClassesHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ classes: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.classes).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student groups', async () => { | ||||||
|  |         req = { params: { username: 'DireStraits' }, query: {} }; | ||||||
|  | 
 | ||||||
|  |         await getStudentGroupsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ groups: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.groups).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student submissions', async () => { | ||||||
|  |         req = { params: { username: 'DireStraits' }, query: { full: 'true' } }; | ||||||
|  | 
 | ||||||
|  |         await getStudentSubmissionsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ submissions: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.submissions).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Student questions', async () => { | ||||||
|  |         req = { params: { username: 'DireStraits' }, query: { full: 'true' } }; | ||||||
|  | 
 | ||||||
|  |         await getStudentQuestionsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ questions: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.questions).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Deleting non-existent student', async () => { | ||||||
|  |         req = { params: { username: 'doesnotexist' } }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => deleteStudentHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get join requests by student', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'PinkFloyd' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getStudentRequestsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith( | ||||||
|  |             expect.objectContaining({ | ||||||
|  |                 requests: expect.anything(), | ||||||
|  |             }) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // Console.log('[JOIN REQUESTS]', result.requests);
 | ||||||
|  |         expect(result.requests.length).toBeGreaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get join request by student and class', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'PinkFloyd', classId: 'id02' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getStudentRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith( | ||||||
|  |             expect.objectContaining({ | ||||||
|  |                 request: expect.anything(), | ||||||
|  |             }) | ||||||
|  |         ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create join request', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'Noordkaap' }, | ||||||
|  |             body: { classId: 'id02' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await createStudentRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create join request duplicate', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'Tool' }, | ||||||
|  |             body: { classId: 'id02' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => createStudentRequestHandler(req as Request, res as Response)).rejects.toThrow(ConflictException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Delete join request', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'Noordkaap', classId: 'id02' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await deleteClassJoinRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         await expect(async () => deleteClassJoinRequestHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
							
								
								
									
										204
									
								
								backend/tests/controllers/teachers.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								backend/tests/controllers/teachers.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,204 @@ | ||||||
|  | import { beforeAll, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import { setupTestApp } from '../setup-tests.js'; | ||||||
|  | import { NotFoundException } from '../../src/exceptions/not-found-exception.js'; | ||||||
|  | import { | ||||||
|  |     createTeacherHandler, | ||||||
|  |     deleteTeacherHandler, | ||||||
|  |     getAllTeachersHandler, | ||||||
|  |     getStudentJoinRequestHandler, | ||||||
|  |     getTeacherClassHandler, | ||||||
|  |     getTeacherHandler, | ||||||
|  |     getTeacherStudentHandler, | ||||||
|  |     updateStudentJoinRequestHandler, | ||||||
|  | } from '../../src/controllers/teachers.js'; | ||||||
|  | import { BadRequestException } from '../../src/exceptions/bad-request-exception.js'; | ||||||
|  | import { EntityAlreadyExistsException } from '../../src/exceptions/entity-already-exists-exception.js'; | ||||||
|  | import { getStudentRequestsHandler } from '../../src/controllers/students.js'; | ||||||
|  | import { TeacherDTO } from '@dwengo-1/common/interfaces/teacher'; | ||||||
|  | 
 | ||||||
|  | describe('Teacher controllers', () => { | ||||||
|  |     let req: Partial<Request>; | ||||||
|  |     let res: Partial<Response>; | ||||||
|  | 
 | ||||||
|  |     let jsonMock: Mock; | ||||||
|  | 
 | ||||||
|  |     beforeAll(async () => { | ||||||
|  |         await setupTestApp(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     beforeEach(() => { | ||||||
|  |         jsonMock = vi.fn(); | ||||||
|  |         res = { | ||||||
|  |             json: jsonMock, | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get teacher', async () => { | ||||||
|  |         req = { params: { username: 'FooFighters' } }; | ||||||
|  | 
 | ||||||
|  |         await getTeacherHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.anything() })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Teacher not found', async () => { | ||||||
|  |         req = { params: { username: 'doesnotexist' } }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => getTeacherHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('No username', async () => { | ||||||
|  |         req = { params: {} }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => getTeacherHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create and delete teacher', async () => { | ||||||
|  |         const teacher = { | ||||||
|  |             id: 'coolteacher', | ||||||
|  |             username: 'coolteacher', | ||||||
|  |             firstName: 'New', | ||||||
|  |             lastName: 'Teacher', | ||||||
|  |         }; | ||||||
|  |         req = { | ||||||
|  |             body: teacher, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await createTeacherHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.objectContaining(teacher) })); | ||||||
|  | 
 | ||||||
|  |         req = { params: { username: 'coolteacher' } }; | ||||||
|  | 
 | ||||||
|  |         await deleteTeacherHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.objectContaining(teacher) })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create duplicate student', async () => { | ||||||
|  |         req = { | ||||||
|  |             body: { | ||||||
|  |                 username: 'FooFighters', | ||||||
|  |                 firstName: 'Dave', | ||||||
|  |                 lastName: 'Grohl', | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => createTeacherHandler(req as Request, res as Response)).rejects.toThrowError(EntityAlreadyExistsException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create teacher no body', async () => { | ||||||
|  |         req = { body: {} }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => createTeacherHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Teacher list', async () => { | ||||||
|  |         req = { query: { full: 'true' } }; | ||||||
|  | 
 | ||||||
|  |         await getAllTeachersHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teachers: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  | 
 | ||||||
|  |         const teacherUsernames = result.teachers.map((s: TeacherDTO) => s.username); | ||||||
|  |         expect(teacherUsernames).toContain('FooFighters'); | ||||||
|  | 
 | ||||||
|  |         expect(result.teachers).toHaveLength(4); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Deleting non-existent student', async () => { | ||||||
|  |         req = { params: { username: 'doesnotexist' } }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => deleteTeacherHandler(req as Request, res as Response)).rejects.toThrow(NotFoundException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get teacher classes', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'FooFighters' }, | ||||||
|  |             query: { full: 'true' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getTeacherClassHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ classes: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // Console.log('[TEACHER CLASSES]', result);
 | ||||||
|  |         expect(result.classes.length).toBeGreaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get teacher students', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'FooFighters' }, | ||||||
|  |             query: { full: 'true' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getTeacherStudentHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ students: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // Console.log('[TEACHER STUDENTS]', result.students);
 | ||||||
|  |         expect(result.students.length).toBeGreaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     /* | ||||||
|  | 
 | ||||||
|  |     It('Get teacher questions', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'FooFighters' }, | ||||||
|  |             query: { full: 'true' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getTeacherQuestionHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ questions: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // console.log('[TEACHER QUESTIONS]', result.questions);
 | ||||||
|  |         expect(result.questions.length).toBeGreaterThan(0); | ||||||
|  | 
 | ||||||
|  |         // TODO fix
 | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  |     it('Get join requests by class', async () => { | ||||||
|  |         req = { | ||||||
|  |             query: { username: 'LimpBizkit' }, | ||||||
|  |             params: { classId: 'id02' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getStudentJoinRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ joinRequests: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // Console.log('[JOIN REQUESTS FOR CLASS]', result.joinRequests);
 | ||||||
|  |         expect(result.joinRequests.length).toBeGreaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Update join request status', async () => { | ||||||
|  |         req = { | ||||||
|  |             query: { username: 'LimpBizkit', studentUsername: 'PinkFloyd' }, | ||||||
|  |             params: { classId: 'id02' }, | ||||||
|  |             body: { accepted: 'true' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await updateStudentJoinRequestHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ request: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         req = { | ||||||
|  |             params: { username: 'PinkFloyd' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getStudentRequestsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         const status: boolean = jsonMock.mock.lastCall?.[0].requests[0].status; | ||||||
|  |         expect(status).toBeTruthy(); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | @ -9,7 +9,7 @@ import { | ||||||
|     getSubmissionRepository, |     getSubmissionRepository, | ||||||
| } from '../../../src/data/repositories'; | } from '../../../src/data/repositories'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { StudentRepository } from '../../../src/data/users/student-repository'; | import { StudentRepository } from '../../../src/data/users/student-repository'; | ||||||
| import { GroupRepository } from '../../../src/data/assignments/group-repository'; | import { GroupRepository } from '../../../src/data/assignments/group-repository'; | ||||||
| import { AssignmentRepository } from '../../../src/data/assignments/assignment-repository'; | import { AssignmentRepository } from '../../../src/data/assignments/assignment-repository'; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { getAttachmentRepository, getLearningObjectRepository } from '../../../s | ||||||
| import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | import { AttachmentRepository } from '../../../src/data/content/attachment-repository.js'; | ||||||
| import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; | import { LearningObjectRepository } from '../../../src/data/content/learning-object-repository.js'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier.js'; | ||||||
| import { Language } from '../../../src/entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| describe('AttachmentRepository', () => { | describe('AttachmentRepository', () => { | ||||||
|     let attachmentRepository: AttachmentRepository; |     let attachmentRepository: AttachmentRepository; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { LearningObjectRepository } from '../../../src/data/content/learning-obj | ||||||
| import { getLearningObjectRepository } from '../../../src/data/repositories'; | import { getLearningObjectRepository } from '../../../src/data/repositories'; | ||||||
| import { setupTestApp } from '../../setup-tests'; | import { setupTestApp } from '../../setup-tests'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| describe('LearningObjectRepository', () => { | describe('LearningObjectRepository', () => { | ||||||
|     let learningObjectRepository: LearningObjectRepository; |     let learningObjectRepository: LearningObjectRepository; | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import { LearningPathRepository } from '../../../src/data/content/learning-path- | ||||||
| import example from '../../test-assets/learning-paths/pn-werking-example.js'; | import example from '../../test-assets/learning-paths/pn-werking-example.js'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity.js'; | ||||||
| import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | import { expectToBeCorrectEntity } from '../../test-utils/expectations.js'; | ||||||
| import { Language } from '../../../src/entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| function expectToHaveFoundPrecisely(expected: LearningPath, result: LearningPath[]): void { | function expectToHaveFoundPrecisely(expected: LearningPath, result: LearningPath[]): void { | ||||||
|     expect(result).toHaveProperty('length'); |     expect(result).toHaveProperty('length'); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest'; | ||||||
| import { getLearningPathRepository } from '../../../src/data/repositories'; | import { getLearningPathRepository } from '../../../src/data/repositories'; | ||||||
| import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; | import { LearningPathRepository } from '../../../src/data/content/learning-path-repository'; | ||||||
| import { setupTestApp } from '../../setup-tests'; | import { setupTestApp } from '../../setup-tests'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| describe('LearningPathRepository', () => { | describe('LearningPathRepository', () => { | ||||||
|     let learningPathRepository: LearningPathRepository; |     let learningPathRepository: LearningPathRepository; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { AnswerRepository } from '../../../src/data/questions/answer-repository' | ||||||
| import { getAnswerRepository, getQuestionRepository, getTeacherRepository } from '../../../src/data/repositories'; | import { getAnswerRepository, getQuestionRepository, getTeacherRepository } from '../../../src/data/repositories'; | ||||||
| import { QuestionRepository } from '../../../src/data/questions/question-repository'; | import { QuestionRepository } from '../../../src/data/questions/question-repository'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { TeacherRepository } from '../../../src/data/users/teacher-repository'; | import { TeacherRepository } from '../../../src/data/users/teacher-repository'; | ||||||
| 
 | 
 | ||||||
| describe('AnswerRepository', () => { | describe('AnswerRepository', () => { | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { QuestionRepository } from '../../../src/data/questions/question-reposit | ||||||
| import { getQuestionRepository, getStudentRepository } from '../../../src/data/repositories'; | import { getQuestionRepository, getStudentRepository } from '../../../src/data/repositories'; | ||||||
| import { StudentRepository } from '../../../src/data/users/student-repository'; | import { StudentRepository } from '../../../src/data/users/student-repository'; | ||||||
| import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | import { LearningObjectIdentifier } from '../../../src/entities/content/learning-object-identifier'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| describe('QuestionRepository', () => { | describe('QuestionRepository', () => { | ||||||
|     let questionRepository: QuestionRepository; |     let questionRepository: QuestionRepository; | ||||||
|  |  | ||||||
|  | @ -5,11 +5,11 @@ import example from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-w | ||||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||||
| import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; | import databaseLearningObjectProvider from '../../../src/services/learning-objects/database-learning-object-provider'; | ||||||
| import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; | import { expectToBeCorrectFilteredLearningObject } from '../../test-utils/expectations'; | ||||||
| import { FilteredLearningObject } from '../../../src/interfaces/learning-content'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Language } from '../../../src/entities/content/language'; |  | ||||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
|  | import { FilteredLearningObject } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  | @ -37,7 +37,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
|         it('should return the learning object when it is queried by its id', async () => { |         it('should return the learning object when it is queried by its id', async () => { | ||||||
|             const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById(exampleLearningObject); |             const result: FilteredLearningObject | null = await databaseLearningObjectProvider.getLearningObjectById(exampleLearningObject); | ||||||
|             expect(result).toBeTruthy(); |             expect(result).toBeTruthy(); | ||||||
|             expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject); |             expectToBeCorrectFilteredLearningObject(result, exampleLearningObject); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         it('should return the learning object when it is queried by only hruid and language (but not version)', async () => { |         it('should return the learning object when it is queried by only hruid and language (but not version)', async () => { | ||||||
|  | @ -46,7 +46,7 @@ describe('DatabaseLearningObjectProvider', () => { | ||||||
|                 language: exampleLearningObject.language, |                 language: exampleLearningObject.language, | ||||||
|             }); |             }); | ||||||
|             expect(result).toBeTruthy(); |             expect(result).toBeTruthy(); | ||||||
|             expectToBeCorrectFilteredLearningObject(result!, exampleLearningObject); |             expectToBeCorrectFilteredLearningObject(result, exampleLearningObject); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         it('should return null when queried with an id that does not exist', async () => { |         it('should return null when queried with an id that does not exist', async () => { | ||||||
|  |  | ||||||
|  | @ -4,11 +4,11 @@ import { LearningObject } from '../../../src/entities/content/learning-object.en | ||||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | ||||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; | import learningObjectService from '../../../src/services/learning-objects/learning-object-service'; | ||||||
| import { LearningObjectIdentifier, LearningPathIdentifier } from '../../../src/interfaces/learning-content'; |  | ||||||
| import { Language } from '../../../src/entities/content/language'; |  | ||||||
| import { envVars, getEnvVar } from '../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||||
|  | import { LearningObjectIdentifier, LearningPathIdentifier } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; | const EXPECTED_DWENGO_LEARNING_OBJECT_TITLE = 'Werken met notebooks'; | ||||||
| const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifier = { | const DWENGO_TEST_LEARNING_OBJECT_ID: LearningObjectIdentifier = { | ||||||
|  | @ -105,8 +105,11 @@ describe('LearningObjectService', () => { | ||||||
|             expect(new Set(result.map((it) => it.key))).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); |             expect(new Set(result.map((it) => it.key))).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); | ||||||
|         }); |         }); | ||||||
|         it('returns an empty list when queried with a non-existing learning path id', async () => { |         it('returns an empty list when queried with a non-existing learning path id', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectsFromPath({ hruid: 'non_existing', language: Language.Dutch }); |             const result = await learningObjectService.getLearningObjectsFromPath({ | ||||||
|             expect(result).toEqual([]); |                 hruid: 'non_existing', | ||||||
|  |                 language: Language.Dutch, | ||||||
|  |             }); | ||||||
|  |             expect(result).toStrictEqual([]); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  | @ -120,8 +123,11 @@ describe('LearningObjectService', () => { | ||||||
|             expect(new Set(result)).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); |             expect(new Set(result)).toEqual(DWENGO_TEST_LEARNING_PATH_HRUIDS); | ||||||
|         }); |         }); | ||||||
|         it('returns an empty list when queried with a non-existing learning path id', async () => { |         it('returns an empty list when queried with a non-existing learning path id', async () => { | ||||||
|             const result = await learningObjectService.getLearningObjectIdsFromPath({ hruid: 'non_existing', language: Language.Dutch }); |             const result = await learningObjectService.getLearningObjectIdsFromPath({ | ||||||
|             expect(result).toEqual([]); |                 hruid: 'non_existing', | ||||||
|  |                 language: Language.Dutch, | ||||||
|  |             }); | ||||||
|  |             expect(result).toStrictEqual([]); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -13,13 +13,14 @@ import learningPathExample from '../../test-assets/learning-paths/pn-werking-exa | ||||||
| import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js'; | import databaseLearningPathProvider from '../../../src/services/learning-paths/database-learning-path-provider.js'; | ||||||
| import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | import { expectToBeCorrectLearningPath } from '../../test-utils/expectations.js'; | ||||||
| import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js'; | import learningObjectService from '../../../src/services/learning-objects/learning-object-service.js'; | ||||||
| import { Language } from '../../../src/entities/content/language.js'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { | import { | ||||||
|     ConditionTestLearningPathAndLearningObjects, |     ConditionTestLearningPathAndLearningObjects, | ||||||
|     createConditionTestLearningPathAndLearningObjects, |     createConditionTestLearningPathAndLearningObjects, | ||||||
| } from '../../test-assets/learning-paths/test-conditions-example.js'; | } from '../../test-assets/learning-paths/test-conditions-example.js'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity.js'; | import { Student } from '../../../src/entities/users/student.entity.js'; | ||||||
| import { LearningObjectNode, LearningPathResponse } from '../../../src/interfaces/learning-content.js'; | 
 | ||||||
|  | import { LearningObjectNode, LearningPathResponse } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  |  | ||||||
|  | @ -5,8 +5,8 @@ import { LearningPath } from '../../../src/entities/content/learning-path.entity | ||||||
| import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | import { getLearningObjectRepository, getLearningPathRepository } from '../../../src/data/repositories'; | ||||||
| import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | import learningObjectExample from '../../test-assets/learning-objects/pn-werkingnotebooks/pn-werkingnotebooks-example'; | ||||||
| import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | import learningPathExample from '../../test-assets/learning-paths/pn-werking-example'; | ||||||
| import { Language } from '../../../src/entities/content/language'; |  | ||||||
| import learningPathService from '../../../src/services/learning-paths/learning-path-service'; | import learningPathService from '../../../src/services/learning-paths/learning-path-service'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | async function initExampleData(): Promise<{ learningObject: LearningObject; learningPath: LearningPath }> { | ||||||
|     const learningObjectRepo = getLearningObjectRepository(); |     const learningObjectRepo = getLearningObjectRepository(); | ||||||
|  | @ -48,8 +48,8 @@ describe('LearningPathService', () => { | ||||||
|             expect(result.data?.length).toBe(1); |             expect(result.data?.length).toBe(1); | ||||||
| 
 | 
 | ||||||
|             // Should include all the nodes, even those pointing to foreign learning objects.
 |             // Should include all the nodes, even those pointing to foreign learning objects.
 | ||||||
|             expect([...result.data![0].nodes.map((it) => it.learningobject_hruid)].sort()).toEqual( |             expect([...result.data![0].nodes.map((it) => it.learningobject_hruid)].sort((a, b) => a.localeCompare(b))).toEqual( | ||||||
|                 example.learningPath.nodes.map((it) => it.learningObjectHruid).sort() |                 example.learningPath.nodes.map((it) => it.learningObjectHruid).sort((a, b) => a.localeCompare(b)) | ||||||
|             ); |             ); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { LearningObjectExample } from '../learning-object-example'; | import { LearningObjectExample } from '../learning-object-example'; | ||||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||||
| import { Language } from '../../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { LearningObjectExample } from '../learning-object-example'; | import { LearningObjectExample } from '../learning-object-example'; | ||||||
| import { Language } from '../../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { LearningObjectExample } from '../learning-object-example'; | ||||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||||
| import { Language } from '../../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||||
| 
 | 
 | ||||||
| const example: LearningObjectExample = { | const example: LearningObjectExample = { | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ import { LearningObjectExample } from '../learning-object-example'; | ||||||
| import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../../src/entities/content/learning-object.entity'; | ||||||
| import { loadTestAsset } from '../../../test-utils/load-test-asset'; | import { loadTestAsset } from '../../../test-utils/load-test-asset'; | ||||||
| import { envVars, getEnvVar } from '../../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../../src/util/envVars'; | ||||||
| import { Language } from '../../../../src/entities/content/language'; |  | ||||||
| import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../../src/services/learning-objects/processing/content-type'; | ||||||
|  | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| const example: LearningObjectExample = { | const example: LearningObjectExample = { | ||||||
|     createLearningObject: () => { |     createLearningObject: () => { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; | import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; | ||||||
| import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { envVars, getEnvVar } from '../../../src/util/envVars'; | import { envVars, getEnvVar } from '../../../src/util/envVars'; | ||||||
| import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | ||||||
| import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example'; | import testMultipleChoiceExample from '../learning-objects/test-multiple-choice/test-multiple-choice-example'; | ||||||
| import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; | import { dummyLearningObject } from '../learning-objects/dummy/dummy-learning-object-example'; | ||||||
| import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | import { createLearningPathNode, createLearningPathTransition } from './learning-path-utils'; | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { AssertionError } from 'node:assert'; | import { AssertionError } from 'node:assert'; | ||||||
| import { LearningObject } from '../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../src/entities/content/learning-object.entity'; | ||||||
| import { FilteredLearningObject, LearningPath } from '../../src/interfaces/learning-content'; |  | ||||||
| import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; | import { LearningPath as LearningPathEntity } from '../../src/entities/content/learning-path.entity'; | ||||||
| import { expect } from 'vitest'; | import { expect } from 'vitest'; | ||||||
|  | import { FilteredLearningObject, LearningPath } from '@dwengo-1/common/interfaces/learning-content'; | ||||||
| 
 | 
 | ||||||
| // Ignored properties because they belang for example to the class, not to the entity itself.
 | // Ignored properties because they belang for example to the class, not to the entity itself.
 | ||||||
| const IGNORE_PROPERTIES = ['parent']; | const IGNORE_PROPERTIES = ['parent']; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | import { Assignment } from '../../../src/entities/assignments/assignment.entity'; | ||||||
| import { Class } from '../../../src/entities/classes/class.entity'; | import { Class } from '../../../src/entities/classes/class.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| 
 | 
 | ||||||
| export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { | export function makeTestAssignemnts(em: EntityManager, classes: Class[]): Assignment[] { | ||||||
|     const assignment01 = em.create(Assignment, { |     const assignment01 = em.create(Assignment, { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Submission } from '../../../src/entities/assignments/submission.entity'; | import { Submission } from '../../../src/entities/assignments/submission.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| import { Group } from '../../../src/entities/assignments/group.entity'; | import { Group } from '../../../src/entities/assignments/group.entity'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { ClassJoinRequest, ClassJoinRequestStatus } from '../../../src/entities/classes/class-join-request.entity'; | import { ClassJoinRequest } from '../../../src/entities/classes/class-join-request.entity'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| import { Class } from '../../../src/entities/classes/class.entity'; | import { Class } from '../../../src/entities/classes/class.entity'; | ||||||
|  | import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export function makeTestClassJoinRequests(em: EntityManager, students: Student[], classes: Class[]): ClassJoinRequest[] { | export function makeTestClassJoinRequests(em: EntityManager, students: Student[], classes: Class[]): ClassJoinRequest[] { | ||||||
|     const classJoinRequest01 = em.create(ClassJoinRequest, { |     const classJoinRequest01 = em.create(ClassJoinRequest, { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | import { LearningObject } from '../../../src/entities/content/learning-object.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; | import { DwengoContentType } from '../../../src/services/learning-objects/processing/content-type'; | ||||||
| import { ReturnValue } from '../../../src/entities/content/return-value.entity'; | import { ReturnValue } from '../../../src/entities/content/return-value.entity'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | import { LearningPath } from '../../../src/entities/content/learning-path.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; | import { LearningPathTransition } from '../../../src/entities/content/learning-path-transition.entity'; | ||||||
| import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | import { LearningPathNode } from '../../../src/entities/content/learning-path-node.entity'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Question } from '../../../src/entities/questions/question.entity'; | import { Question } from '../../../src/entities/questions/question.entity'; | ||||||
| import { Language } from '../../../src/entities/content/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| 
 | 
 | ||||||
| export function makeTestQuestions(em: EntityManager, students: Student[]): Question[] { | export function makeTestQuestions(em: EntityManager, students: Student[]): Question[] { | ||||||
|  |  | ||||||
|  | @ -1,49 +1,19 @@ | ||||||
| import { EntityManager } from '@mikro-orm/core'; | import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { Student } from '../../../src/entities/users/student.entity'; | import { Student } from '../../../src/entities/users/student.entity'; | ||||||
| 
 | 
 | ||||||
|  | // 🔓 Ruwe testdata array — herbruikbaar in assertions
 | ||||||
|  | export const TEST_STUDENTS = [ | ||||||
|  |     { username: 'Noordkaap', firstName: 'Stijn', lastName: 'Meuris' }, | ||||||
|  |     { username: 'DireStraits', firstName: 'Mark', lastName: 'Knopfler' }, | ||||||
|  |     { username: 'Tool', firstName: 'Maynard', lastName: 'Keenan' }, | ||||||
|  |     { username: 'SmashingPumpkins', firstName: 'Billy', lastName: 'Corgan' }, | ||||||
|  |     { username: 'PinkFloyd', firstName: 'David', lastName: 'Gilmoure' }, | ||||||
|  |     { username: 'TheDoors', firstName: 'Jim', lastName: 'Morisson' }, | ||||||
|  |     // ⚠️ Deze mag niet gebruikt worden in elke test!
 | ||||||
|  |     { username: 'Nirvana', firstName: 'Kurt', lastName: 'Cobain' }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | // 🏗️ Functie die ORM entities maakt uit de data array
 | ||||||
| export function makeTestStudents(em: EntityManager): Student[] { | export function makeTestStudents(em: EntityManager): Student[] { | ||||||
|     const student01 = em.create(Student, { |     return TEST_STUDENTS.map((data) => em.create(Student, data)); | ||||||
|         username: 'Noordkaap', |  | ||||||
|         firstName: 'Stijn', |  | ||||||
|         lastName: 'Meuris', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const student02 = em.create(Student, { |  | ||||||
|         username: 'DireStraits', |  | ||||||
|         firstName: 'Mark', |  | ||||||
|         lastName: 'Knopfler', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const student03 = em.create(Student, { |  | ||||||
|         username: 'Tool', |  | ||||||
|         firstName: 'Maynard', |  | ||||||
|         lastName: 'Keenan', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const student04 = em.create(Student, { |  | ||||||
|         username: 'SmashingPumpkins', |  | ||||||
|         firstName: 'Billy', |  | ||||||
|         lastName: 'Corgan', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const student05 = em.create(Student, { |  | ||||||
|         username: 'PinkFloyd', |  | ||||||
|         firstName: 'David', |  | ||||||
|         lastName: 'Gilmoure', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const student06 = em.create(Student, { |  | ||||||
|         username: 'TheDoors', |  | ||||||
|         firstName: 'Jim', |  | ||||||
|         lastName: 'Morisson', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     // Do not use for any tests, gets deleted in a unit test
 |  | ||||||
|     const student07 = em.create(Student, { |  | ||||||
|         username: 'Nirvana', |  | ||||||
|         firstName: 'Kurt', |  | ||||||
|         lastName: 'Cobain', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     return [student01, student02, student03, student04, student05, student06, student07]; |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,11 @@ | ||||||
|     "include": ["src/**/*.ts"], |     "include": ["src/**/*.ts"], | ||||||
|     "compilerOptions": { |     "compilerOptions": { | ||||||
|         "rootDir": "./src", |         "rootDir": "./src", | ||||||
|         "outDir": "./dist", |         "outDir": "./dist" | ||||||
|         "resolveJsonModule": true |     }, | ||||||
|     } |     "references": [ | ||||||
|  |         { | ||||||
|  |             "path": "../common" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,6 @@ export default defineConfig({ | ||||||
|     test: { |     test: { | ||||||
|         environment: 'node', |         environment: 'node', | ||||||
|         globals: true, |         globals: true, | ||||||
|         testTimeout: 10000, |         testTimeout: 100000, | ||||||
|     }, |     }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Reference in a new issue
	
	 laurejablonski
						laurejablonski