diff --git a/next-swagger-doc.json b/next-swagger-doc.json index 2e142c8..7af9ffe 100644 --- a/next-swagger-doc.json +++ b/next-swagger-doc.json @@ -51,6 +51,15 @@ "name": { "type": "string" } } }, + "Participant": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/SimpleUser" + }, + "status": { "type": "string" } + } + }, "Event": { "type": "object", "properties": { @@ -67,7 +76,7 @@ "participants": { "type": "array", "items": { - "$ref": "#/components/schemas/SimpleUser" + "$ref": "#/components/schemas/Participant" } } } diff --git a/src/app/api/event/[eventID]/participant/[user]/route.ts b/src/app/api/event/[eventID]/participant/[user]/route.ts new file mode 100644 index 0000000..91e818a --- /dev/null +++ b/src/app/api/event/[eventID]/participant/[user]/route.ts @@ -0,0 +1,439 @@ +import { prisma } from '@/prisma'; +import { auth } from '@/auth'; +import { NextResponse } from 'next/server'; + +/** + * @swagger + * /api/event/{eventID}/participant/{user}: + * get: + * summary: Get a specific participant's details in an event + * description: Returns the details of a specific participant in an event. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event. + * - in: path + * name: user + * required: true + * schema: + * type: string + * description: The ID or name of the user. + * responses: + * 200: + * description: Details of the participant. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * participant: + * $ref: '#/components/schemas/Participant' + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 404: + * description: User not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User not found + * 403: + * description: User is not a participant or organizer of this event. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User is not a participant or organizer of this event + */ +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 user = (await params).user; + + const isParticipant = await prisma.meetingParticipant.findFirst({ + where: { + meeting_id: eventID, + user_id: dbUser.id, + }, + }); + + const isOrganizer = await prisma.meeting.findFirst({ + where: { + id: eventID, + organizer_id: dbUser.id, + }, + }); + + if (!isParticipant && !isOrganizer) { + return NextResponse.json( + { + success: false, + message: 'User is not a participant or organizer of this event', + }, + { status: 403 }, + ); + } + + const participant = await prisma.meetingParticipant.findUnique({ + where: { + meeting_id_user_id: { + meeting_id: eventID, + user_id: user, + }, + }, + include: { + user: true, + }, + }); + + if (!participant) { + return NextResponse.json( + { success: false, message: 'Participant not found' }, + { status: 404 }, + ); + } + + return NextResponse.json({ + success: true, + participant: { + user: { + id: participant.user.id, + name: participant.user.name, + }, + status: participant.status, + }, + }); +}); + +/** + * @swagger + * /api/event/{eventID}/participant/{user}: + * delete: + * summary: Remove a participant from an event + * description: Removes a participant from an event. Only the organizer can remove participants. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event. + * - in: path + * name: user + * required: true + * schema: + * type: string + * description: The ID or name of the user to be removed. + * responses: + * 200: + * description: Participant removed 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 + * 404: + * description: User not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User not found + * 403: + * description: Only organizer can remove participants. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Only organizer can remove participants + */ +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 user = (await params).user; + + const isOrganizer = await prisma.meeting.findFirst({ + where: { + id: eventID, + organizer_id: dbUser.id, + }, + }); + + if (!isOrganizer) { + return NextResponse.json( + { success: false, message: 'Only organizer can remove participants' }, + { status: 403 }, + ); + } + + const participant = await prisma.meetingParticipant.findUnique({ + where: { + meeting_id_user_id: { + meeting_id: eventID, + user_id: user, + }, + }, + }); + + if (!participant) { + return NextResponse.json( + { success: false, message: 'Participant not found' }, + { status: 404 }, + ); + } + + await prisma.meetingParticipant.delete({ + where: { + meeting_id_user_id: { + meeting_id: eventID, + user_id: user, + }, + }, + }); + + return NextResponse.json({ + success: true, + message: 'Participant removed successfully', + }); +}); + +/** + * @swagger + * /api/event/{eventID}/participant/{user}: + * patch: + * summary: Update a participant's status in an event + * description: Updates the status of a participant in an event. Only the participant can update their own status. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event. + * - in: path + * name: user + * required: true + * schema: + * type: string + * description: The ID or name of the user whose status is being updated. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * enum: [accepted, declined, tentative] + * description: The new status of the participant. + * responses: + * 200: + * description: Participant status updated successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * participant: + * $ref: '#/components/schemas/Participant' + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 404: + * description: User not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User not found + * 403: + * description: Only participant can update their status. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Only participant can update their status + */ +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 user = (await params).user; + + if (dbUser.id !== user && dbUser.name !== user) { + return NextResponse.json( + { success: false, message: 'You can only update your own participation' }, + { status: 403 }, + ); + } + + const participant = await prisma.meetingParticipant.findUnique({ + where: { + meeting_id_user_id: { + meeting_id: eventID, + user_id: dbUser.id, + }, + }, + }); + + if (!participant) { + return NextResponse.json( + { success: false, message: 'Participant not found' }, + { status: 404 }, + ); + } + + const body = await req.json(); + const { status } = body; + + if (!status) { + return NextResponse.json( + { success: false, message: 'Status is required' }, + { status: 400 }, + ); + } + + if (!['accepted', 'declined', 'tentative'].includes(status)) { + return NextResponse.json( + { success: false, message: 'Invalid status' }, + { status: 400 }, + ); + } + + await prisma.meetingParticipant.update({ + where: { + meeting_id_user_id: { + meeting_id: eventID, + user_id: dbUser.id, + }, + }, + data: { + status: status.toUpperCase(), + }, + }); + + return NextResponse.json({ + success: true, + participant: { + user: { + id: dbUser.id, + name: dbUser.name, + }, + status, + }, + }); +}); diff --git a/src/app/api/event/[eventID]/participant/route.ts b/src/app/api/event/[eventID]/participant/route.ts new file mode 100644 index 0000000..967f377 --- /dev/null +++ b/src/app/api/event/[eventID]/participant/route.ts @@ -0,0 +1,301 @@ +import { prisma } from '@/prisma'; +import { auth } from '@/auth'; +import { NextResponse } from 'next/server'; + +/** + * @swagger + * /api/event/{eventID}/participant: + * get: + * summary: Get participants of an event + * description: Returns all participants of a specific event. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event. + * responses: + * 200: + * description: List of participants for the event. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * participants: + * type: array + * items: + * $ref: '#/components/schemas/Participant' + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 404: + * description: User not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User not found + * 403: + * description: User is not a participant or organizer of this event. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User is not a participant or organizer of this event + */ +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 isParticipant = await prisma.meetingParticipant.findFirst({ + where: { + meeting_id: eventID, + user_id: dbUser.id, + }, + }); + + const isOrganizer = await prisma.meeting.findFirst({ + where: { + id: eventID, + organizer_id: dbUser.id, + }, + }); + + if (!isParticipant && !isOrganizer) { + return NextResponse.json( + { + success: false, + message: 'User is not a participant or organizer of this event', + }, + { status: 403 }, + ); + } + + const participants = await prisma.meetingParticipant.findMany({ + where: { + meeting_id: eventID, + }, + include: { + user: true, + }, + }); + + return NextResponse.json({ + success: true, + participants: participants.map((participant) => ({ + user: { + id: participant.user.id, + name: participant.user.name, + }, + status: participant.status, + })), + }); +}); + +/** + * @swagger + * /api/event/{eventID}/participant: + * post: + * summary: Add a participant to an event + * description: Adds a user as a participant to a specific event. + * parameters: + * - in: path + * name: eventID + * required: true + * schema: + * type: string + * description: The ID of the event. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * userId: + * type: string + * description: The ID of the user to add as a participant. + * responses: + * 200: + * description: Participant added successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * participant: + * $ref: '#/components/schemas/Participant' + * 400: + * description: Bad request, user ID is required. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User ID is required + * 401: + * description: Not authenticated. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Not authenticated + * 404: + * description: User not found. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User not found + * 403: + * description: Only organizers can add participants. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: Only organizers can add participants + * 409: + * description: User is already a participant of this event. + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * example: + * success: false + * message: User is already a participant of this event + */ +export const POST = 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 isOrganizer = await prisma.meeting.findFirst({ + where: { + id: eventID, + organizer_id: dbUser.id, + }, + }); + + if (!isOrganizer) { + return NextResponse.json( + { success: false, message: 'Only organizers can add participants' }, + { status: 403 }, + ); + } + + const body = await req.json(); + const { userId } = body; + + if (!userId) { + return NextResponse.json( + { success: false, message: 'User ID is required' }, + { status: 400 }, + ); + } + + const participantExists = await prisma.meetingParticipant.findFirst({ + where: { + meeting_id: eventID, + user_id: userId, + }, + }); + + if (participantExists) { + return NextResponse.json( + { + success: false, + message: 'User is already a participant of this event', + }, + { status: 409 }, + ); + } + + const newParticipant = await prisma.meetingParticipant.create({ + data: { + meeting_id: eventID, + user_id: userId, + }, + include: { + user: true, + }, + }); + + return NextResponse.json({ + success: true, + participant: { + user: { + id: newParticipant.user.id, + name: newParticipant.user.name, + }, + status: newParticipant.status, + }, + }); +}); diff --git a/src/app/api/event/[eventID]/route.ts b/src/app/api/event/[eventID]/route.ts index 89a4149..81bd863 100644 --- a/src/app/api/event/[eventID]/route.ts +++ b/src/app/api/event/[eventID]/route.ts @@ -107,8 +107,11 @@ export const GET = auth(async (req, { params }) => { name: event.organizer.name, }, participants: event.participants.map((participant) => ({ - id: participant.user.id, - name: participant.user.name, + user: { + id: participant.user.id, + name: participant.user.name, + }, + status: participant.status, })), }, }, @@ -411,10 +414,40 @@ export const PATCH = auth(async (req, { params }) => { id: eventID, }, data: updateData, + include: { + organizer: true, + participants: { + include: { + user: true, + }, + }, + }, }); return NextResponse.json( - { success: true, event: updatedEvent }, + { + success: true, + event: { + id: updatedEvent.id, + title: updatedEvent.title, + description: updatedEvent.description, + start_time: updatedEvent.start_time, + end_time: updatedEvent.end_time, + status: updatedEvent.status, + location: updatedEvent.location, + organizer: { + id: updatedEvent.organizer_id, + name: dbUser.name, + }, + participants: updatedEvent.participants.map((participant) => ({ + user: { + id: participant.user.id, + name: participant.user.name, + }, + status: participant.status, + })), + }, + }, { status: 200 }, ); });