import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import bcrypt from "bcryptjs"; import { connectDb } from "@/lib/server/db"; import { ACTIVE_BRANCH, Branch } from "@/lib/server/models/Branch"; import { User } from "@/lib/server/models/User"; import { authorize, getAuth } from "@/lib/server/auth"; import { normalizeKenyanPhone } from "@/lib/server/phone"; import { branchLimitError, userLimitError, loadTier } from "@/lib/server/subscription"; import { issueInvite, unusablePasswordHash } from "@/lib/server/invites"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function GET(req: NextRequest) { await connectDb(); const auth = getAuth(req); if (!auth) return NextResponse.json({ error: "Not authenticated" }, { status: 401 }); if (auth.role === "SUPER_ADMIN") { // Deactivated branches are only listed on request (the Branches page), // so every picker elsewhere offers active branches only. const includeInactive = req.nextUrl.searchParams.get("includeInactive") === "1"; const filter = includeInactive ? { businessId: auth.businessId } : { businessId: auth.businessId, ...ACTIVE_BRANCH }; const branches = await Branch.find(filter).sort({ branchName: 1 }).lean(); return NextResponse.json({ branches }); } if (!auth.branchId) return NextResponse.json({ branches: [] }); const branch = await Branch.findOne({ _id: auth.branchId, businessId: auth.businessId }).lean(); return NextResponse.json({ branches: branch ? [branch] : [] }); } const createSchema = z.object({ branchName: z.string().trim().min(1).max(120), locationCity: z.string().trim().min(1).max(120), manager: z .object({ name: z.string().trim().min(1).max(120), email: z.string().email(), phoneNumber: z.string().min(9).max(15), // Omit to invite: the response carries a one-time link for the manager. password: z.string().min(8, "Password must be at least 8 characters").optional(), }) .optional(), }); export async function POST(req: NextRequest) { await connectDb(); const gate = authorize(req, ["SUPER_ADMIN"]); if (!gate.ok) return gate.response; const auth = gate.auth; const json = await req.json().catch(() => null); const parsed = createSchema.safeParse(json); if (!parsed.success) { return NextResponse.json({ error: parsed.error.issues[0]?.message ?? "Invalid request" }, { status: 400 }); } const { branchName, locationCity, manager } = parsed.data; const businessId = auth.businessId; const tier = await loadTier(businessId); const branchErr = await branchLimitError(businessId); if (branchErr) return NextResponse.json({ error: branchErr }, { status: 403 }); let managerPhone: string | null = null; if (manager) { const userErr = await userLimitError(businessId, tier); if (userErr) return NextResponse.json({ error: userErr }, { status: 403 }); try { managerPhone = normalizeKenyanPhone(manager.phoneNumber); } catch (e) { return NextResponse.json({ error: (e as Error).message }, { status: 400 }); } // Unique within this business only (see the User model). const lowerEmail = manager.email.toLowerCase(); const emailTaken = await User.exists({ businessId, email: lowerEmail }); if (emailTaken) { return NextResponse.json({ error: "Someone in your business already uses this email." }, { status: 409 }); } const phoneTaken = await User.exists({ businessId, phoneNumber: managerPhone }); if (phoneTaken) { return NextResponse.json({ error: "Someone in your business already uses this phone number." }, { status: 409 }); } } const branch = await Branch.create({ branchName, locationCity, businessId }); if (manager && managerPhone) { try { const passwordHash = manager.password ? await bcrypt.hash(manager.password, 12) : await unusablePasswordHash(); const managerUser = new User({ businessId, name: manager.name, role: "BRANCH_MANAGER", branchId: branch._id, phoneNumber: managerPhone, email: manager.email.toLowerCase(), passwordHash, }); const inviteUrl = manager.password ? null : issueInvite(managerUser, req); await managerUser.save(); branch.managerId = managerUser._id; await branch.save(); return NextResponse.json( { branch, manager: { id: managerUser._id, name: managerUser.name, email: managerUser.email }, inviteUrl }, { status: 201 } ); } catch (err: any) { await Branch.deleteOne({ _id: branch._id, businessId }).catch(() => {}); if (err?.code === 11000) { return NextResponse.json({ error: "Someone in your business already uses this phone number or email." }, { status: 409 }); } return NextResponse.json({ error: "Could not create the branch manager. Please try again." }, { status: 500 }); } } return NextResponse.json({ branch }, { status: 201 }); }