|
| 1 | +import { NextResponse } from 'next/server'; |
| 2 | +import { prisma } from '@/lib/prisma'; |
| 3 | +import { requirePermission } from '@/lib/authorization'; |
| 4 | +import { createAudit } from '@/services/audit.service'; |
| 5 | + |
| 6 | +/** |
| 7 | + * POST /api/admin/profiles |
| 8 | + * Create a role-specific profile for a user |
| 9 | + * |
| 10 | + * RBAC: admin.users.create |
| 11 | + * Body: { |
| 12 | + * userId: string; |
| 13 | + * role: 'DOCTOR' | 'NURSE' | 'PHARMACIST' | 'LAB_TECH' | 'RECEPTIONIST'; |
| 14 | + * specialization?: string; (DOCTOR only) |
| 15 | + * licenseNumber?: string; (DOCTOR, NURSE, PHARMACIST) |
| 16 | + * department?: string; (NURSE, LAB_TECH) |
| 17 | + * } |
| 18 | + * |
| 19 | + * Returns: The created profile object |
| 20 | + * |
| 21 | + * Edge cases handled: |
| 22 | + * - User not found (404) |
| 23 | + * - Profile already exists (409) |
| 24 | + * - Missing required fields for role (400) |
| 25 | + * - Invalid role (400) |
| 26 | + * - Duplicate license number (409) |
| 27 | + */ |
| 28 | +export async function POST(req: Request) { |
| 29 | + try { |
| 30 | + await requirePermission(req, 'admin.users.create'); |
| 31 | + } catch (err) { |
| 32 | + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); |
| 33 | + } |
| 34 | + |
| 35 | + try { |
| 36 | + const { userId, role, specialization, licenseNumber, department } = await req.json(); |
| 37 | + |
| 38 | + // Validate required fields |
| 39 | + if (!userId) { |
| 40 | + return NextResponse.json({ error: 'userId is required' }, { status: 400 }); |
| 41 | + } |
| 42 | + |
| 43 | + if (!role) { |
| 44 | + return NextResponse.json({ error: 'role is required' }, { status: 400 }); |
| 45 | + } |
| 46 | + |
| 47 | + // Validate role |
| 48 | + const validRoles = ['DOCTOR', 'NURSE', 'PHARMACIST', 'LAB_TECH', 'RECEPTIONIST']; |
| 49 | + if (!validRoles.includes(role)) { |
| 50 | + return NextResponse.json({ error: `Invalid role: ${role}` }, { status: 400 }); |
| 51 | + } |
| 52 | + |
| 53 | + // Check if user exists |
| 54 | + const user = await prisma.user.findUnique({ |
| 55 | + where: { id: userId }, |
| 56 | + select: { id: true, email: true, name: true, role: true } |
| 57 | + }); |
| 58 | + |
| 59 | + if (!user) { |
| 60 | + return NextResponse.json({ error: 'User not found' }, { status: 404 }); |
| 61 | + } |
| 62 | + |
| 63 | + // Handle role-specific profile creation |
| 64 | + let profile: any = null; |
| 65 | + |
| 66 | + switch (role) { |
| 67 | + case 'DOCTOR': { |
| 68 | + // Validate required fields for DOCTOR |
| 69 | + if (!specialization || !specialization.trim()) { |
| 70 | + return NextResponse.json({ error: 'specialization is required for DOCTOR' }, { status: 400 }); |
| 71 | + } |
| 72 | + if (!licenseNumber || !licenseNumber.trim()) { |
| 73 | + return NextResponse.json({ error: 'licenseNumber is required for DOCTOR' }, { status: 400 }); |
| 74 | + } |
| 75 | + |
| 76 | + // Check if license number already exists |
| 77 | + const existingDoctor = await prisma.doctor.findUnique({ |
| 78 | + where: { licenseNumber: licenseNumber.trim() } |
| 79 | + }); |
| 80 | + |
| 81 | + if (existingDoctor) { |
| 82 | + return NextResponse.json({ error: 'License number already exists' }, { status: 409 }); |
| 83 | + } |
| 84 | + |
| 85 | + // Check if doctor profile already exists for this user |
| 86 | + const existingProfile = await prisma.doctor.findUnique({ |
| 87 | + where: { userId } |
| 88 | + }); |
| 89 | + |
| 90 | + if (existingProfile) { |
| 91 | + return NextResponse.json({ error: 'Doctor profile already exists for this user' }, { status: 409 }); |
| 92 | + } |
| 93 | + |
| 94 | + profile = await prisma.doctor.create({ |
| 95 | + data: { |
| 96 | + userId, |
| 97 | + specialization: specialization.trim(), |
| 98 | + licenseNumber: licenseNumber.trim() |
| 99 | + } |
| 100 | + }); |
| 101 | + break; |
| 102 | + } |
| 103 | + |
| 104 | + case 'NURSE': { |
| 105 | + // Validate required fields for NURSE |
| 106 | + if (!licenseNumber || !licenseNumber.trim()) { |
| 107 | + return NextResponse.json({ error: 'licenseNumber is required for NURSE' }, { status: 400 }); |
| 108 | + } |
| 109 | + if (!department || !department.trim()) { |
| 110 | + return NextResponse.json({ error: 'department is required for NURSE' }, { status: 400 }); |
| 111 | + } |
| 112 | + |
| 113 | + // Check if license number already exists |
| 114 | + const existingNurse = await prisma.nurse.findUnique({ |
| 115 | + where: { licenseNumber: licenseNumber.trim() } |
| 116 | + }); |
| 117 | + |
| 118 | + if (existingNurse) { |
| 119 | + return NextResponse.json({ error: 'License number already exists' }, { status: 409 }); |
| 120 | + } |
| 121 | + |
| 122 | + // Check if nurse profile already exists |
| 123 | + const existingProfile = await prisma.nurse.findUnique({ |
| 124 | + where: { userId } |
| 125 | + }); |
| 126 | + |
| 127 | + if (existingProfile) { |
| 128 | + return NextResponse.json({ error: 'Nurse profile already exists for this user' }, { status: 409 }); |
| 129 | + } |
| 130 | + |
| 131 | + profile = await prisma.nurse.create({ |
| 132 | + data: { |
| 133 | + userId, |
| 134 | + licenseNumber: licenseNumber.trim(), |
| 135 | + department: department.trim() |
| 136 | + } |
| 137 | + }); |
| 138 | + break; |
| 139 | + } |
| 140 | + |
| 141 | + case 'PHARMACIST': { |
| 142 | + // Validate required fields for PHARMACIST |
| 143 | + if (!licenseNumber || !licenseNumber.trim()) { |
| 144 | + return NextResponse.json({ error: 'licenseNumber is required for PHARMACIST' }, { status: 400 }); |
| 145 | + } |
| 146 | + |
| 147 | + // Check if license number already exists |
| 148 | + const existingPharmacist = await prisma.pharmacist.findUnique({ |
| 149 | + where: { licenseNumber: licenseNumber.trim() } |
| 150 | + }); |
| 151 | + |
| 152 | + if (existingPharmacist) { |
| 153 | + return NextResponse.json({ error: 'License number already exists' }, { status: 409 }); |
| 154 | + } |
| 155 | + |
| 156 | + // Check if pharmacist profile already exists |
| 157 | + const existingProfile = await prisma.pharmacist.findUnique({ |
| 158 | + where: { userId } |
| 159 | + }); |
| 160 | + |
| 161 | + if (existingProfile) { |
| 162 | + return NextResponse.json({ error: 'Pharmacist profile already exists for this user' }, { status: 409 }); |
| 163 | + } |
| 164 | + |
| 165 | + profile = await prisma.pharmacist.create({ |
| 166 | + data: { |
| 167 | + userId, |
| 168 | + licenseNumber: licenseNumber.trim() |
| 169 | + } |
| 170 | + }); |
| 171 | + break; |
| 172 | + } |
| 173 | + |
| 174 | + case 'LAB_TECH': { |
| 175 | + // Validate required fields for LAB_TECH |
| 176 | + if (!department || !department.trim()) { |
| 177 | + return NextResponse.json({ error: 'department is required for LAB_TECH' }, { status: 400 }); |
| 178 | + } |
| 179 | + |
| 180 | + // Check if lab tech profile already exists |
| 181 | + const existingProfile = await prisma.labTech.findUnique({ |
| 182 | + where: { userId } |
| 183 | + }); |
| 184 | + |
| 185 | + if (existingProfile) { |
| 186 | + return NextResponse.json({ error: 'Lab Tech profile already exists for this user' }, { status: 409 }); |
| 187 | + } |
| 188 | + |
| 189 | + profile = await prisma.labTech.create({ |
| 190 | + data: { |
| 191 | + userId, |
| 192 | + department: department.trim() |
| 193 | + } |
| 194 | + }); |
| 195 | + break; |
| 196 | + } |
| 197 | + |
| 198 | + case 'RECEPTIONIST': { |
| 199 | + // Check if receptionist profile already exists |
| 200 | + const existingProfile = await prisma.receptionist.findUnique({ |
| 201 | + where: { userId } |
| 202 | + }); |
| 203 | + |
| 204 | + if (existingProfile) { |
| 205 | + return NextResponse.json({ error: 'Receptionist profile already exists for this user' }, { status: 409 }); |
| 206 | + } |
| 207 | + |
| 208 | + profile = await prisma.receptionist.create({ |
| 209 | + data: { |
| 210 | + userId |
| 211 | + } |
| 212 | + }); |
| 213 | + break; |
| 214 | + } |
| 215 | + |
| 216 | + default: |
| 217 | + return NextResponse.json({ error: 'Invalid role' }, { status: 400 }); |
| 218 | + } |
| 219 | + |
| 220 | + // Create audit log |
| 221 | + await createAudit({ |
| 222 | + actorId: null, |
| 223 | + action: 'profile.create', |
| 224 | + resource: `${role}_Profile`, |
| 225 | + resourceId: profile.id, |
| 226 | + meta: { userId, role, email: user.email } |
| 227 | + }); |
| 228 | + |
| 229 | + return NextResponse.json(profile, { status: 201 }); |
| 230 | + } catch (err) { |
| 231 | + console.error('[Admin POST /profiles] Error:', err); |
| 232 | + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); |
| 233 | + } |
| 234 | +} |
0 commit comments