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 themeRoutes from './routes/themes.js'; | ||||
| import learningPathRoutes from './routes/learningPaths.js'; | ||||
| import learningObjectRoutes from './routes/learningObjects.js'; | ||||
| import learningPathRoutes from './routes/learning-paths.js'; | ||||
| import learningObjectRoutes from './routes/learning-objects.js'; | ||||
| 
 | ||||
| import studentRouter from './routes/student.js'; | ||||
| import groupRouter from './routes/group.js'; | ||||
| import submissionRouter from './routes/submission.js'; | ||||
| import classRouter from './routes/class.js'; | ||||
| import questionRouter from './routes/question.js'; | ||||
| import loginRouter from './routes/login.js'; | ||||
| import studentRoutes from './routes/students.js'; | ||||
| import teacherRoutes from './routes/teachers.js' | ||||
| import groupRoutes from './routes/groups.js'; | ||||
| import submissionRoutes from './routes/submissions.js'; | ||||
| import classRoutes from './routes/classes.js'; | ||||
| import questionRoutes from './routes/questions.js'; | ||||
| 
 | ||||
| const app: Express = express(); | ||||
| const port: string | number = getNumericEnvVar(EnvVars.Port); | ||||
| 
 | ||||
| app.use(express.json()); | ||||
| 
 | ||||
| app.use(express.json()); | ||||
| 
 | ||||
| // TODO Replace with Express routes
 | ||||
| app.get('/', (_, res: Response) => { | ||||
|     res.json({ | ||||
|  | @ -25,12 +27,12 @@ app.get('/', (_, res: Response) => { | |||
|     }); | ||||
| }); | ||||
| 
 | ||||
| app.use('/student', studentRouter); | ||||
| app.use('/group', groupRouter); | ||||
| app.use('/submission', submissionRouter); | ||||
| app.use('/class', classRouter); | ||||
| app.use('/question', questionRouter); | ||||
| app.use('/login', loginRouter); | ||||
| app.use('/student', studentRoutes); | ||||
| app.use('/teacher', teacherRoutes); | ||||
| app.use('/group', groupRoutes); | ||||
| app.use('/submission', submissionRoutes); | ||||
| app.use('/class', classRoutes); | ||||
| app.use('/question', questionRoutes); | ||||
| 
 | ||||
| app.use('/theme', themeRoutes); | ||||
| app.use('/learningPath', learningPathRoutes); | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| 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( | ||||
|     req: Request, | ||||
|  | @ -47,7 +48,9 @@ export async function getClassStudentsHandler( | |||
|     const classId = req.params.id; | ||||
|     const full = req.query.full === "true"; | ||||
| 
 | ||||
|     const students = await getClassStudents(classId, full); | ||||
|     let students = full | ||||
|         ? await getClassStudents(classId) | ||||
|         : await getClassStudentsIds(classId); | ||||
| 
 | ||||
|     res.json({ | ||||
|         students: students, | ||||
|  |  | |||
|  | @ -3,9 +3,9 @@ import { | |||
|     getLearningObjectById, | ||||
|     getLearningObjectIdsFromPath, | ||||
|     getLearningObjectsFromPath, | ||||
| } from '../services/learningObjects.js'; | ||||
| } from '../services/learning-objects.js'; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| import { FilteredLearningObject } from '../interfaces/learningPath'; | ||||
| import { FilteredLearningObject } from '../interfaces/learning-path'; | ||||
| 
 | ||||
| export async function getAllLearningObjects( | ||||
|     req: Request, | ||||
|  | @ -4,7 +4,7 @@ import { FALLBACK_LANG } from '../config.js'; | |||
| import { | ||||
|     fetchLearningPaths, | ||||
|     searchLearningPaths, | ||||
| } from '../services/learningPaths.js'; | ||||
| } from '../services/learning-paths.js'; | ||||
| /** | ||||
|  * 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 { themes } from '../data/themes.js'; | ||||
| import { loadTranslations } from "../util/translationHelper.js"; | ||||
| import { loadTranslations } from "../util/translation-helper.js"; | ||||
| import { FALLBACK_LANG } from '../config.js'; | ||||
| 
 | ||||
| interface Translations { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||
| import { Class } from '../../entities/classes/class.entity.js'; | ||||
| import { Student } from '../../entities/users/student.entity.js'; | ||||
| import {Teacher} from "../../entities/users/teacher.entity"; | ||||
| 
 | ||||
| export class ClassRepository extends DwengoEntityRepository<Class> { | ||||
|     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
 | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     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 { LearningObject } from '../../entities/content/learning-object.entity.js'; | ||||
| import { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||
| import {Teacher} from "../../entities/users/teacher.entity"; | ||||
| 
 | ||||
| export class LearningObjectRepository extends DwengoEntityRepository<LearningObject> { | ||||
|     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.
 | ||||
| 
 | ||||
|     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 { LearningObjectIdentifier } from '../../entities/content/learning-object-identifier.js'; | ||||
| import { Student } from '../../entities/users/student.entity.js'; | ||||
| import {LearningObject} from "../../entities/content/learning-object.entity"; | ||||
| 
 | ||||
| export class QuestionRepository extends DwengoEntityRepository<Question> { | ||||
|     public createQuestion(question: { | ||||
|  | @ -42,4 +43,17 @@ export class QuestionRepository extends DwengoEntityRepository<Question> { | |||
|             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"; | ||||
| 
 | ||||
| /** | ||||
|  * Teacher Data Transfer Object | ||||
|  */ | ||||
| export interface TeacherDTO { | ||||
|     id: string; | ||||
|     username: string; | ||||
|     firstName: string; | ||||
|     lastName: string; | ||||
|     endpoints?: { | ||||
|         self: string; | ||||
|         classes: string; | ||||
|         questions: string; | ||||
|         invitations: string; | ||||
|         groups: string; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Maps a Teacher entity to a TeacherDTO | ||||
|  */ | ||||
| export function mapToTeacherDTO(teacher: Teacher): TeacherDTO { | ||||
|     return { | ||||
|         id: teacher.username, | ||||
|         username: teacher.username, | ||||
|         firstName: teacher.firstName, | ||||
|         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 { | ||||
|     getAllLearningObjects, | ||||
|     getLearningObject, | ||||
| } from '../controllers/learningObjects.js'; | ||||
| } from '../controllers/learning-objects.js'; | ||||
| 
 | ||||
| const router = express.Router(); | ||||
| 
 | ||||
|  | @ -1,5 +1,5 @@ | |||
| import express from 'express'; | ||||
| import { getLearningPaths } from '../controllers/learningPaths.js'; | ||||
| import { getLearningPaths } from '../controllers/learning-paths.js'; | ||||
| 
 | ||||
| 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 { ClassDTO, mapToClassDTO } from "../interfaces/classes.js"; | ||||
| import { mapToStudentDTO, StudentDTO } from "../interfaces/students.js"; | ||||
| import { mapToTeacherInvitationDTO, mapToTeacherInvitationDTOIds, TeacherInvitationDTO } from "../interfaces/teacher-invitation.js"; | ||||
| import { getClassRepository } from "../data/repositories"; | ||||
| import { Class } from "../entities/classes/class.entity"; | ||||
| import { ClassDTO, mapToClassDTO } from "../interfaces/class"; | ||||
| import { mapToStudentDTO, StudentDTO } from "../interfaces/student"; | ||||
| 
 | ||||
| export async function getAllClasses(full: boolean): Promise<ClassDTO[] | string[]> { | ||||
|     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 cls = await classRepository.findById(classId); | ||||
| 
 | ||||
|     if (!cls) { | ||||
|     if (!cls) | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     if (full) { | ||||
|         return cls.students.map(mapToStudentDTO); | ||||
|     } else { | ||||
|         return cls.students.map((student) => student.username); | ||||
|     } | ||||
|     return cls.students.map(mapToStudentDTO); | ||||
| } | ||||
| 
 | ||||
| export async function getClassTeacherInvitations(classId: string, full: boolean): Promise<TeacherInvitationDTO[]> { | ||||
|     const classRepository = getClassRepository(); | ||||
|     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 getClassStudents(classId: string): Promise<StudentDTO[]> { | ||||
|     return await fetchClassStudents(classId); | ||||
| } | ||||
| 
 | ||||
| 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 { fetchWithLogging } from '../util/apiHelper.js'; | ||||
| import { fetchWithLogging } from '../util/api-helper.js'; | ||||
| import { | ||||
|     FilteredLearningObject, | ||||
|     LearningObjectMetadata, | ||||
|     LearningObjectNode, | ||||
|     LearningPathResponse, | ||||
| } from '../interfaces/learningPath.js'; | ||||
| import { fetchLearningPaths } from './learningPaths.js'; | ||||
| } from '../interfaces/learning-path.js'; | ||||
| import { fetchLearningPaths } from './learning-paths.js'; | ||||
| 
 | ||||
| function filterData( | ||||
|     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 { | ||||
|     LearningPath, | ||||
|     LearningPathResponse, | ||||
| } from '../interfaces/learningPath.js'; | ||||
| } from '../interfaces/learning-path.js'; | ||||
| 
 | ||||
| export async function fetchLearningPaths( | ||||
|     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 path from 'path'; | ||||
| import yaml from 'js-yaml'; | ||||
| import {FALLBACK_LANG} from "../config.js"; | ||||
| import { FALLBACK_LANG } from "../config.js"; | ||||
| 
 | ||||
| export function loadTranslations<T>(language: string): T { | ||||
|     try { | ||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl