From 4ff4b09ebd3aafc38c02a3d16d1670a3c77b66e1 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Sat, 14 Jun 2025 11:41:10 +0200 Subject: [PATCH] feat(api): implement event management endpoints for GET, DELETE, and PATCH operations --- src/app/api/event/[eventID]/route.ts | 410 +++++++++++++++++++++++++++ src/app/api/user/me/route.ts | 4 +- 2 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 src/app/api/event/[eventID]/route.ts diff --git a/src/app/api/event/[eventID]/route.ts b/src/app/api/event/[eventID]/route.ts new file mode 100644 index 0000000..159dc13 --- /dev/null +++ b/src/app/api/event/[eventID]/route.ts @@ -0,0 +1,410 @@ +import { prisma } from '@/prisma'; +import { auth } from '@/auth'; +import { NextResponse } from 'next/server'; + +/** + * @swagger + * /api/event/{eventID}: + * get: + * summary: Get details of a specific event + * description: Returns the details of an event by its ID. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event to retrieve. + * responses: + * 200: + * description: Event details retrieved successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * event: + * $ref: '#/components/schemas/Event' + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 404: + * description: User or event not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +export const GET = auth(async (req, { params }) => { + if (!req.auth) + return NextResponse.json( + { success: false, message: 'Not authenticated' }, + { status: 401 }, + ); + if (!req.auth.user || !req.auth.user.id) + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: req.auth.user.id, + }, + }); + + if (!dbUser) { + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + } + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + }, + include: { + organizer: true, + participants: { + include: { + user: true, + }, + }, + }, + }); + + if (!event) { + return NextResponse.json( + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + } + + return NextResponse.json( + { + success: true, + event: { + id: event.id, + title: event.title, + description: event.description, + start_time: event.start_time, + end_time: event.end_time, + status: event.status, + location: event.location, + organizer: { + id: event.organizer.id, + name: event.organizer.name, + }, + participants: event.participants.map((participant) => ({ + id: participant.user.id, + name: participant.user.name, + })), + }, + }, + { status: 200 }, + ); +}); + +/** + * @swagger + * /api/event/{eventID}: + * delete: + * summary: Delete a specific event + * description: Deletes an event by its ID if the user is the organizer. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event to delete. + * responses: + * 200: + * description: Event deleted successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * message: + * type: string + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 403: + * description: User is not the organizer. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: You are not the organizer of this event + * 404: + * description: User or event not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + */ +export const DELETE = auth(async (req, { params }) => { + if (!req.auth) + return NextResponse.json( + { success: false, message: 'Not authenticated' }, + { status: 401 }, + ); + if (!req.auth.user || !req.auth.user.id) + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: req.auth.user.id, + }, + }); + + if (!dbUser) { + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + } + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + }, + }); + + if (!event) { + return NextResponse.json( + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + } + + if (event.organizer_id !== dbUser.id) { + return NextResponse.json( + { success: false, message: 'You are not the organizer of this event' }, + { status: 403 }, + ); + } + + await prisma.meeting.delete({ + where: { + id: eventID, + }, + }); + + return NextResponse.json( + { success: true, message: 'Event deleted successfully' }, + { status: 200 }, + ); +}); + +/** + * @swagger + * /api/event/{eventID}: + * patch: + * summary: Update a specific event + * description: Updates an event by its ID if the user is the organizer. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event to update. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * title: + * type: string + * description: + * type: string + * start_time: + * type: string + * format: date-time + * end_time: + * type: string + * format: date-time + * location: + * type: string + * status: + * type: string + * enum: [TENTATIVE, CONFIRMED, CANCELLED] + * responses: + * 200: + * description: Event updated successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * event: + * $ref: '#/components/schemas/Event' + * 400: + * description: Invalid input data. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 403: + * description: User is not the organizer. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: You are not the organizer of this event + * 404: + * description: User or event not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User or event not found + */ +export const PATCH = auth(async (req, { params }) => { + if (!req.auth) + return NextResponse.json( + { success: false, message: 'Not authenticated' }, + { status: 401 }, + ); + if (!req.auth.user || !req.auth.user.id) + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: req.auth.user.id, + }, + }); + + if (!dbUser) { + return NextResponse.json( + { success: false, message: 'User not found' }, + { status: 404 }, + ); + } + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + }, + }); + + if (!event) { + return NextResponse.json( + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + } + + if (event.organizer_id !== dbUser.id) { + return NextResponse.json( + { success: false, message: 'You are not the organizer of this event' }, + { status: 403 }, + ); + } + + const body = await req.json(); + + const { title, description, start_time, end_time, location, status } = body; + + if (!title && !description && !start_time && !end_time && !location && !status) { + return NextResponse.json( + { success: false, message: 'No fields to update' }, + { status: 400 }, + ); + } + + const updateData: Record = {}; + if (title) updateData.title = title; + if (description) updateData.description = description; + if (start_time) { + const startTimeValidation = new Date(start_time); + if (isNaN(startTimeValidation.getTime())) { + return NextResponse.json( + { success: false, message: 'Invalid start time' }, + { status: 400 }, + ); + } + updateData.start_time = startTimeValidation.getTime().toString(); + } + if (end_time) { + const endTimeValidation = new Date(end_time); + if (isNaN(endTimeValidation.getTime())) { + return NextResponse.json( + { success: false, message: 'Invalid end time' }, + { status: 400 }, + ); + } + updateData.end_time = endTimeValidation.getTime().toString(); + } + if (location) updateData.location = location; + if (status) { + const validStatuses = ['TENTATIVE', 'CONFIRMED', 'CANCELLED']; + if (!validStatuses.includes(status.toUpperCase())) { + return NextResponse.json( + { success: false, message: 'Invalid status' }, + { status: 400 }, + ); + } + updateData.status = status.toUpperCase(); + } + + const updatedEvent = await prisma.meeting.update({ + where: { + id: eventID, + }, + data: updateData, + }); + + return NextResponse.json({ success: true, event: updatedEvent }, { status: 200 }); +}); diff --git a/src/app/api/user/me/route.ts b/src/app/api/user/me/route.ts index f7eb8a4..f5b9935 100644 --- a/src/app/api/user/me/route.ts +++ b/src/app/api/user/me/route.ts @@ -89,7 +89,7 @@ export const GET = auth(async function GET(req) { /** * @swagger * /api/user/me: - * put: + * patch: * description: Update the information of the currently authenticated user. * requestBody: * required: true @@ -163,7 +163,7 @@ export const GET = auth(async function GET(req) { * message: 'No fields to update' * } */ -export const PUT = auth(async function PUT(req) { +export const PATCH = auth(async function PATCH(req) { if (!req.auth) return NextResponse.json( { success: false, message: 'Not authenticated' },