Merge pull request #190 from SELab-2/feat/teacher-invitation
feat: teacher invitations
This commit is contained in:
		
						commit
						ab1bc42619
					
				
					 22 changed files with 486 additions and 43 deletions
				
			
		
							
								
								
									
										66
									
								
								backend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								backend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import { requireFields } from './error-helper'; | ||||||
|  | import { createInvitation, deleteInvitation, getAllInvitations, getInvitation, updateInvitation } from '../services/teacher-invitations'; | ||||||
|  | import { TeacherInvitationData } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
|  | 
 | ||||||
|  | export async function getAllInvitationsHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const username = req.params.username; | ||||||
|  |     const by = req.query.sent === 'true'; | ||||||
|  |     requireFields({ username }); | ||||||
|  | 
 | ||||||
|  |     const invitations = await getAllInvitations(username, by); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitations }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getInvitationHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const sender = req.params.sender; | ||||||
|  |     const receiver = req.params.receiver; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ sender, receiver, classId }); | ||||||
|  | 
 | ||||||
|  |     const invitation = await getInvitation(sender, receiver, classId); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitation }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createInvitationHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const sender = req.body.sender; | ||||||
|  |     const receiver = req.body.receiver; | ||||||
|  |     const classId = req.body.class; | ||||||
|  |     requireFields({ sender, receiver, classId }); | ||||||
|  | 
 | ||||||
|  |     const data = req.body as TeacherInvitationData; | ||||||
|  |     const invitation = await createInvitation(data); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitation }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateInvitationHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const sender = req.body.sender; | ||||||
|  |     const receiver = req.body.receiver; | ||||||
|  |     const classId = req.body.class; | ||||||
|  |     req.body.accepted = req.body.accepted !== 'false'; | ||||||
|  |     requireFields({ sender, receiver, classId }); | ||||||
|  | 
 | ||||||
|  |     const data = req.body as TeacherInvitationData; | ||||||
|  |     const invitation = await updateInvitation(data); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitation }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteInvitationHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const sender = req.params.sender; | ||||||
|  |     const receiver = req.params.receiver; | ||||||
|  |     const classId = req.params.classId; | ||||||
|  |     requireFields({ sender, receiver, classId }); | ||||||
|  | 
 | ||||||
|  |     const data: TeacherInvitationData = { | ||||||
|  |         sender, | ||||||
|  |         receiver, | ||||||
|  |         class: classId, | ||||||
|  |     }; | ||||||
|  |     const invitation = await deleteInvitation(data); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitation }); | ||||||
|  | } | ||||||
|  | @ -2,14 +2,14 @@ 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'; | import { ClassStatus } 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, status: ClassJoinRequestStatus.Open } }); // TODO check if works like this
 |         return this.findAll({ where: { class: clazz, status: ClassStatus.Open } }); // TODO check if works like this
 | ||||||
