import { NextRequest } from 'next/server'; import { db } from '@/lib/db'; import { auth } from '@/lib/auth'; import { ProjectMemberRole } from '@prisma/client'; import { rateLimit } from '@/lib/rate-limit'; import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response'; type RouteParams = { params: Promise<{ projectId: string }> }; // GET /api/projects/[projectId]/members - List members export async function GET(request: NextRequest, { params }: RouteParams) { try { const session = await auth(); const { projectId } = await params; if (!session?.user?.id) { return apiErrors.unauthorized(); } const project = await db.project.findUnique({ where: { id: projectId }, include: { members: { where: { userId: session.user.id } }, }, }); if (!project) { return apiErrors.notFound('Project'); } const isOwner = project.ownerId === session.user.id; const isMember = project.members.length > 0; if (!isOwner && !isMember) { return apiErrors.forbidden('Access denied'); } const members = await db.projectMember.findMany({ where: { projectId }, include: { user: { select: { id: true, name: true, email: true, image: true } }, }, orderBy: { createdAt: 'asc' }, }); const owner = await db.user.findUnique({ where: { id: project.ownerId }, select: { id: true, name: true, email: true, image: true }, }); const response = successResponse({ members, owner }); return withCacheControl(response, 'private, max-age=60, stale-while-revalidate=120'); } catch (error) { console.error('Error fetching project members:', error); return apiErrors.internalError('Failed to fetch members'); } } // POST /api/projects/[projectId]/members - Invite a member export async function POST(request: NextRequest, { params }: RouteParams) { try { const limited = await rateLimit(request, 'invite-member'); if (limited) return limited; const session = await auth(); const { projectId } = await params; if (!session?.user?.id) { return apiErrors.unauthorized(); } // Check if user is owner or admin const project = await db.project.findUnique({ where: { id: projectId }, include: { members: { where: { userId: session.user.id } } }, }); if (!project) { return apiErrors.notFound('Project'); } const isOwner = project.ownerId === session.user.id; const isAdmin = project.members[0]?.role === ProjectMemberRole.ADMIN; if (!isOwner && !isAdmin) { return apiErrors.forbidden('Only project owners and admins can invite members'); } const body = await request.json(); const { email, role } = body; if (!email || typeof email !== 'string') { return apiErrors.badRequest('Email is required'); } // Validate role const validRoles = ['ADMIN', 'COMMENTATOR']; const memberRole = validRoles.includes(role) ? role : 'COMMENTATOR'; // Find user by email const userToInvite = await db.user.findUnique({ where: { email: email.toLowerCase().trim() }, }); if (!userToInvite) { const response = successResponse({ message: 'If the user exists, an invitation has been sent.' }); return withCacheControl(response, 'private, no-store'); } if (userToInvite.id === project.ownerId) { return apiErrors.badRequest('Cannot invite the project owner as a member'); } // Check if already a member const existingMember = await db.projectMember.findUnique({ where: { projectId_userId: { projectId, userId: userToInvite.id } }, }); if (existingMember) { return apiErrors.conflict('User is already a member of this project'); } const member = await db.projectMember.create({ data: { projectId, userId: userToInvite.id, role: memberRole as ProjectMemberRole, }, include: { user: { select: { id: true, name: true, email: true, image: true } }, }, }); const response = successResponse(member, 201); return withCacheControl(response, 'private, no-store'); } catch (error) { console.error('Error inviting project member:', error); return apiErrors.internalError('Failed to invite member'); } }