import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { db, collections, collectionMembers, users, eq, and } from "@epicure/db"; import { requireSession } from "@/lib/api-auth"; type Params = { params: Promise<{ id: string }> }; // ─── GET /api/v1/collections/[id]/members ──────────────────────────────────── // Owner only — returns members joined with basic user info. export async function GET(_req: NextRequest, { params }: Params) { const { session, response } = await requireSession(); if (response) return response; const { id } = await params; // Verify ownership const col = await db.query.collections.findFirst({ where: and(eq(collections.id, id), eq(collections.userId, session!.user.id)), }); if (!col) return NextResponse.json({ error: "Not found" }, { status: 404 }); const members = await db.query.collectionMembers.findMany({ where: eq(collectionMembers.collectionId, id), with: { user: true }, }); const result = members.map((m) => ({ id: m.id, userId: m.userId, role: m.role, createdAt: m.createdAt, user: { name: m.user.name, username: m.user.username, avatarUrl: m.user.avatarUrl, }, })); return NextResponse.json(result); } // ─── POST /api/v1/collections/[id]/members ─────────────────────────────────── // Owner only — invite by email or userId. const InviteSchema = z .object({ email: z.string().email().optional(), userId: z.string().optional(), role: z.enum(["viewer", "editor"]), }) .refine((d) => d.email !== undefined || d.userId !== undefined, { message: "Provide either email or userId", }); export async function POST(req: NextRequest, { params }: Params) { const { session, response } = await requireSession(); if (response) return response; const { id } = await params; // Verify ownership const col = await db.query.collections.findFirst({ where: and(eq(collections.id, id), eq(collections.userId, session!.user.id)), }); if (!col) return NextResponse.json({ error: "Not found" }, { status: 404 }); const body = await req.json() as unknown; const parsed = InviteSchema.safeParse(body); if (!parsed.success) return NextResponse.json({ error: "Validation error" }, { status: 400 }); const { email, userId, role } = parsed.data; // Resolve target user const targetUser = await db.query.users.findFirst({ where: email ? eq(users.email, email) : eq(users.id, userId!), }); if (!targetUser) return NextResponse.json({ error: "User not found" }, { status: 404 }); // Prevent owner adding themselves if (targetUser.id === session!.user.id) { return NextResponse.json({ error: "Cannot invite yourself" }, { status: 400 }); } // Check not already a member const existing = await db.query.collectionMembers.findFirst({ where: and( eq(collectionMembers.collectionId, id), eq(collectionMembers.userId, targetUser.id), ), }); if (existing) return NextResponse.json({ error: "Already a member" }, { status: 409 }); const memberId = crypto.randomUUID(); await db.insert(collectionMembers).values({ id: memberId, collectionId: id, userId: targetUser.id, role, }); return NextResponse.json({ id: memberId }, { status: 201 }); } // ─── DELETE /api/v1/collections/[id]/members?memberId=… ────────────────────── // Owner OR the member themselves can remove. export async function DELETE(req: NextRequest, { params }: Params) { const { session, response } = await requireSession(); if (response) return response; const { id } = await params; const memberId = req.nextUrl.searchParams.get("memberId"); if (!memberId) return NextResponse.json({ error: "memberId required" }, { status: 400 }); // Load member record const member = await db.query.collectionMembers.findFirst({ where: and(eq(collectionMembers.id, memberId), eq(collectionMembers.collectionId, id)), }); if (!member) return NextResponse.json({ error: "Not found" }, { status: 404 }); // Load collection to check owner const col = await db.query.collections.findFirst({ where: eq(collections.id, id), }); const isOwner = col?.userId === session!.user.id; const isSelf = member.userId === session!.user.id; if (!isOwner && !isSelf) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } await db.delete(collectionMembers).where(eq(collectionMembers.id, memberId)); return new NextResponse(null, { status: 204 }); }