From f5a5704be315979dd8f4a23141207b477c881042 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Fri, 20 Jun 2025 13:30:27 +0200 Subject: [PATCH] feat(api): implement /api/event/[eventID] endpoint --- src/app/api/event/[eventID]/route.ts | 318 +++++++++++++++++++++++++ src/app/api/event/[eventID]/swagger.ts | 94 ++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/app/api/event/[eventID]/route.ts create mode 100644 src/app/api/event/[eventID]/swagger.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..8c06b64 --- /dev/null +++ b/src/app/api/event/[eventID]/route.ts @@ -0,0 +1,318 @@ +import { prisma } from '@/prisma'; +import { auth } from '@/auth'; +import { + returnZodTypeCheckedResponse, + userAuthenticated, +} from '@/lib/apiHelpers'; +import { + ErrorResponseSchema, + SuccessResponseSchema, + ZodErrorResponseSchema, +} from '../../validation'; +import { EventResponseSchema } from '../validation'; +import { updateEventSchema } from '../validation'; + +export const GET = auth(async (req, { params }) => { + const authCheck = userAuthenticated(req); + if (!authCheck.continue) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + authCheck.response, + authCheck.metadata, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: authCheck.user.id, + }, + }); + + if (!dbUser) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + OR: [ + { organizer_id: dbUser.id }, + { participants: { some: { user_id: dbUser.id } } }, + ], + }, + select: { + id: true, + title: true, + description: true, + start_time: true, + end_time: true, + status: true, + location: true, + organizer_id: true, + created_at: true, + updated_at: true, + organizer: { + select: { + id: true, + name: true, + first_name: true, + last_name: true, + image: true, + timezone: true, + }, + }, + participants: { + select: { + user: { + select: { + id: true, + name: true, + first_name: true, + last_name: true, + image: true, + timezone: true, + }, + }, + status: true, + }, + }, + }, + }); + + if (!event) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + + return returnZodTypeCheckedResponse( + EventResponseSchema, + { success: true, event }, + { status: 200 }, + ); +}); + +export const DELETE = auth(async (req, { params }) => { + const authCheck = userAuthenticated(req); + if (!authCheck.continue) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + authCheck.response, + authCheck.metadata, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: authCheck.user.id, + }, + }); + + if (!dbUser) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + OR: [ + { organizer_id: dbUser.id }, + { participants: { some: { user_id: dbUser.id } } }, + ], + }, + }); + + if (!event) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + + if (event.organizer_id !== dbUser.id) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'You are not the organizer of this event' }, + { status: 403 }, + ); + + await prisma.meeting.delete({ + where: { + id: eventID, + }, + }); + + return returnZodTypeCheckedResponse( + SuccessResponseSchema, + { success: true, message: 'Event deleted successfully' }, + { status: 200 }, + ); +}); + +export const PATCH = auth(async (req, { params }) => { + const authCheck = userAuthenticated(req); + if (!authCheck.continue) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + authCheck.response, + authCheck.metadata, + ); + + const dbUser = await prisma.user.findUnique({ + where: { + id: authCheck.user.id, + }, + }); + + if (!dbUser) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'User not found' }, + { status: 404 }, + ); + + const eventID = (await params).eventID; + + const event = await prisma.meeting.findUnique({ + where: { + id: eventID, + OR: [ + { organizer_id: dbUser.id }, + { participants: { some: { user_id: dbUser.id } } }, + ], + }, + }); + + if (!event) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'Event not found' }, + { status: 404 }, + ); + + if (event.organizer_id !== dbUser.id) + return returnZodTypeCheckedResponse( + ErrorResponseSchema, + { success: false, message: 'You are not the organizer of this event' }, + { status: 403 }, + ); + + const dataRaw = await req.json(); + const data = await updateEventSchema.safeParseAsync(dataRaw); + if (!data.success) { + return returnZodTypeCheckedResponse( + ZodErrorResponseSchema, + { + success: false, + message: 'Invalid input data', + errors: data.error.issues, + }, + { status: 400 }, + ); + } + const { + title, + description, + start_time, + end_time, + location, + status, + participants, + } = data.data; + + if (participants !== undefined) + for (const participant of participants) { + await prisma.meetingParticipant.upsert({ + where: { + meeting_id_user_id: { + user_id: participant, + meeting_id: eventID, + }, + }, + create: { + user_id: participant, + meeting_id: eventID, + }, + update: {}, + }); + } + + const updatedEvent = await prisma.meeting.update({ + where: { + id: eventID, + }, + data: { + title, + description, + start_time, + end_time, + location, + status, + participants: + participants !== undefined + ? { + deleteMany: { + user_id: { + notIn: participants || [], + }, + }, + } + : {}, + }, + select: { + id: true, + title: true, + description: true, + start_time: true, + end_time: true, + status: true, + location: true, + organizer_id: true, + created_at: true, + updated_at: true, + organizer: { + select: { + id: true, + name: true, + first_name: true, + last_name: true, + image: true, + timezone: true, + }, + }, + participants: { + select: { + user: { + select: { + id: true, + name: true, + first_name: true, + last_name: true, + image: true, + timezone: true, + }, + }, + status: true, + }, + }, + }, + }); + + return returnZodTypeCheckedResponse( + EventResponseSchema, + { + success: true, + event: updatedEvent, + }, + { status: 200 }, + ); +}); diff --git a/src/app/api/event/[eventID]/swagger.ts b/src/app/api/event/[eventID]/swagger.ts new file mode 100644 index 0000000..4703556 --- /dev/null +++ b/src/app/api/event/[eventID]/swagger.ts @@ -0,0 +1,94 @@ +import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; +import { EventResponseSchema, updateEventSchema } from '../validation'; +import { + invalidRequestDataResponse, + notAuthenticatedResponse, + serverReturnedDataValidationErrorResponse, + userNotFoundResponse, +} from '@/lib/defaultApiResponses'; +import { + EventIdParamSchema, + SuccessResponseSchema, +} from '@/app/api/validation'; +import zod from 'zod/v4'; + +export default function registerSwaggerPaths(registry: OpenAPIRegistry) { + registry.registerPath({ + method: 'get', + path: '/api/event/{eventID}', + request: { + params: zod.object({ + eventID: EventIdParamSchema, + }), + }, + responses: { + 200: { + description: 'Event retrieved successfully', + content: { + 'application/json': { + schema: EventResponseSchema, + }, + }, + }, + ...userNotFoundResponse, + ...serverReturnedDataValidationErrorResponse, + }, + tags: ['Event'], + }); + + registry.registerPath({ + method: 'delete', + path: '/api/event/{eventID}', + request: { + params: zod.object({ + eventID: EventIdParamSchema, + }), + }, + responses: { + 200: { + description: 'Event deleted successfully', + content: { + 'application/json': { + schema: SuccessResponseSchema, + }, + }, + }, + ...notAuthenticatedResponse, + ...userNotFoundResponse, + ...serverReturnedDataValidationErrorResponse, + }, + tags: ['Event'], + }); + + registry.registerPath({ + method: 'patch', + path: '/api/event/{eventID}', + request: { + params: zod.object({ + eventID: EventIdParamSchema, + }), + body: { + content: { + 'application/json': { + schema: updateEventSchema, + }, + }, + }, + }, + responses: { + 200: { + description: 'Event updated successfully', + content: { + 'application/json': { + schema: EventResponseSchema, + }, + }, + }, + ...invalidRequestDataResponse, + ...notAuthenticatedResponse, + ...userNotFoundResponse, + ...serverReturnedDataValidationErrorResponse, + }, + tags: ['Event'], + }); +}