Skip to content

Commit 2f3f0eb

Browse files
committed
user profile creation
1 parent e6c19dd commit 2f3f0eb

3 files changed

Lines changed: 631 additions & 82 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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+
}

src/app/api/admin/users/route.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,55 @@ export async function POST(req: Request) {
2323
const { email, password, name, role, roleEntityId } = await req.json();
2424
if (!email || !password) return NextResponse.json({ error: 'email & password required' }, { status: 400 });
2525
const hashed = await bcrypt.hash(password, 10);
26-
const user = await prisma.user.create({ data: { email, password: hashed, name: name ?? '', role: role ?? 'RECEPTIONIST', roleEntityId } });
27-
await createAudit({ actorId: null, action: 'user.create', resource: 'User', resourceId: user.id, meta: { email, role, roleEntityId } });
26+
27+
// Determine final role and roleEntityId
28+
let finalRole = role;
29+
let finalRoleEntityId = roleEntityId;
30+
31+
// If roleEntityId is provided but role is not, look up the role name
32+
if (finalRoleEntityId && !finalRole) {
33+
const roleEntity = await prisma.roleEntity.findUnique({
34+
where: { id: finalRoleEntityId },
35+
select: { name: true }
36+
});
37+
if (roleEntity) {
38+
finalRole = roleEntity.name;
39+
}
40+
}
41+
42+
// If role is provided but roleEntityId is not, look up the roleEntityId
43+
if (!finalRoleEntityId && finalRole) {
44+
const roleEntity = await prisma.roleEntity.findUnique({
45+
where: { name: finalRole },
46+
select: { id: true }
47+
});
48+
if (roleEntity) {
49+
finalRoleEntityId = roleEntity.id;
50+
}
51+
}
52+
53+
// Default to RECEPTIONIST if no role is specified
54+
if (!finalRole) {
55+
finalRole = 'RECEPTIONIST';
56+
}
57+
58+
const user = await prisma.user.create({
59+
data: {
60+
email,
61+
password: hashed,
62+
name: name ?? '',
63+
role: finalRole,
64+
roleEntityId: finalRoleEntityId
65+
}
66+
});
67+
68+
await createAudit({
69+
actorId: null,
70+
action: 'user.create',
71+
resource: 'User',
72+
resourceId: user.id,
73+
meta: { email, role: finalRole, roleEntityId: finalRoleEntityId }
74+
});
75+
2876
return NextResponse.json({ id: user.id, email: user.email });
2977
}

0 commit comments

Comments
 (0)