|     } |     } | ||||||
|     public async findByStudentAndClass(requester: Student, clazz: Class): Promise<ClassJoinRequest | null> { |     public async findByStudentAndClass(requester: Student, clazz: Class): Promise<ClassJoinRequest | null> { | ||||||
|         return this.findOne({ requester, class: clazz }); |         return this.findOne({ requester, class: clazz }); | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import { DwengoEntityRepository } from '../dwengo-entity-repository.js'; | ||||||
| import { Class } from '../../entities/classes/class.entity.js'; | import { Class } from '../../entities/classes/class.entity.js'; | ||||||
| import { TeacherInvitation } from '../../entities/classes/teacher-invitation.entity.js'; | import { TeacherInvitation } from '../../entities/classes/teacher-invitation.entity.js'; | ||||||
| import { Teacher } from '../../entities/users/teacher.entity.js'; | import { Teacher } from '../../entities/users/teacher.entity.js'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherInvitation> { | export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherInvitation> { | ||||||
|     public async findAllInvitationsForClass(clazz: Class): Promise<TeacherInvitation[]> { |     public async findAllInvitationsForClass(clazz: Class): Promise<TeacherInvitation[]> { | ||||||
|  | @ -11,7 +12,7 @@ export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherI | ||||||
|         return this.findAll({ where: { sender: sender } }); |         return this.findAll({ where: { sender: sender } }); | ||||||
|     } |     } | ||||||
|     public async findAllInvitationsFor(receiver: Teacher): Promise<TeacherInvitation[]> { |     public async findAllInvitationsFor(receiver: Teacher): Promise<TeacherInvitation[]> { | ||||||
|         return this.findAll({ where: { receiver: receiver } }); |         return this.findAll({ where: { receiver: receiver, status: ClassStatus.Open } }); | ||||||
|     } |     } | ||||||
|     public async deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise<void> { |     public async deleteBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise<void> { | ||||||
|         return this.deleteWhere({ |         return this.deleteWhere({ | ||||||
|  | @ -20,4 +21,11 @@ export class TeacherInvitationRepository extends DwengoEntityRepository<TeacherI | ||||||
|             class: clazz, |             class: clazz, | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |     public async findBy(clazz: Class, sender: Teacher, receiver: Teacher): Promise<TeacherInvitation | null> { | ||||||
|  |         return this.findOne({ | ||||||
|  |             sender: sender, | ||||||
|  |             receiver: receiver, | ||||||
|  |             class: clazz, | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +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'; | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| @Entity({ | @Entity({ | ||||||
|     repository: () => ClassJoinRequestRepository, |     repository: () => ClassJoinRequestRepository, | ||||||
|  | @ -20,6 +20,6 @@ export class ClassJoinRequest { | ||||||
|     }) |     }) | ||||||
|     class!: Class; |     class!: Class; | ||||||
| 
 | 
 | ||||||
|     @Enum(() => ClassJoinRequestStatus) |     @Enum(() => ClassStatus) | ||||||
|     status!: ClassJoinRequestStatus; |     status!: ClassStatus; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import { Entity, ManyToOne } from '@mikro-orm/core'; | import { Entity, Enum, ManyToOne } from '@mikro-orm/core'; | ||||||
| import { Teacher } from '../users/teacher.entity.js'; | import { Teacher } from '../users/teacher.entity.js'; | ||||||
| import { Class } from './class.entity.js'; | import { Class } from './class.entity.js'; | ||||||
| import { TeacherInvitationRepository } from '../../data/classes/teacher-invitation-repository.js'; | import { TeacherInvitationRepository } from '../../data/classes/teacher-invitation-repository.js'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Invitation of a teacher into a class (in order to teach it). |  * Invitation of a teacher into a class (in order to teach it). | ||||||
|  | @ -25,4 +26,7 @@ export class TeacherInvitation { | ||||||
|         primary: true, |         primary: true, | ||||||
|     }) |     }) | ||||||
|     class!: Class; |     class!: Class; | ||||||
|  | 
 | ||||||
|  |     @Enum(() => ClassStatus) | ||||||
|  |     status!: ClassStatus; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { getClassJoinRequestRepository } from '../data/repositories.js'; | ||||||
| import { Student } from '../entities/users/student.entity.js'; | import { Student } from '../entities/users/student.entity.js'; | ||||||
| import { Class } from '../entities/classes/class.entity.js'; | import { Class } from '../entities/classes/class.entity.js'; | ||||||
| import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | ||||||
| import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export function mapToStudentRequestDTO(request: ClassJoinRequest): ClassJoinRequestDTO { | export function mapToStudentRequestDTO(request: ClassJoinRequest): ClassJoinRequestDTO { | ||||||
|     return { |     return { | ||||||
|  | @ -18,6 +18,6 @@ export function mapToStudentRequest(student: Student, cls: Class): ClassJoinRequ | ||||||
|     return getClassJoinRequestRepository().create({ |     return getClassJoinRequestRepository().create({ | ||||||
|         requester: student, |         requester: student, | ||||||
|         class: cls, |         class: cls, | ||||||
|         status: ClassJoinRequestStatus.Open, |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,17 @@ | ||||||
| import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; | import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity.js'; | ||||||
| import { mapToClassDTO } from './class.js'; |  | ||||||
| import { mapToUserDTO } from './user.js'; | import { mapToUserDTO } from './user.js'; | ||||||
| import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; | import { TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
|  | import { getTeacherInvitationRepository } from '../data/repositories'; | ||||||
|  | import { Teacher } from '../entities/users/teacher.entity'; | ||||||
|  | import { Class } from '../entities/classes/class.entity'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | ||||||
|     return { |     return { | ||||||
|         sender: mapToUserDTO(invitation.sender), |         sender: mapToUserDTO(invitation.sender), | ||||||
|         receiver: mapToUserDTO(invitation.receiver), |         receiver: mapToUserDTO(invitation.receiver), | ||||||
|         class: mapToClassDTO(invitation.class), |         classId: invitation.class.classId!, | ||||||
|  |         status: invitation.status, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -15,6 +19,16 @@ export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): Tea | ||||||
|     return { |     return { | ||||||
|         sender: invitation.sender.username, |         sender: invitation.sender.username, | ||||||
|         receiver: invitation.receiver.username, |         receiver: invitation.receiver.username, | ||||||
|         class: invitation.class.classId!, |         classId: invitation.class.classId!, | ||||||
|  |         status: invitation.status, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function mapToInvitation(sender: Teacher, receiver: Teacher, cls: Class): TeacherInvitation { | ||||||
|  |     return getTeacherInvitationRepository().create({ | ||||||
|  |         sender, | ||||||
|  |         receiver, | ||||||
|  |         class: cls, | ||||||
|  |         status: ClassStatus.Open, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								backend/src/routes/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								backend/src/routes/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | import express from 'express'; | ||||||
|  | import { | ||||||
|  |     createInvitationHandler, | ||||||
|  |     deleteInvitationHandler, | ||||||
|  |     getAllInvitationsHandler, | ||||||
|  |     getInvitationHandler, | ||||||
|  |     updateInvitationHandler, | ||||||
|  | } from '../controllers/teacher-invitations'; | ||||||
|  | 
 | ||||||
|  | const router = express.Router({ mergeParams: true }); | ||||||
|  | 
 | ||||||
|  | router.get('/:username', getAllInvitationsHandler); | ||||||
|  | 
 | ||||||
|  | router.get('/:sender/:receiver/:classId', getInvitationHandler); | ||||||
|  | 
 | ||||||
|  | router.post('/', createInvitationHandler); | ||||||
|  | 
 | ||||||
|  | router.put('/', updateInvitationHandler); | ||||||
|  | 
 | ||||||
|  | router.delete('/:sender/:receiver/:classId', deleteInvitationHandler); | ||||||
|  | 
 | ||||||
|  | export default router; | ||||||
|  | @ -10,6 +10,8 @@ import { | ||||||
|     getTeacherStudentHandler, |     getTeacherStudentHandler, | ||||||
|     updateStudentJoinRequestHandler, |     updateStudentJoinRequestHandler, | ||||||
| } from '../controllers/teachers.js'; | } from '../controllers/teachers.js'; | ||||||
|  | import invitationRouter from './teacher-invitations.js'; | ||||||
|  | 
 | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
| // Root endpoint used to search objects
 | // Root endpoint used to search objects
 | ||||||
|  | @ -32,10 +34,6 @@ router.get('/:username/joinRequests/:classId', getStudentJoinRequestHandler); | ||||||
| router.put('/:username/joinRequests/:classId/:studentUsername', updateStudentJoinRequestHandler); | 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('/invitations', invitationRouter); | ||||||
|     res.json({ |  | ||||||
|         invitations: ['0'], |  | ||||||
|     }); |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| export default router; | export default router; | ||||||
|  |  | ||||||
							
								
								
									
										87
									
								
								backend/src/services/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								backend/src/services/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | ||||||
|  | import { fetchTeacher } from './teachers'; | ||||||
|  | import { getTeacherInvitationRepository } from '../data/repositories'; | ||||||
|  | import { mapToInvitation, mapToTeacherInvitationDTO } from '../interfaces/teacher-invitation'; | ||||||
|  | import { addClassTeacher, fetchClass } from './classes'; | ||||||
|  | import { TeacherInvitationData, TeacherInvitationDTO } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
|  | import { ConflictException } from '../exceptions/conflict-exception'; | ||||||
|  | import { NotFoundException } from '../exceptions/not-found-exception'; | ||||||
|  | import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
|  | 
 | ||||||
|  | export async function getAllInvitations(username: string, sent: boolean): Promise<TeacherInvitationDTO[]> { | ||||||
|  |     const teacher = await fetchTeacher(username); | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  | 
 | ||||||
|  |     let invitations; | ||||||
|  |     if (sent) { | ||||||
|  |         invitations = await teacherInvitationRepository.findAllInvitationsBy(teacher); | ||||||
|  |     } else { | ||||||
|  |         invitations = await teacherInvitationRepository.findAllInvitationsFor(teacher); | ||||||
|  |     } | ||||||
|  |     return invitations.map(mapToTeacherInvitationDTO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function createInvitation(data: TeacherInvitationData): Promise<TeacherInvitationDTO> { | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  |     const sender = await fetchTeacher(data.sender); | ||||||
|  |     const receiver = await fetchTeacher(data.receiver); | ||||||
|  | 
 | ||||||
|  |     const cls = await fetchClass(data.class); | ||||||
|  | 
 | ||||||
|  |     if (!cls.teachers.contains(sender)) { | ||||||
|  |         throw new ConflictException('The teacher sending the invite is not part of the class'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const newInvitation = mapToInvitation(sender, receiver, cls); | ||||||
|  |     await teacherInvitationRepository.save(newInvitation, { preventOverwrite: true }); | ||||||
|  | 
 | ||||||
|  |     return mapToTeacherInvitationDTO(newInvitation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function fetchInvitation(usernameSender: string, usernameReceiver: string, classId: string): Promise<TeacherInvitation> { | ||||||
|  |     const sender = await fetchTeacher(usernameSender); | ||||||
|  |     const receiver = await fetchTeacher(usernameReceiver); | ||||||
|  |     const cls = await fetchClass(classId); | ||||||
|  | 
 | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  |     const invite = await teacherInvitationRepository.findBy(cls, sender, receiver); | ||||||
|  | 
 | ||||||
|  |     if (!invite) { | ||||||
|  |         throw new NotFoundException('Teacher invite not found'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return invite; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getInvitation(sender: string, receiver: string, classId: string): Promise<TeacherInvitationDTO> { | ||||||
|  |     const invitation = await fetchInvitation(sender, receiver, classId); | ||||||
|  |     return mapToTeacherInvitationDTO(invitation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function updateInvitation(data: TeacherInvitationData): Promise<TeacherInvitationDTO> { | ||||||
|  |     const invitation = await fetchInvitation(data.sender, data.receiver, data.class); | ||||||
|  |     invitation.status = ClassStatus.Declined; | ||||||
|  | 
 | ||||||
|  |     if (data.accepted) { | ||||||
|  |         invitation.status = ClassStatus.Accepted; | ||||||
|  |         await addClassTeacher(data.class, data.receiver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  |     await teacherInvitationRepository.save(invitation); | ||||||
|  | 
 | ||||||
|  |     return mapToTeacherInvitationDTO(invitation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function deleteInvitation(data: TeacherInvitationData): Promise<TeacherInvitationDTO> { | ||||||
|  |     const invitation = await fetchInvitation(data.sender, data.receiver, data.class); | ||||||
|  | 
 | ||||||
|  |     const sender = await fetchTeacher(data.sender); | ||||||
|  |     const receiver = await fetchTeacher(data.receiver); | ||||||
|  |     const cls = await fetchClass(data.class); | ||||||
|  | 
 | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  |     await teacherInvitationRepository.deleteBy(cls, sender, receiver); | ||||||
|  | 
 | ||||||
|  |     return mapToTeacherInvitationDTO(invitation); | ||||||
|  | } | ||||||
|  | @ -28,7 +28,7 @@ import { ClassDTO } from '@dwengo-1/common/interfaces/class'; | ||||||
| import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | import { StudentDTO } from '@dwengo-1/common/interfaces/student'; | ||||||
| import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | import { QuestionDTO, QuestionId } from '@dwengo-1/common/interfaces/question'; | ||||||
| import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | import { ClassJoinRequestDTO } from '@dwengo-1/common/interfaces/class-join-request'; | ||||||
| import { ClassJoinRequestStatus } from '@dwengo-1/common/util/class-join-request'; | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| import { ConflictException } from '../exceptions/conflict-exception.js'; | import { ConflictException } from '../exceptions/conflict-exception.js'; | ||||||
| 
 | 
 | ||||||
| export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | export async function getAllTeachers(full: boolean): Promise<TeacherDTO[] | string[]> { | ||||||
|  | @ -160,10 +160,10 @@ export async function updateClassJoinRequestStatus(studentUsername: string, clas | ||||||
|         throw new NotFoundException('Join request not found'); |         throw new NotFoundException('Join request not found'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     request.status = ClassJoinRequestStatus.Declined; |     request.status = ClassStatus.Declined; | ||||||
| 
 | 
 | ||||||
|     if (accepted) { |     if (accepted) { | ||||||
|         request.status = ClassJoinRequestStatus.Accepted; |         request.status = ClassStatus.Accepted; | ||||||
|         await addClassStudent(classId, studentUsername); |         await addClassStudent(classId, studentUsername); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										123
									
								
								backend/tests/controllers/teacher-invitations.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								backend/tests/controllers/teacher-invitations.test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,123 @@ | ||||||
|  | import { beforeAll, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import { setupTestApp } from '../setup-tests.js'; | ||||||
|  | import { | ||||||
|  |     createInvitationHandler, | ||||||
|  |     deleteInvitationHandler, | ||||||
|  |     getAllInvitationsHandler, | ||||||
|  |     getInvitationHandler, | ||||||
|  |     updateInvitationHandler, | ||||||
|  | } from '../../src/controllers/teacher-invitations'; | ||||||
|  | import { TeacherInvitationData } from '@dwengo-1/common/interfaces/teacher-invitation'; | ||||||
|  | import { getClassHandler } from '../../src/controllers/classes'; | ||||||
|  | import { BadRequestException } from '../../src/exceptions/bad-request-exception'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
|  | 
 | ||||||
|  | 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 invitations by', async () => { | ||||||
|  |         req = { params: { username: 'LimpBizkit' }, query: { sent: 'true' } }; | ||||||
|  | 
 | ||||||
|  |         await getAllInvitationsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ invitations: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         // Console.log(result.invitations);
 | ||||||
|  |         expect(result.invitations).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get teacher invitations for', async () => { | ||||||
|  |         req = { params: { username: 'FooFighters' }, query: { by: 'false' } }; | ||||||
|  | 
 | ||||||
|  |         await getAllInvitationsHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ invitations: expect.anything() })); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.invitations).to.have.length.greaterThan(0); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Create and delete invitation', async () => { | ||||||
|  |         const body = { | ||||||
|  |             sender: 'LimpBizkit', | ||||||
|  |             receiver: 'testleerkracht1', | ||||||
|  |             class: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||||
|  |         } as TeacherInvitationData; | ||||||
|  |         req = { body }; | ||||||
|  | 
 | ||||||
|  |         await createInvitationHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         req = { | ||||||
|  |             params: { | ||||||
|  |                 sender: 'LimpBizkit', | ||||||
|  |                 receiver: 'testleerkracht1', | ||||||
|  |                 classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||||
|  |             }, | ||||||
|  |             body: { accepted: 'false' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await deleteInvitationHandler(req as Request, res as Response); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get invitation', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { | ||||||
|  |                 sender: 'LimpBizkit', | ||||||
|  |                 receiver: 'FooFighters', | ||||||
|  |                 classId: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |         await getInvitationHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ invitation: expect.anything() })); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Get invitation error', async () => { | ||||||
|  |         req = { | ||||||
|  |             params: { no: 'no params' }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await expect(async () => getInvitationHandler(req as Request, res as Response)).rejects.toThrowError(BadRequestException); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('Accept invitation', async () => { | ||||||
|  |         const body = { | ||||||
|  |             sender: 'LimpBizkit', | ||||||
|  |             receiver: 'FooFighters', | ||||||
|  |             class: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||||
|  |         } as TeacherInvitationData; | ||||||
|  |         req = { body }; | ||||||
|  | 
 | ||||||
|  |         await updateInvitationHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         const result1 = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result1.invitation.status).toEqual(ClassStatus.Accepted); | ||||||
|  | 
 | ||||||
|  |         req = { | ||||||
|  |             params: { | ||||||
|  |                 id: '34d484a1-295f-4e9f-bfdc-3e7a23d86a89', | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         await getClassHandler(req as Request, res as Response); | ||||||
|  | 
 | ||||||
|  |         const result = jsonMock.mock.lastCall?.[0]; | ||||||
|  |         expect(result.class.teachers).toContain('FooFighters'); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  | @ -13,6 +13,7 @@ import { makeTestAttachments } from './test_assets/content/attachments.testdata. | ||||||
| import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; | import { makeTestQuestions } from './test_assets/questions/questions.testdata.js'; | ||||||
| import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | import { makeTestAnswers } from './test_assets/questions/answers.testdata.js'; | ||||||
| import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | import { makeTestSubmissions } from './test_assets/assignments/submission.testdata.js'; | ||||||
|  | import { Collection } from '@mikro-orm/core'; | ||||||
| 
 | 
 | ||||||
| export async function setupTestApp(): Promise<void> { | export async function setupTestApp(): Promise<void> { | ||||||
|     dotenv.config({ path: '.env.test' }); |     dotenv.config({ path: '.env.test' }); | ||||||
|  | @ -28,8 +29,8 @@ export async function setupTestApp(): Promise<void> { | ||||||
|     const assignments = makeTestAssignemnts(em, classes); |     const assignments = makeTestAssignemnts(em, classes); | ||||||
|     const groups = makeTestGroups(em, students, assignments); |     const groups = makeTestGroups(em, students, assignments); | ||||||
| 
 | 
 | ||||||
|     assignments[0].groups = groups.slice(0, 3); |     assignments[0].groups = new Collection(groups.slice(0, 3)); | ||||||
|     assignments[1].groups = groups.slice(3, 4); |     assignments[1].groups = new Collection(groups.slice(3, 4)); | ||||||
| 
 | 
 | ||||||
|     const teacherInvitations = makeTestTeacherInvitations(em, teachers, classes); |     const teacherInvitations = makeTestTeacherInvitations(em, teachers, classes); | ||||||
|     const classJoinRequests = makeTestClassJoinRequests(em, students, classes); |     const classJoinRequests = makeTestClassJoinRequests(em, students, classes); | ||||||
|  |  | ||||||
|  | @ -2,31 +2,31 @@ import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { ClassJoinRequest } 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'; | import { ClassStatus } 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, { | ||||||
|         requester: students[4], |         requester: students[4], | ||||||
|         class: classes[1], |         class: classes[1], | ||||||
|         status: ClassJoinRequestStatus.Open, |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const classJoinRequest02 = em.create(ClassJoinRequest, { |     const classJoinRequest02 = em.create(ClassJoinRequest, { | ||||||
|         requester: students[2], |         requester: students[2], | ||||||
|         class: classes[1], |         class: classes[1], | ||||||
|         status: ClassJoinRequestStatus.Open, |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const classJoinRequest03 = em.create(ClassJoinRequest, { |     const classJoinRequest03 = em.create(ClassJoinRequest, { | ||||||
|         requester: students[4], |         requester: students[4], | ||||||
|         class: classes[2], |         class: classes[2], | ||||||
|         status: ClassJoinRequestStatus.Open, |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const classJoinRequest04 = em.create(ClassJoinRequest, { |     const classJoinRequest04 = em.create(ClassJoinRequest, { | ||||||
|         requester: students[3], |         requester: students[3], | ||||||
|         class: classes[2], |         class: classes[2], | ||||||
|         status: ClassJoinRequestStatus.Open, |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return [classJoinRequest01, classJoinRequest02, classJoinRequest03, classJoinRequest04]; |     return [classJoinRequest01, classJoinRequest02, classJoinRequest03, classJoinRequest04]; | ||||||
|  |  | ||||||
|  | @ -2,30 +2,35 @@ import { EntityManager } from '@mikro-orm/core'; | ||||||
| import { TeacherInvitation } from '../../../src/entities/classes/teacher-invitation.entity'; | import { TeacherInvitation } from '../../../src/entities/classes/teacher-invitation.entity'; | ||||||
| import { Teacher } from '../../../src/entities/users/teacher.entity'; | import { Teacher } from '../../../src/entities/users/teacher.entity'; | ||||||
| import { Class } from '../../../src/entities/classes/class.entity'; | import { Class } from '../../../src/entities/classes/class.entity'; | ||||||
|  | import { ClassStatus } from '@dwengo-1/common/util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export function makeTestTeacherInvitations(em: EntityManager, teachers: Teacher[], classes: Class[]): TeacherInvitation[] { | export function makeTestTeacherInvitations(em: EntityManager, teachers: Teacher[], classes: Class[]): TeacherInvitation[] { | ||||||
|     const teacherInvitation01 = em.create(TeacherInvitation, { |     const teacherInvitation01 = em.create(TeacherInvitation, { | ||||||
|         sender: teachers[1], |         sender: teachers[1], | ||||||
|         receiver: teachers[0], |         receiver: teachers[0], | ||||||
|         class: classes[1], |         class: classes[1], | ||||||
|  |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const teacherInvitation02 = em.create(TeacherInvitation, { |     const teacherInvitation02 = em.create(TeacherInvitation, { | ||||||
|         sender: teachers[1], |         sender: teachers[1], | ||||||
|         receiver: teachers[2], |         receiver: teachers[2], | ||||||
|         class: classes[1], |         class: classes[1], | ||||||
|  |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const teacherInvitation03 = em.create(TeacherInvitation, { |     const teacherInvitation03 = em.create(TeacherInvitation, { | ||||||
|         sender: teachers[2], |         sender: teachers[2], | ||||||
|         receiver: teachers[0], |         receiver: teachers[0], | ||||||
|         class: classes[2], |         class: classes[2], | ||||||
|  |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const teacherInvitation04 = em.create(TeacherInvitation, { |     const teacherInvitation04 = em.create(TeacherInvitation, { | ||||||
|         sender: teachers[0], |         sender: teachers[0], | ||||||
|         receiver: teachers[1], |         receiver: teachers[1], | ||||||
|         class: classes[0], |         class: classes[0], | ||||||
|  |         status: ClassStatus.Open, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return [teacherInvitation01, teacherInvitation02, teacherInvitation03, teacherInvitation04]; |     return [teacherInvitation01, teacherInvitation02, teacherInvitation03, teacherInvitation04]; | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import { StudentDTO } from './student'; | import { StudentDTO } from './student'; | ||||||
| import { ClassJoinRequestStatus } from '../util/class-join-request'; | import { ClassStatus } from '../util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export interface ClassJoinRequestDTO { | export interface ClassJoinRequestDTO { | ||||||
|     requester: StudentDTO; |     requester: StudentDTO; | ||||||
|     class: string; |     class: string; | ||||||
|     status: ClassJoinRequestStatus; |     status: ClassStatus; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,16 @@ | ||||||
| import { UserDTO } from './user'; | import { UserDTO } from './user'; | ||||||
| import { ClassDTO } from './class'; | import { ClassStatus } from '../util/class-join-request'; | ||||||
| 
 | 
 | ||||||
| export interface TeacherInvitationDTO { | export interface TeacherInvitationDTO { | ||||||
|     sender: string | UserDTO; |     sender: string | UserDTO; | ||||||
|     receiver: string | UserDTO; |     receiver: string | UserDTO; | ||||||
|     class: string | ClassDTO; |     classId: string; | ||||||
|  |     status: ClassStatus; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface TeacherInvitationData { | ||||||
|  |     sender: string; | ||||||
|  |     receiver: string; | ||||||
|  |     class: string; | ||||||
|  |     accepted?: boolean; // Use for put requests, else skip
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| export enum ClassJoinRequestStatus { | export enum ClassStatus { | ||||||
|     Open = 'open', |     Open = 'open', | ||||||
|     Accepted = 'accepted', |     Accepted = 'accepted', | ||||||
|     Declined = 'declined', |     Declined = 'declined', | ||||||
|  |  | ||||||
|  | @ -2,8 +2,8 @@ import { BaseController } from "./base-controller"; | ||||||
| import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | import type { ClassDTO } from "@dwengo-1/common/interfaces/class"; | ||||||
| import type { StudentsResponse } from "./students"; | import type { StudentsResponse } from "./students"; | ||||||
| import type { AssignmentsResponse } from "./assignments"; | import type { AssignmentsResponse } from "./assignments"; | ||||||
| import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; |  | ||||||
| import type { TeachersResponse } from "@/controllers/teachers.ts"; | import type { TeachersResponse } from "@/controllers/teachers.ts"; | ||||||
|  | import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations.ts"; | ||||||
| 
 | 
 | ||||||
| export interface ClassesResponse { | export interface ClassesResponse { | ||||||
|     classes: ClassDTO[] | string[]; |     classes: ClassDTO[] | string[]; | ||||||
|  | @ -13,14 +13,6 @@ export interface ClassResponse { | ||||||
|     class: ClassDTO; |     class: ClassDTO; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface TeacherInvitationsResponse { |  | ||||||
|     invites: TeacherInvitationDTO[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface TeacherInvitationResponse { |  | ||||||
|     invite: TeacherInvitationDTO; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class ClassController extends BaseController { | export class ClassController extends BaseController { | ||||||
|     constructor() { |     constructor() { | ||||||
|         super("class"); |         super("class"); | ||||||
|  |  | ||||||
							
								
								
									
										36
									
								
								frontend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								frontend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | import { BaseController } from "@/controllers/base-controller.ts"; | ||||||
|  | import type { TeacherInvitationData, TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation"; | ||||||
|  | 
 | ||||||
|  | export interface TeacherInvitationsResponse { | ||||||
|  |     invitations: TeacherInvitationDTO[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface TeacherInvitationResponse { | ||||||
|  |     invitation: TeacherInvitationDTO; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TeacherInvitationController extends BaseController { | ||||||
|  |     constructor() { | ||||||
|  |         super("teachers/invitations"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getAll(username: string, sent: boolean): Promise<TeacherInvitationsResponse> { | ||||||
|  |         return this.get<TeacherInvitationsResponse>(`/${username}`, { sent }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getBy(data: TeacherInvitationData): Promise<TeacherInvitationResponse> { | ||||||
|  |         return this.get<TeacherInvitationResponse>(`/${data.sender}/${data.receiver}/${data.class}`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async create(data: TeacherInvitationData): Promise<TeacherInvitationResponse> { | ||||||
|  |         return this.post<TeacherInvitationResponse>("/", data); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async remove(data: TeacherInvitationData): Promise<TeacherInvitationResponse> { | ||||||
|  |         return this.delete<TeacherInvitationResponse>(`/${data.sender}/${data.receiver}/${data.class}`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async respond(data: TeacherInvitationData): Promise<TeacherInvitationResponse> { | ||||||
|  |         return this.put<TeacherInvitationResponse>("/", data); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								frontend/src/queries/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/src/queries/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | ||||||
|  | import { useMutation, useQuery, type UseMutationReturnType, type UseQueryReturnType } from "@tanstack/vue-query"; | ||||||
|  | import { computed, toValue } from "vue"; | ||||||
|  | import type { MaybeRefOrGetter } from "vue"; | ||||||
|  | import { | ||||||
|  |     TeacherInvitationController, | ||||||
|  |     type TeacherInvitationResponse, | ||||||
|  |     type TeacherInvitationsResponse, | ||||||
|  | } from "@/controllers/teacher-invitations.ts"; | ||||||
|  | import type { TeacherInvitationData } from "@dwengo-1/common/interfaces/teacher-invitation"; | ||||||
|  | import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher"; | ||||||
|  | 
 | ||||||
|  | const controller = new TeacherInvitationController(); | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |     All the invitations the teacher sent | ||||||
|  | **/ | ||||||
|  | export function useTeacherInvitationsSentQuery( | ||||||
|  |     username: MaybeRefOrGetter<string | undefined>, | ||||||
|  | ): UseQueryReturnType<TeacherInvitationsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryFn: computed(async () => controller.getAll(toValue(username), true)), | ||||||
|  |         enabled: () => Boolean(toValue(username)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |     All the pending invitations sent to this teacher | ||||||
|  |  */ | ||||||
|  | export function useTeacherInvitationsReceivedQuery( | ||||||
|  |     username: MaybeRefOrGetter<string | undefined>, | ||||||
|  | ): UseQueryReturnType<TeacherInvitationsResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryFn: computed(async () => controller.getAll(toValue(username), false)), | ||||||
|  |         enabled: () => Boolean(toValue(username)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useTeacherInvitationQuery( | ||||||
|  |     data: MaybeRefOrGetter<TeacherInvitationData | undefined>, | ||||||
|  | ): UseQueryReturnType<TeacherInvitationResponse, Error> { | ||||||
|  |     return useQuery({ | ||||||
|  |         queryFn: computed(async () => controller.getBy(toValue(data))), | ||||||
|  |         enabled: () => Boolean(toValue(data)), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useCreateTeacherInvitationMutation(): UseMutationReturnType< | ||||||
|  |     TeacherInvitationResponse, | ||||||
|  |     Error, | ||||||
|  |     TeacherDTO, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data: TeacherInvitationData) => controller.create(data), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useRespondTeacherInvitationMutation(): UseMutationReturnType< | ||||||
|  |     TeacherInvitationResponse, | ||||||
|  |     Error, | ||||||
|  |     TeacherDTO, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data: TeacherInvitationData) => controller.respond(data), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function useDeleteTeacherInvitationMutation(): UseMutationReturnType< | ||||||
|  |     TeacherInvitationResponse, | ||||||
|  |     Error, | ||||||
|  |     TeacherDTO, | ||||||
|  |     unknown | ||||||
|  | > { | ||||||
|  |     return useMutation({ | ||||||
|  |         mutationFn: async (data: TeacherInvitationData) => controller.remove(data), | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | @ -276,10 +276,11 @@ | ||||||
|                 <tbody> |                 <tbody> | ||||||
|                     <tr |                     <tr | ||||||
|                         v-for="i in invitations" |                         v-for="i in invitations" | ||||||
|                         :key="(i.class as ClassDTO).id" |                         :key="i.classId" | ||||||
|                     > |                     > | ||||||
|                         <td> |                         <td> | ||||||
|                             {{ (i.class as ClassDTO).displayName }} |                             {{ i.classId }} | ||||||
|  |                             <!-- TODO fetch display name via classId because db only returns classId field --> | ||||||
|                         </td> |                         </td> | ||||||
|                         <td>{{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }}</td> |                         <td>{{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }}</td> | ||||||
|                         <td class="text-right"> |                         <td class="text-right"> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl