feat: teacher invitation backend
This commit is contained in:
		
							parent
							
								
									a91e4b2a73
								
							
						
					
					
						commit
						311e76149c
					
				
					 7 changed files with 151 additions and 5 deletions
				
			
		
							
								
								
									
										38
									
								
								backend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								backend/src/controllers/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | import { Request, Response } from 'express'; | ||||||
|  | import {requireFields} from "./error-helper"; | ||||||
|  | import {createInvitation, deleteInvitationFor, getAllInvitations} 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.by === 'true'; | ||||||
|  |     requireFields({ username }); | ||||||
|  | 
 | ||||||
|  |     const invitations = getAllInvitations(username, by); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitations }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 deleteInvitationForHandler(req: Request, res: Response): Promise<void> { | ||||||
|  |     const sender = req.params.sender; | ||||||
|  |     const receiver = req.params.receiver; | ||||||
|  |     const classId = req.params.class; | ||||||
|  |     const accepted = req.body.accepted !== 'false'; | ||||||
|  |     requireFields({ sender, receiver, classId }); | ||||||
|  | 
 | ||||||
|  |     const invitation = deleteInvitationFor(sender, receiver, classId, accepted); | ||||||
|  | 
 | ||||||
|  |     res.json({ invitation }); | ||||||
|  | } | ||||||
|  | @ -20,4 +20,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,6 +2,9 @@ import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity | ||||||
| import { mapToClassDTO } from './class.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"; | ||||||
| 
 | 
 | ||||||
| export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | export function mapToTeacherInvitationDTO(invitation: TeacherInvitation): TeacherInvitationDTO { | ||||||
|     return { |     return { | ||||||
|  | @ -18,3 +21,9 @@ export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): Tea | ||||||
|         class: invitation.class.classId!, |         class: invitation.class.classId!, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function mapToInvitation(sender: Teacher, receiver: Teacher, cls: Class): TeacherInvitation { | ||||||
|  |     return getTeacherInvitationRepository().create({ | ||||||
|  |         sender, receiver, class: cls | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								backend/src/routes/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								backend/src/routes/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | ||||||
|  | import express from "express"; | ||||||
|  | import { | ||||||
|  |     createInvitationHandler, | ||||||
|  |     deleteInvitationForHandler, | ||||||
|  |     getAllInvitationsHandler | ||||||
|  | } from "../controllers/teacher-invitations"; | ||||||
|  | 
 | ||||||
|  | const router = express.Router({ mergeParams: true }); | ||||||
|  | 
 | ||||||
|  | router.get('/:username', getAllInvitationsHandler); | ||||||
|  | 
 | ||||||
|  | router.post('/', createInvitationHandler); | ||||||
|  | 
 | ||||||
|  | router.delete('/:sender/:receiver/:classId', deleteInvitationForHandler); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  |  | ||||||
							
								
								
									
										71
									
								
								backend/src/services/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								backend/src/services/teacher-invitations.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | ||||||
|  | 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 {Teacher} from "../entities/users/teacher.entity"; | ||||||
|  | import {Class} from "../entities/classes/class.entity"; | ||||||
|  | import {NotFoundException} from "../exceptions/not-found-exception"; | ||||||
|  | import {TeacherInvitation} from "../entities/classes/teacher-invitation.entity"; | ||||||
|  | 
 | ||||||
|  | export async function getAllInvitations(username: string, by: boolean): Promise<TeacherInvitationDTO[]> { | ||||||
|  |     const teacher = await fetchTeacher(username); | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  | 
 | ||||||
|  |     let invitations; | ||||||
|  |     if (by) { | ||||||
|  |         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(sender: Teacher, receiver: Teacher, cls: Class): Promise<TeacherInvitation> { | ||||||
|  |     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 deleteInvitationFor(usernameSender: string, usernameReceiver: string, classId: string, accepted: boolean) { | ||||||
|  |     const teacherInvitationRepository = getTeacherInvitationRepository(); | ||||||
|  |     const sender = await fetchTeacher(usernameSender); | ||||||
|  |     const receiver = await fetchTeacher(usernameReceiver); | ||||||
|  | 
 | ||||||
|  |     const cls = await fetchClass(classId); | ||||||
|  | 
 | ||||||
|  |     const invitation = await fetchInvitation(sender, receiver, cls); | ||||||
|  |     await teacherInvitationRepository.deleteBy(cls, sender, receiver); | ||||||
|  | 
 | ||||||
|  |     if (accepted){ | ||||||
|  |         await addClassTeacher(classId, usernameReceiver); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return mapToTeacherInvitationDTO(invitation); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -6,3 +6,9 @@ export interface TeacherInvitationDTO { | ||||||
|     receiver: string | UserDTO; |     receiver: string | UserDTO; | ||||||
|     class: string | ClassDTO; |     class: string | ClassDTO; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export interface TeacherInvitationData { | ||||||
|  |     sender: string; | ||||||
|  |     receiver: string; | ||||||
|  |     class: string; | ||||||
|  | } | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl