import { NextRequest, NextResponse } from "next/server"; import { auth, clerkClient } from "@clerk/nextjs/server"; import { db, users, clients } from "@fitai/database"; import { createUserSchema } from "@/lib/validation/user-schemas"; import { sendWelcomeEmail, sendInvitationEmail } from "@/lib/email"; import { successResponse, errorResponse, unauthorizedResponse, } from "@/lib/api/responses"; import log from "@/lib/logger"; /** * POST /api/users/create * * Create a new user with support for: * - Direct creation (database only, no Clerk account) * - Invitation-based creation (sends Clerk invitation email) * - Client-specific fields (membership type, status) * - Welcome emails for clients */ export async function POST(request: NextRequest) { try { const { userId } = await auth(); if (!userId) { return unauthorizedResponse("Unauthorized"); } const body = await request.json(); const validationResult = createUserSchema.safeParse(body); if (!validationResult.success) { return errorResponse("Validation failed", { status: 400, details: validationResult.error.flatten().fieldErrors as Record< string, string[] >, }); } const data = validationResult.data; // Note: Email is required in current schema // TODO: Add schema migration to make email optional for direct creation if (!data.email) { return errorResponse("Email is required", { status: 400 }); } // Case 1: Send Clerk Invitation (requires email) if (data.sendInvitation && data.email) { try { const client = await clerkClient(); const invitation = await client.invitations.createInvitation({ emailAddress: data.email, publicMetadata: { role: data.role, gymId: data.gymId, }, }); log.info("Clerk invitation sent", { email: data.email, role: data.role, invitationId: invitation.id, }); // Send custom invitation email (in addition to Clerk's) if (invitation.url) { await sendInvitationEmail(data.email, data.role, invitation.url); } return successResponse( { invitation: { id: invitation.id, email: invitation.emailAddress, status: invitation.status, }, }, { status: 200 }, ); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; log.error("Failed to send Clerk invitation", error); return errorResponse(`Failed to send invitation: ${errorMessage}`, { status: 500, }); } } // Case 2: Direct Creation (database only, no Clerk account) // This creates a user record that can later be linked when they sign up // Generate a temporary user ID (will be replaced when they sign up with Clerk) const tempUserId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Insert user into database const [newUser] = await db .insert(users) .values({ id: tempUserId, email: data.email, firstName: data.firstName, lastName: data.lastName, role: data.role, phone: data.phone, gymId: data.gymId, }) .returning(); log.info("User created in database", { userId: newUser.id, role: data.role, hasEmail: !!data.email, }); // Case 3: Create client record if role is client if (data.role === "client" && data.membershipType) { const clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await db.insert(clients).values({ id: clientId, userId: newUser.id, membershipType: data.membershipType, membershipStatus: data.membershipStatus || "active", joinDate: new Date(), }); log.info("Client record created", { userId: newUser.id, membershipType: data.membershipType, }); // Send welcome email if requested and email provided if (data.sendWelcomeEmail && data.email) { await sendWelcomeEmail(data.email, data.firstName); log.info("Welcome email sent", { email: data.email }); } } return successResponse( { user: { id: newUser.id, email: newUser.email, firstName: newUser.firstName, lastName: newUser.lastName, role: newUser.role, }, }, { status: 201 }, ); } catch (error) { log.error("Failed to create user", error); return errorResponse("Failed to create user", { status: 500 }); } }