feat: question-teacher route
This commit is contained in:
		
							parent
							
								
									4968d7cb07
								
							
						
					
					
						commit
						16b73b9e18
					
				
					 6 changed files with 138 additions and 22 deletions
				
			
		|  | @ -2,15 +2,16 @@ import { Request, Response } from 'express'; | ||||||
| import { | import { | ||||||
|     createTeacher, |     createTeacher, | ||||||
|     deleteTeacher, |     deleteTeacher, | ||||||
|     fetchTeacherByUsername, |     getTeacherByUsername, | ||||||
|     getClassesByTeacher, |     getClassesByTeacher, | ||||||
|     getClassIdsByTeacher, |     getClassIdsByTeacher, | ||||||
|     getAllTeachers, |     getAllTeachers, | ||||||
|     getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher |     getAllTeachersIds, getStudentsByTeacher, getStudentIdsByTeacher, getQuestionsByTeacher, getQuestionIdsByTeacher | ||||||
| } from '../services/teachers.js'; | } from '../services/teachers.js'; | ||||||
| import {TeacherDTO} from "../interfaces/teacher"; | import {TeacherDTO} from "../interfaces/teacher"; | ||||||
| import {ClassDTO} from "../interfaces/class"; | import {ClassDTO} from "../interfaces/class"; | ||||||
| import {StudentDTO} from "../interfaces/student"; | import {StudentDTO} from "../interfaces/student"; | ||||||
|  | import {QuestionDTO, QuestionId} from "../interfaces/question"; | ||||||
| 
 | 
 | ||||||
| export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | export async function getTeacherHandler(req: Request, res: Response): Promise<void> { | ||||||
|     try { |     try { | ||||||
|  | @ -18,7 +19,7 @@ export async function getTeacherHandler(req: Request, res: Response): Promise<vo | ||||||
|         const username = req.query.username as string; |         const username = req.query.username as string; | ||||||
| 
 | 
 | ||||||
|         if (username){ |         if (username){ | ||||||
|             const teacher = await fetchTeacherByUsername(username); |             const teacher = await getTeacherByUsername(username); | ||||||
|             if (!teacher){ |             if (!teacher){ | ||||||
|                 res.status(404).json({ error: `Teacher with username '${username}' not found.` }); |                 res.status(404).json({ error: `Teacher with username '${username}' not found.` }); | ||||||
|                 return; |                 return; | ||||||
|  | @ -128,3 +129,27 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro | ||||||
|         res.status(500).json({ error: 'Internal server 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,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 | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ import { | ||||||
|     createTeacherHandler, |     createTeacherHandler, | ||||||
|     deleteTeacherHandler, |     deleteTeacherHandler, | ||||||
|     getTeacherClassHandler, |     getTeacherClassHandler, | ||||||
|     getTeacherHandler, getTeacherStudentHandler |     getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler | ||||||
| } from "../controllers/teachers.js"; | } from "../controllers/teachers.js"; | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
|  | @ -18,14 +18,7 @@ router.get('/:username/classes', getTeacherClassHandler); | ||||||
| 
 | 
 | ||||||
| router.get('/:username/students', getTeacherStudentHandler); | router.get('/:username/students', getTeacherStudentHandler); | ||||||
| 
 | 
 | ||||||
| // the questions students asked a teacher
 | router.get('/:username/questions', getTeacherQuestionHandler); | ||||||
| router.get('/:id/questions', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         questions: [ |  | ||||||
|             '0' |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| // 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) => { | ||||||
|  | @ -36,14 +29,6 @@ router.get('/:id/invitations', (req, res) => { | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // a list with ids of classes a teacher is in
 |  | ||||||
| router.get('/:id/classes', (req, res) => { |  | ||||||
|     res.json({ |  | ||||||
|         classes: [ |  | ||||||
|             '0' |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default router | export default router | ||||||
|  |  | ||||||
|  | @ -1,9 +1,15 @@ | ||||||
| import {getClassRepository, getTeacherRepository} from "../data/repositories.js"; | import { | ||||||
|  |     getClassRepository, | ||||||
|  |     getLearningObjectRepository, | ||||||
|  |     getQuestionRepository, | ||||||
|  |     getTeacherRepository | ||||||
|  | } from "../data/repositories.js"; | ||||||
| import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; | import {mapToTeacher, mapToTeacherDTO, TeacherDTO} from "../interfaces/teacher.js"; | ||||||
| import { Teacher } from "../entities/users/teacher.entity"; | import { Teacher } from "../entities/users/teacher.entity"; | ||||||
| import {ClassDTO, mapToClassDTO} from "../interfaces/class"; | import {ClassDTO, mapToClassDTO} from "../interfaces/class"; | ||||||
| import {getClassStudents, getClassStudentsIds} from "./class"; | import {getClassStudents, getClassStudentsIds} from "./class"; | ||||||
| import {StudentDTO} from "../interfaces/student"; | import {StudentDTO} from "../interfaces/student"; | ||||||
|  | import {mapToQuestionDTO, QuestionDTO, QuestionId} from "../interfaces/question"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async function fetchAllTeachers(): Promise<TeacherDTO[]> { | async function fetchAllTeachers(): Promise<TeacherDTO[]> { | ||||||
|  | @ -29,7 +35,7 @@ export async function createTeacher(teacherData: TeacherDTO): Promise<Teacher> { | ||||||
|     return newTeacher; |     return newTeacher; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function fetchTeacherByUsername(username: string): Promise<TeacherDTO | null> { | export async function getTeacherByUsername(username: string): Promise<TeacherDTO | null> { | ||||||
|     const teacherRepository = getTeacherRepository(); |     const teacherRepository = getTeacherRepository(); | ||||||
|     const teacher = await teacherRepository.findByUsername(username); |     const teacher = await teacherRepository.findByUsername(username); | ||||||
| 
 | 
 | ||||||
|  | @ -84,5 +90,42 @@ export async function getStudentIdsByTeacher(): Promise<string[]> { | ||||||
|     return await fetchStudentsByTeacher(username).map((student) => student.username); |     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 | ||||||
|  |     })); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl