Merge branch 'feat/service-layer' into feat/service-layer-adriaan
# Conflicts: # backend/src/controllers/classes.ts # backend/src/controllers/students.ts # backend/src/data/users/teacher-repository.ts # backend/src/interfaces/assignment.ts # backend/src/interfaces/teacher.ts # backend/src/routes/classes.ts # backend/src/services/assignments.ts # backend/src/services/class.ts # backend/src/services/students.ts # backend/src/util/translation-helper.ts
This commit is contained in:
		
						commit
						6c4ea0eefb
					
				
					 33 changed files with 454 additions and 137 deletions
				
			
		|  | @ -3,21 +3,23 @@ import { initORM } from './orm.js'; | ||||||
| import { EnvVars, getNumericEnvVar } from './util/envvars.js'; | import { EnvVars, getNumericEnvVar } from './util/envvars.js'; | ||||||
| 
 | 
 | ||||||
| import themeRoutes from './routes/themes.js'; | import themeRoutes from './routes/themes.js'; | ||||||
| import learningPathRoutes from './routes/learningPaths.js'; | import learningPathRoutes from './routes/learning-paths.js'; | ||||||
| import learningObjectRoutes from './routes/learningObjects.js'; | import learningObjectRoutes from './routes/learning-objects.js'; | ||||||
| 
 | 
 | ||||||
| import studentRouter from './routes/student.js'; | import studentRoutes from './routes/students.js'; | ||||||
| import groupRouter from './routes/group.js'; | import teacherRoutes from './routes/teachers.js' | ||||||
| import submissionRouter from './routes/submission.js'; | import groupRoutes from './routes/groups.js'; | ||||||
| import classRouter from './routes/class.js'; | import submissionRoutes from './routes/submissions.js'; | ||||||
| import questionRouter from './routes/question.js'; | import classRoutes from './routes/classes.js'; | ||||||
| import loginRouter from './routes/login.js'; | import questionRoutes from './routes/questions.js'; | ||||||
| 
 | 
 | ||||||
| const app: Express = express(); | const app: Express = express(); | ||||||
| const port: string | number = getNumericEnvVar(EnvVars.Port); | const port: string | number = getNumericEnvVar(EnvVars.Port); | ||||||
| 
 | 
 | ||||||
| app.use(express.json()); | app.use(express.json()); | ||||||
| 
 | 
 | ||||||
|  | app.use(express.json()); | ||||||
|  | 
 | ||||||
| // TODO Replace with Express routes
 | // TODO Replace with Express routes
 | ||||||
