diff --git a/backend/src/controllers/teacher-invitations.ts b/backend/src/controllers/teacher-invitations.ts new file mode 100644 index 00000000..3292b7bf --- /dev/null +++ b/backend/src/controllers/teacher-invitations.ts @@ -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 { + 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 { + 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 { + 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 }); +} diff --git a/backend/src/data/classes/teacher-invitation-repository.ts b/backend/src/data/classes/teacher-invitation-repository.ts index ce059ca8..5461d29b 100644 --- a/backend/src/data/classes/teacher-invitation-repository.ts +++ b/backend/src/data/classes/teacher-invitation-repository.ts @@ -20,4 +20,11 @@ export class TeacherInvitationRepository extends DwengoEntityRepository { + return this.findOne({ + sender: sender, + receiver: receiver, + class: clazz, + }) + } } diff --git a/backend/src/interfaces/teacher-invitation.ts b/backend/src/interfaces/teacher-invitation.ts index d9cb9915..98189938 100644 --- a/backend/src/interfaces/teacher-invitation.ts +++ b/backend/src/interfaces/teacher-invitation.ts @@ -2,6 +2,9 @@ import { TeacherInvitation } from '../entities/classes/teacher-invitation.entity import { mapToClassDTO } from './class.js'; import { mapToUserDTO } from './user.js'; 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 { return { @@ -18,3 +21,9 @@ export function mapToTeacherInvitationDTOIds(invitation: TeacherInvitation): Tea class: invitation.class.classId!, }; } + +export function mapToInvitation(sender: Teacher, receiver: Teacher, cls: Class): TeacherInvitation { + return getTeacherInvitationRepository().create({ + sender, receiver, class: cls + }); +} diff --git a/backend/src/routes/teacher-invitations.ts b/backend/src/routes/teacher-invitations.ts new file mode 100644 index 00000000..01a18195 --- /dev/null +++ b/backend/src/routes/teacher-invitations.ts @@ -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; diff --git a/backend/src/routes/teachers.ts b/backend/src/routes/teachers.ts index a6106a80..801eaee8 100644 --- a/backend/src/routes/teachers.ts +++ b/backend/src/routes/teachers.ts @@ -10,6 +10,8 @@ import { getTeacherStudentHandler, updateStudentJoinRequestHandler, } from '../controllers/teachers.js'; +import invitationRouter from './teacher-invitations.js'; + const router = express.Router(); // Root endpoint used to search objects @@ -32,10 +34,6 @@ router.get('/:username/joinRequests/:classId', getStudentJoinRequestHandler); router.put('/:username/joinRequests/:classId/:studentUsername', updateStudentJoinRequestHandler); // Invitations to other classes a teacher received -router.get('/:id/invitations', (_req, res) => { - res.json({ - invitations: ['0'], - }); -}); +router.get('/invitations', invitationRouter); export default router; diff --git a/backend/src/services/teacher-invitations.ts b/backend/src/services/teacher-invitations.ts new file mode 100644 index 00000000..1b9ef179 --- /dev/null +++ b/backend/src/services/teacher-invitations.ts @@ -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 { + 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 { + 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 { + 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); +} + + + diff --git a/common/src/interfaces/teacher-invitation.ts b/common/src/interfaces/teacher-invitation.ts index 13709322..c61f9a6a 100644 --- a/common/src/interfaces/teacher-invitation.ts +++ b/common/src/interfaces/teacher-invitation.ts @@ -6,3 +6,9 @@ export interface TeacherInvitationDTO { receiver: string | UserDTO; class: string | ClassDTO; } + +export interface TeacherInvitationData { + sender: string; + receiver: string; + class: string; +}