feat(api): add participant management endpoints and update event response structure

This commit is contained in:
Dominik 2025-06-14 19:09:48 +02:00
parent a0e9aca808
commit ea12d37120
Signed by: dominik
GPG key ID: 06A4003FC5049644
4 changed files with 786 additions and 4 deletions

View file

@ -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"
}
}
}

View file

@ -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,
},
});
});

View file

@ -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,
},
});
});

View file

@ -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 },
);
});