| app.get('/', (_, res: Response) => { | app.get('/', (_, res: Response) => { | ||||||
|     res.json({ |     res.json({ | ||||||
|  | @ -25,12 +27,12 @@ app.get('/', (_, res: Response) => { | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| app.use('/student', studentRouter); | app.use('/student', studentRoutes); | ||||||
| app.use('/group', groupRouter); | app.use('/teacher', teacherRoutes); | ||||||
| app.use('/submission', submissionRouter); | app.use('/group', groupRoutes); | ||||||
| app.use('/class', classRouter); | app.use('/submission', submissionRoutes); | ||||||
| app.use('/question', questionRouter); | app.use('/class', classRoutes); | ||||||
| app.use('/login', loginRouter); | app.use('/question', questionRoutes); | ||||||
| 
 | 
 | ||||||
| app.use('/theme', themeRoutes); | app.use('/theme', themeRoutes); | ||||||
| app.use('/learningPath', learningPathRoutes); | app.use('/learningPath', learningPathRoutes); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { getAllClasses, getClass, getClassStudents, getClassTeacherInvitations } from '../services/class.js'; | import { getAllClasses, getClass, getClassStudents, getClassStudentsIds, getClassTeacherInvitations } from '../services/class.js'; | ||||||
|  | import { ClassDTO } from '../interfaces/classes.js'; | ||||||
| 
 | 
 | ||||||
| export async function getAllClassesHandler( | export async function getAllClassesHandler( | ||||||
|     req: Request, |     req: Request, | ||||||
|  | @ -47,7 +48,9 @@ export async function getClassStudentsHandler( | ||||||
|     const classId = req.params.id; |     const classId = req.params.id; | ||||||
|     const full = req.query.full === "true"; |     const full = req.query.full === "true"; | ||||||
| 
 | 
 | ||||||
|     const students = await getClassStudents(classId, full); |     let students = full | ||||||
|  |         ? await getClassStudents(classId) | ||||||
|  |         : await getClassStudentsIds(classId); | ||||||
| 
 | 
 | ||||||
|     res.json({ |     res.json({ | ||||||
|         students: students, |         students: students, | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ import { | ||||||
|     getLearningObjectById, |     getLearningObjectById, | ||||||
|     getLearningObjectIdsFromPath, |     getLearningObjectIdsFromPath, | ||||||
|     getLearningObjectsFromPath, |     getLearningObjectsFromPath, | ||||||
| } from '../services/learningObjects.js'; | } from '../services/learning-objects.js'; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| import { FilteredLearningObject } from '../interfaces/learningPath'; | import { FilteredLearningObject } from '../interfaces/learning-path'; | ||||||
| 
 | 
 | ||||||
| export async function getAllLearningObjects( | export async function getAllLearningObjects( | ||||||
|     req: Request, |     req: Request, | ||||||
|  | @ -4,7 +4,7 @@ import { FALLBACK_LANG } from '../config.js'; | ||||||
| import { | import { | ||||||
|     fetchLearningPaths, |     fetchLearningPaths, | ||||||
|     searchLearningPaths, |     searchLearningPaths, | ||||||
| } from '../services/learningPaths.js'; | } from '../services/learning-paths.js'; | ||||||
| /** | /** | ||||||
|  * Fetch learning paths based on query parameters. |  * Fetch learning paths based on query parameters. | ||||||
|  */ |  */ | ||||||
							
								
								
									
										155
									
								
								backend/src/controllers/teachers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								backend/src/controllers/teachers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,155 @@ | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import { | ||||||
|  |     createTeacher, | ||||||
|  |     deleteTeacher, | ||||||
|  |     getTeacherByUsername, | ||||||
|  |     getClassesByTeacher, | ||||||
|  |     getClassIdsByTeacher, | ||||||
|  |     getAllTeachers, | ||||||
|  |     getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher, getQuestionsByTeacher, getQuestionIdsByTeacher | ||||||
|  | } from '../services/teachers.js'; | ||||||
|  | import {TeacherDTO} from "../interfaces/teacher"; | ||||||
|  | import {ClassDTO} from "../interfaces/class"; | ||||||
|  | import {StudentDTO} from "../interfaces/student"; | ||||||
|  | import {QuestionDTO, QuestionId} from "../interfaces/question"; | ||||||
|  | 
 | ||||||
|  | export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const full = req.query.full === 'true'; | ||||||
|  |         const username = req.query.username as string; | ||||||
|  | 
 | ||||||
|  |         if (username){ | ||||||
|  |             const teacher = await getTeacherByUsername(username); | ||||||
|  |             if (!teacher){ | ||||||
|  |                 res.status(404).json({ error: `Teacher with username '${username}' not found.` }); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             res.json(teacher); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let teachers: TeacherDTO[] | string[]; | ||||||
|  | 
 | ||||||
|  |         if (full) teachers = await getAllTeachers(); | ||||||
|  |         else teachers = await getAllTeachersIds(); | ||||||
|  | 
 | ||||||
|  |         res.json(teachers); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error("❌ Error fetching teachers:", error); | ||||||
|  |         res.status(500).json({ error: "Internal server error" }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createTeacherHandler( | ||||||
|  |     req: Request, | ||||||
|  |     res: Response | ||||||
|  | ): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const teacherData = req.body as TeacherDTO; | ||||||
|  | 
 | ||||||
|  |         if (!teacherData.username || !teacherData.firstName || !teacherData.lastName) { | ||||||
|  |             res.status(400).json({ error: 'Missing required fields: username, firstName, lastName' }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const newTeacher = await createTeacher(teacherData); | ||||||
|  |         res.status(201).json(newTeacher); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error creating teacher:', error); | ||||||
|  |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteTeacherHandler( | ||||||
|  |     req: Request, | ||||||
|  |     res: Response | ||||||
|  | ): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const username = req.params.username as string; | ||||||
|  | 
 | ||||||
|  |         if (!username) { | ||||||
|  |             res.status(400).json({ error: 'Missing required field: username' }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const deletedTeacher = await deleteTeacher(username); | ||||||
|  | 
 | ||||||
|  |         if (!deletedTeacher) { | ||||||
|  |             res.status(400).json({ error: `Did not find teacher with username ${username}` }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         res.status(201).json(deletedTeacher); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error deleting teacher:', error); | ||||||
|  |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getTeacherClassHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const username = req.params.username as string; | ||||||
|  |         const full = req.query.full === 'true'; | ||||||
|  | 
 | ||||||
|  |         if (!username) { | ||||||
|  |             res.status(400).json({ error: 'Missing required field: username' }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let classes: ClassDTO[] | string[]; | ||||||
|  | 
 | ||||||
|  |         if (full) classes = await getClassesByTeacher(username); | ||||||
|  |         else classes = await getClassIdsByTeacher(username); | ||||||
|  | 
 | ||||||
|  |         res.status(201).json(classes); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error fetching classes by teacher:', error); | ||||||
|  |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getTeacherStudentHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const username = req.params.username as string; | ||||||
|  |         const full = req.query.full === 'true'; | ||||||
|  | 
 | ||||||
|  |         if (!username) { | ||||||
|  |             res.status(400).json({ error: 'Missing required field: username' }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let students: StudentDTO[] | string[]; | ||||||
|  | 
 | ||||||
|  |         if (full) students = await getStudentsByTeacher(username); | ||||||
|  |         else students = await getStudentIdsByTeacher(username); | ||||||
|  | 
 | ||||||
|  |         res.status(201).json(students); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error fetching students by teacher:', error); | ||||||
|  |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getTeacherQuestionHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     try { | ||||||
|  |         const username = req.params.username as string; | ||||||
|  |         const full = req.query.full === 'true'; | ||||||
|  | 
 | ||||||
|  |         if (!username) { | ||||||
|  |             res.status(400).json({ error: 'Missing required field: username' }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let questions: QuestionDTO[] | QuestionId[]; | ||||||
|  | 
 | ||||||
|  |         if (full) questions = await getQuestionsByTeacher(username); | ||||||
|  |         else questions = await getQuestionIdsByTeacher(username); | ||||||
|  | 
 | ||||||
|  |         res.status(201).json(questions); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('Error fetching questions by teacher:', error); | ||||||
|  |         res.status(500).json({ error: 'Internal server error' }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Request, Response } from 'express'; | import { Request, Response } from 'express'; | ||||||
| import { themes } from '../data/themes.js'; | import { themes } from '../data/themes.js'; | ||||||
| import { loadTranslations } from "../util/translationHelper.js"; | import { loadTranslations } from "../util/translation-helper.js"; | ||||||
| import { FALLBACK_LANG } from '../config.js'; | import { FALLBACK_LANG } from '../config.js'; | ||||||
| 
 | 
 | ||||||
| interface Translations { | interface Translations { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| 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"; | ||||||
| 
 | 
 | ||||||
| export class ClassRepository extends DwengoEntityRepository<Class> { | export class ClassRepository extends DwengoEntityRepository<Class> { | ||||||
|     public findById(id: string): Promise<Class | null> { |     public findById(id: string): Promise<Class | null> { | ||||||
|  | @ -18,4 +19,11 @@ export class ClassRepository extends DwengoEntityRepository<Class> { | ||||||
|             { populate: ["students", "teachers"] } // voegt student en teacher objecten toe
 |             { populate: ["students", "teachers"] } // voegt student en teacher objecten toe
 | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public findByTeacher(teacher: Teacher): Promise<Class[]> { | ||||||
|  |         return this.find( | ||||||
|  |             { teachers: teacher }, | ||||||
|  |             { populate: ["students", "teachers"] } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +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 {Teacher} from "../../entities/users/teacher.entity"; | ||||||
| 
 | 
 | ||||||
| export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | ||||||
|     public findByIdentifier( |     public findByIdentifier( | ||||||
|  | @ -13,4 +14,11 @@ export class LearningObjectRepository extends DwengoEntityRepository<LearningObj | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     // This repository is read-only for now since creating own learning object is an extension feature.
 |     // This repository is read-only for now since creating own learning object is an extension feature.
 | ||||||
|  | 
 | ||||||
|  |     public findAllByTeacher(teacher: Teacher): Promise<LearningObject[]> { | ||||||
|  |         return this.find( | ||||||
|  |             { admins: teacher }, | ||||||
|  |             { populate: ['admins'] } // Make sure to load admin relations
 | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| 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 { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||||
| import { Student } from '../../entities/users/student.entity.js'; | import { Student } from '../../entities/users/student.entity.js'; | ||||||
|  | import {LearningObject} from "../../entities/content/learning-object.entity"; | ||||||
| 
 | 
 | ||||||
| export class QuestionRepository extends DwengoEntityRepository<Question> { | export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|     public createQuestion(question: { |     public createQuestion(question: { | ||||||
|  | @ -42,4 +43,17 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||||
|             sequenceNumber: sequenceNumber, |             sequenceNumber: sequenceNumber, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public async findAllByLearningObjects(learningObjects: LearningObject[]): Promise<Question[]> { | ||||||
|  |         const objectIdentifiers = learningObjects.map(lo => ({ | ||||||
|  |                 learningObjectHruid: lo.hruid, | ||||||
|  |                 learningObjectLanguage: lo.language, | ||||||
|  |                 learningObjectVersion: lo.version | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         return this.findAll({ | ||||||
|  |             where: { $or: objectIdentifiers }, | ||||||
|  |             orderBy: { timestamp: 'ASC' }, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										41
									
								
								backend/src/interfaces/question.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								backend/src/interfaces/question.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | import {Question} from "../entities/questions/question.entity"; | ||||||
|  | import {Enum, PrimaryKey} from "@mikro-orm/core"; | ||||||
|  | import {Language} from "../entities/content/language"; | ||||||
|  | 
 | ||||||
|  | export interface QuestionDTO { | ||||||
|  |     learningObjectHruid: string; | ||||||
|  |     learningObjectLanguage: string; | ||||||
|  |     learningObjectVersion: string; | ||||||
|  |     sequenceNumber: number; | ||||||
|  |     authorUsername: string; | ||||||
|  |     timestamp: string; | ||||||
|  |     content: string; | ||||||
|  |     endpoints?: { | ||||||
|  |         classes: string; | ||||||
|  |         questions: string; | ||||||
|  |         invitations: string; | ||||||
|  |         groups: string; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Convert a Question entity to a DTO format. | ||||||
|  |  */ | ||||||
|  | export function mapToQuestionDTO(question: Question): QuestionDTO { | ||||||
|  |     return { | ||||||
|  |         learningObjectHruid: question.learningObjectHruid, | ||||||
|  |         learningObjectLanguage: question.learningObjectLanguage, | ||||||
|  |         learningObjectVersion: question.learningObjectVersion, | ||||||
|  |         sequenceNumber: question.sequenceNumber, | ||||||
|  |         authorUsername: question.author.username, | ||||||
|  |         timestamp: question.timestamp.toISOString(), | ||||||
|  |         content: question.content, | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface QuestionId { | ||||||
|  |     learningObjectHruid: string, | ||||||
|  |     learningObjectLanguage: Language, | ||||||
|  |     learningObjectVersion: string, | ||||||
|  |     sequenceNumber: number | ||||||
|  | } | ||||||
|  | @ -1,23 +1,36 @@ | ||||||
| import { Teacher } from "../entities/users/teacher.entity.js"; | import { Teacher } from "../entities/users/teacher.entity.js"; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Teacher Data Transfer Object | ||||||
|  |  */ | ||||||
| export interface TeacherDTO { | export interface TeacherDTO { | ||||||
|     id: string; |  | ||||||
|     username: string; |     username: string; | ||||||
|     firstName: string; |     firstName: string; | ||||||
|     lastName: string; |     lastName: string; | ||||||
|     endpoints?: { |     endpoints?: { | ||||||
|  |         self: string; | ||||||
|         classes: string; |         classes: string; | ||||||
|         questions: string; |         questions: string; | ||||||
|         invitations: string; |         invitations: string; | ||||||
|         groups: string; |  | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Maps a Teacher entity to a TeacherDTO | ||||||
|  |  */ | ||||||
| export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { | export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { | ||||||
|     return { |     return { | ||||||
|         id: teacher.username, |  | ||||||
|         username: teacher.username, |         username: teacher.username, | ||||||
|         firstName: teacher.firstName, |         firstName: teacher.firstName, | ||||||
|         lastName: teacher.lastName, |         lastName: teacher.lastName, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function mapToTeacher(teacherData: TeacherDTO): Teacher { | ||||||
|  |     const teacher = new Teacher(); | ||||||
|  |     teacher.username = teacherData.username; | ||||||
|  |     teacher.firstName = teacherData.firstName; | ||||||
|  |     teacher.lastName = teacherData.lastName; | ||||||
|  | 
 | ||||||
|  |     return teacher; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import express from 'express'; | ||||||
| import { | import { | ||||||
|     getAllLearningObjects, |     getAllLearningObjects, | ||||||
|     getLearningObject, |     getLearningObject, | ||||||
| } from '../controllers/learningObjects.js'; | } from '../controllers/learning-objects.js'; | ||||||
| 
 | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import express from 'express'; | import express from 'express'; | ||||||
| import { getLearningPaths } from '../controllers/learningPaths.js'; | import { getLearningPaths } from '../controllers/learning-paths.js'; | ||||||
| 
 | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| import express from 'express' |  | ||||||
| const router = express.Router(); |  | ||||||
| 
 |  | ||||||
| // returns login paths for IDP
 |  | ||||||
| router.get('/', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         // dummy variables, needs to be changed
 |  | ||||||
|         // with IDP endpoints
 |  | ||||||
|         leerkracht: '/login-leerkracht', |  | ||||||
|         leerling: '/login-leerling', |  | ||||||
|     }); |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| export default router |  | ||||||
|  | @ -1,58 +0,0 @@ | ||||||
| import express from 'express' |  | ||||||
| const router = express.Router(); |  | ||||||
| 
 |  | ||||||
| // root endpoint used to search objects
 |  | ||||||
| router.get('/', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         teachers: [ |  | ||||||
|             '0', |  | ||||||
|             '1', |  | ||||||
|         ] |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| // information about a teacher
 |  | ||||||
| router.get('/:id', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         id: req.params.id, |  | ||||||
|         firstName: 'John', |  | ||||||
|         lastName: 'Doe', |  | ||||||
|         username: 'JohnDoe1', |  | ||||||
|         links: { |  | ||||||
|             self: `${req.baseUrl}/${req.params.id}`, |  | ||||||
|             classes: `${req.baseUrl}/${req.params.id}/classes`, |  | ||||||
|             questions: `${req.baseUrl}/${req.params.id}/questions`, |  | ||||||
|             invitations: `${req.baseUrl}/${req.params.id}/invitations`, |  | ||||||
|         }, |  | ||||||
|     }); |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| // the questions students asked a teacher
 |  | ||||||
| router.get('/:id/questions', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         questions: [ |  | ||||||
|             '0' |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| // invitations to other classes a teacher received
 |  | ||||||
| router.get('/:id/invitations', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         invitations: [ |  | ||||||
|             '0' |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| // a list with ids of classes a teacher is in
 |  | ||||||
| router.get('/:id/classes', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         classes: [ |  | ||||||
|             '0' |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| export default router |  | ||||||
							
								
								
									
										34
									
								
								backend/src/routes/teachers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/src/routes/teachers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | import express from 'express' | ||||||
|  | import { | ||||||
|  |     createTeacherHandler, | ||||||
|  |     deleteTeacherHandler, | ||||||
|  |     getTeacherClassHandler, | ||||||
|  |     getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler | ||||||
|  | } from "../controllers/teachers.js"; | ||||||
|  | const router = express.Router(); | ||||||
|  | 
 | ||||||
|  | // root endpoint used to search objects
 | ||||||
|  | router.get('/', getTeacherHandler); | ||||||
|  | 
 | ||||||
|  | router.post('/', createTeacherHandler); | ||||||
|  | 
 | ||||||
|  | router.delete('/:username', deleteTeacherHandler); | ||||||
|  | 
 | ||||||
|  | router.get('/:username/classes', getTeacherClassHandler); | ||||||
|  | 
 | ||||||
|  | router.get('/:username/students', getTeacherStudentHandler); | ||||||
|  | 
 | ||||||
|  | router.get('/:username/questions', getTeacherQuestionHandler); | ||||||
|  | 
 | ||||||
|  | // invitations to other classes a teacher received
 | ||||||
|  | router.get('/:id/invitations', (req, res) => { | ||||||
|  |     res.json({ | ||||||
|  |         invitations: [ | ||||||
|  |             '0' | ||||||
|  |         ], | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export default router | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { getClassRepository, getTeacherInvitationRepository } from "../data/repositories.js"; | import { getClassRepository } from "../data/repositories"; | ||||||
| import { ClassDTO, mapToClassDTO } from "../interfaces/classes.js"; | import { Class } from "../entities/classes/class.entity"; | ||||||
| import { mapToStudentDTO, StudentDTO } from "../interfaces/students.js"; | import { ClassDTO, mapToClassDTO } from "../interfaces/class"; | ||||||
| import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation.js"; | import { mapToStudentDTO, StudentDTO } from "../interfaces/student"; | ||||||
| 
 | 
 | ||||||
| export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> { | export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> { | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|  | @ -28,41 +28,21 @@ export async function getClass(classId: string): Promise<ClassDTO | null> { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getClassStudents(classId: string, full: boolean): Promise<StudentDTO[] | string[]> { | async function fetchClassStudents(classId: string, full: boolean): Promise<StudentDTO[] | string[]> { | ||||||
|     const classRepository = getClassRepository(); |     const classRepository = getClassRepository(); | ||||||
|     const cls = await classRepository.findById(classId); |     const cls = await classRepository.findById(classId); | ||||||
| 
 | 
 | ||||||
|     if (!cls) { |     if (!cls) | ||||||
|         return []; |         return []; | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (full) { |  | ||||||
|     return cls.students.map(mapToStudentDTO); |     return cls.students.map(mapToStudentDTO); | ||||||
|     } else { |  | ||||||
|         return cls.students.map((student) => student.username); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getClassTeacherInvitations(classId: string, full: boolean): Promise<TeacherInvitationDTO[]> { | export async function getClassStudents(classId: string): Promise<StudentDTO[]> { | ||||||
|     const classRepository = getClassRepository(); |     return await fetchClassStudents(classId); | ||||||
|     const cls = await classRepository.findById(classId); |  | ||||||
| 
 |  | ||||||
|     if (!cls) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const teacherInvitationRepository = getTeacherInvitationRepository(); |  | ||||||
|     const invitations = await teacherInvitationRepository.findAllInvitationsForClass(cls); |  | ||||||
| 
 |  | ||||||
|     console.log(invitations); |  | ||||||
| 
 |  | ||||||
|     if (!invitations) { |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (full) { |  | ||||||
|         return invitations.map(mapToTeacherInvitationDTO); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return invitations.map(mapToTeacherInvitationDTOIds); |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function getClassStudentsIds(classId: string): Promise<string[]> { | ||||||
|  |     return await fetchClassStudents(classId).map((student) => student.username); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import { DWENGO_API_BASE } from '../config.js'; | import { DWENGO_API_BASE } from '../config.js'; | ||||||
| import { fetchWithLogging } from '../util/apiHelper.js'; | import { fetchWithLogging } from '../util/api-helper.js'; | ||||||
| import { | import { | ||||||
|     FilteredLearningObject, |     FilteredLearningObject, | ||||||
|     LearningObjectMetadata, |     LearningObjectMetadata, | ||||||
|     LearningObjectNode, |     LearningObjectNode, | ||||||
|     LearningPathResponse, |     LearningPathResponse, | ||||||
| } from '../interfaces/learningPath.js'; | } from '../interfaces/learning-path.js'; | ||||||
| import { fetchLearningPaths } from './learningPaths.js'; | import { fetchLearningPaths } from './learning-paths.js'; | ||||||
| 
 | 
 | ||||||
| function filterData( | function filterData( | ||||||
|     data: LearningObjectMetadata, |     data: LearningObjectMetadata, | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { fetchWithLogging } from '../util/apiHelper.js'; | import { fetchWithLogging } from '../util/api-helper.js'; | ||||||
| import { DWENGO_API_BASE } from '../config.js'; | import { DWENGO_API_BASE } from '../config.js'; | ||||||
| import { | import { | ||||||
|     LearningPath, |     LearningPath, | ||||||
|     LearningPathResponse, |     LearningPathResponse, | ||||||
| } from '../interfaces/learningPath.js'; | } from '../interfaces/learning-path.js'; | ||||||
| 
 | 
 | ||||||
| export async function fetchLearningPaths( | export async function fetchLearningPaths( | ||||||
|     hruids: string[], |     hruids: string[], | ||||||
							
								
								
									
										131
									
								
								backend/src/services/teachers.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								backend/src/services/teachers.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,131 @@ | ||||||
|  | import { | ||||||
|  |     getClassRepository, | ||||||
|  |     getLearningObjectRepository, | ||||||
|  |     getQuestionRepository, | ||||||
|  |     getTeacherRepository | ||||||
|  | } from "../data/repositories.js"; | ||||||
|  | import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; | ||||||
|  | import { Teacher } from "../entities/users/teacher.entity"; | ||||||
|  | import {ClassDTO, mapToClassDTO} from "../interfaces/class"; | ||||||
|  | import {getClassStudents, getClassStudentsIds} from "./class"; | ||||||
|  | import {StudentDTO} from "../interfaces/student"; | ||||||
|  | import {mapToQuestionDTO, QuestionDTO, QuestionId} from "../interfaces/question"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | async function fetchAllTeachers(): Promise<TeacherDTO[]> { | ||||||
|  |     const teacherRepository = getTeacherRepository(); | ||||||
|  |     const teachers = await teacherRepository.find({}); | ||||||
|  | 
 | ||||||
|  |     return teachers.map(mapToTeacherDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getAllTeachers(): Promise<TeacherDTO[]> { | ||||||
|  |     return await fetchAllTeachers(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getAllTeachersIds(): Promise<string[]> { | ||||||
|  |     return await fetchAllTeachers().map((teacher) => teacher.username) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createTeacher(teacherData: TeacherDTO): Promise<Teacher> { | ||||||
|  |     const teacherRepository = getTeacherRepository(); | ||||||
|  |     const newTeacher = mapToTeacher(teacherData); | ||||||
|  | 
 | ||||||
|  |     await teacherRepository.addTeacher(newTeacher); | ||||||
|  |     return newTeacher; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getTeacherByUsername(username: string): Promise<TeacherDTO | null> { | ||||||
|  |     const teacherRepository = getTeacherRepository(); | ||||||
|  |     const teacher = await teacherRepository.findByUsername(username); | ||||||
|  | 
 | ||||||
|  |     return teacher ? mapToTeacherDTO(teacher) : null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteTeacher(username: string): Promise<TeacherDTO | null> { | ||||||
|  |     const teacherRepository = getTeacherRepository(); | ||||||
|  |     const teacher = await teacherRepository.findByUsername(username); | ||||||
|  | 
 | ||||||
|  |     if (!teacher) | ||||||
|  |         return null; | ||||||
|  | 
 | ||||||
|  |     await teacherRepository.deleteByUsername(username); | ||||||
|  |     return teacher; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function fetchClassesByTeacher(username: string): Promise<ClassDTO[]> { | ||||||
|  |     const teacherRepository = getTeacherRepository(); | ||||||
|  |     const classRepository = getClassRepository(); | ||||||
|  | 
 | ||||||
|  |     const teacher = await teacherRepository.findByUsername(username); | ||||||
|  |     if (!teacher) { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const classes = await classRepository.findByTeacher(teacher); | ||||||
|  |     return classes.map(mapToClassDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getClassesByTeacher(username: string): Promise<ClassDTO[]> { | ||||||
|  |     return await fetchClassesByTeacher(username) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getClassIdsByTeacher(): Promise<string[]> { | ||||||
|  |     return await fetchClassesByTeacher(username).map((cls) => cls.id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function fetchStudentsByTeacher(username: string) { | ||||||
|  |     const classes = await getClassIdsByTeacher(); | ||||||
|  | 
 | ||||||
|  |     return Promise.all( | ||||||
|  |         classes.map( async (id) => getClassStudents(id)) | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentsByTeacher(username: string): Promise<StudentDTO[]> { | ||||||
|  |     return await fetchStudentsByTeacher(username); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getStudentIdsByTeacher(): Promise<string[]> { | ||||||
|  |     return await fetchStudentsByTeacher(username).map((student) => student.username); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function fetchTeacherQuestions(username: string): Promise<QuestionDTO[]> { | ||||||
|  |     const learningObjectRepository = getLearningObjectRepository(); | ||||||
|  |     const questionRepository = getQuestionRepository(); | ||||||
|  | 
 | ||||||
|  |     const teacher = getTeacherByUsername(username); | ||||||
|  |     if (!teacher) { | ||||||
|  |         throw new Error(`Teacher with username '${username}' not found.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Find all learning objects that this teacher manages
 | ||||||
|  |     const learningObjects = await learningObjectRepository.findAllByTeacher(teacher); | ||||||
|  | 
 | ||||||
|  |     // Fetch all questions related to these learning objects
 | ||||||
|  |     const questions = await questionRepository.findAllByLearningObjects(learningObjects); | ||||||
|  | 
 | ||||||
|  |     return questions.map(mapToQuestionDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getQuestionsByTeacher(username: string): Promise<QuestionDTO[]> { | ||||||
|  |     return await fetchTeacherQuestions(username); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getQuestionIdsByTeacher(username: string): Promise<QuestionId[]> { | ||||||
|  |     const questions = await fetchTeacherQuestions(username); | ||||||
|  | 
 | ||||||
|  |     return questions.map((question) => ({ | ||||||
|  |         learningObjectHruid: question.learningObjectHruid, | ||||||
|  |         learningObjectLanguage: question.learningObjectLanguage, | ||||||
|  |         learningObjectVersion: question.learningObjectVersion, | ||||||
|  |         sequenceNumber: question.sequenceNumber | ||||||
|  |     })); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import fs from 'fs'; | import fs from 'fs'; | ||||||
| import path from 'path'; | import path from 'path'; | ||||||
| import yaml from 'js-yaml'; | import yaml from 'js-yaml'; | ||||||
| import {FALLBACK_LANG} from "../config.js"; | import { FALLBACK_LANG } from "../config.js"; | ||||||
| 
 | 
 | ||||||
| export function loadTranslations<T>(language: string): T { | export function loadTranslations<T>(language: string): T { | ||||||
|     try { |     try { | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl