import { NextRequest, NextResponse } from "next/server"; import { getDatabase } from "../../../lib/database/index"; import bcrypt from "bcryptjs"; import { auth, clerkClient } from "@clerk/nextjs/server"; export async function GET(request: NextRequest) { try { const db = await getDatabase(); const { searchParams } = new URL(request.url); const role = searchParams.get("role"); let users = await db.getAllUsers(); if (role) { users = users.filter((user) => user.role === role); } const usersWithClients = await Promise.all( users.map(async (user) => { const { password: _, ...userWithoutPassword } = user; const client = await db.getClientByUserId(user.id); // Get active check-in status let isCheckedIn = false; let checkInTime = null; if (client) { const activeCheckIn = await db.getActiveCheckIn(client.id); if (activeCheckIn) { isCheckedIn = true; checkInTime = activeCheckIn.checkInTime; } } return { ...userWithoutPassword, client, isCheckedIn, checkInTime }; }), ); return NextResponse.json({ users: usersWithClients }); } catch (error) { console.error("Get users error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } } export async function POST(request: NextRequest) { try { const { userId: clerkUserId } = await auth(); if (!clerkUserId) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const db = await getDatabase(); // Get current user to check role // Note: In a real app, we'd map Clerk ID to our DB ID. // For now, we'll assume we can find the user by some means or trust the Clerk metadata if we synced it. // Since we don't have Clerk ID in our local DB users table yet (we only have our own ID), // we might need to rely on the user being synced. // Let's assume the user calling this API is already in our DB. // For the prototype, we'll fetch the user by matching the Clerk ID if we stored it, // OR we'll assume the first user is Super Admin if no users exist? // Actually, we should look up the user by email if we can't by ID, or add a clerkId column. // For this step, let's assume we can get the user. // WAIT: The current `users` table has `id` as a string. Is it the Clerk ID? // In `sync-user.ts`, we use `evt.data.id` as the `id` when creating the user. // So yes, `users.id` IS the Clerk ID. const currentUser = await db.getUserById(clerkUserId); if (!currentUser) { return NextResponse.json({ error: "Current user not found in database" }, { status: 403 }); } const body = await request.json(); const { email, firstName, lastName, role, phone } = body; if (!email || !firstName || !lastName || !role) { return NextResponse.json( { error: "Missing required fields" }, { status: 400 }, ); } // Enforce Hierarchy const allowed: Record = { superAdmin: ["admin", "trainer", "client"], admin: ["trainer", "client"], trainer: ["client"], client: [] }; const userRole = currentUser.role as keyof typeof allowed; if (!allowed[userRole] || !allowed[userRole].includes(role)) { return NextResponse.json( { error: `You are not authorized to create a ${role}` }, { status: 403 } ); } // Check if user already exists locally const existingUser = await db.getUserByEmail(email); if (existingUser) { return NextResponse.json( { error: "User with this email already exists" }, { status: 409 }, ); } // Create Clerk Invitation // Note: We pass the role in publicMetadata so it persists when they sign up try { const client = await clerkClient(); await client.invitations.createInvitation({ emailAddress: email, publicMetadata: { role, }, ignoreExisting: true // Don't fail if invite exists }); } catch (clerkError: any) { console.error("Clerk invitation error:", clerkError); // If user already exists in Clerk, we might want to handle it. // But for now, let's proceed to create local record if invite sent or if they exist. if (clerkError.errors?.[0]?.code === 'form_identifier_exists') { return NextResponse.json( { error: "User already exists in Clerk system" }, { status: 409 }, ); } return NextResponse.json( { error: "Failed to send invitation: " + (clerkError.message || "Unknown error") }, { status: 500 }, ); } // Create user in local DB with temporary ID (will be migrated on first login) // We set a placeholder password since it's required by schema but won't be used const newUserId = await db.createUser({ email, password: "INVITED_USER_PENDING", firstName, lastName, role, phone, }); // If creating a client, create the client record too if (role === 'client') { await db.createClient({ userId: newUserId.id, membershipType: 'basic', membershipStatus: 'active', joinDate: new Date() }); } return NextResponse.json({ userId: newUserId.id, message: "Invitation sent" }, { status: 201 }); } catch (error) { console.error("Create user error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } } export async function PUT(request: NextRequest) { try { const db = await getDatabase(); const body = await request.json(); const { id, email, firstName, lastName, role, phone } = body; if (!id) { return NextResponse.json( { error: "User ID is required" }, { status: 400 }, ); } // Get existing user const existingUser = await db.getUserById(id); if (!existingUser) { return NextResponse.json({ error: "User not found" }, { status: 404 }); } // Check if email is being changed and if it's already taken if (email && email !== existingUser.email) { const userWithEmail = await db.getUserByEmail(email); if (userWithEmail) { return NextResponse.json( { error: "Email already in use" }, { status: 409 }, ); } } // Update user await db.updateUser(id, { email: email || existingUser.email, firstName: firstName || existingUser.firstName, lastName: lastName || existingUser.lastName, role: role || existingUser.role, phone: phone !== undefined ? phone : existingUser.phone, }); return NextResponse.json({ success: true }); } catch (error) { console.error("Update user error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } } export async function DELETE(request: NextRequest) { try { const db = await getDatabase(); const { searchParams } = new URL(request.url); const id = searchParams.get("id"); const body = await request.json().catch(() => ({})); const { ids } = body; if (ids && Array.isArray(ids)) { // Bulk delete await Promise.all(ids.map((userId: string) => db.deleteUser(userId))); return NextResponse.json({ success: true, deleted: ids.length }); } else if (id) { // Single delete const user = await db.getUserById(id); if (!user) { return NextResponse.json({ error: "User not found" }, { status: 404 }); } await db.deleteUser(id); return NextResponse.json({ success: true }); } else { return NextResponse.json( { error: "User ID or IDs array required" }, { status: 400 }, ); } } catch (error) { console.error("Delete user error:", error); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } }