82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
import { type NextRequest } from "next/server";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { ok, errorResponse, requireRole, parseJson, ApiError } from "@/lib/api";
|
|
import { CreateUserSchema } from "@/lib/schemas";
|
|
import { hashPassword, hashPin } from "@/lib/password";
|
|
import { audit } from "@/lib/audit";
|
|
import { clientIp } from "@/lib/request";
|
|
|
|
export async function GET() {
|
|
try {
|
|
await requireRole("admin");
|
|
const users = await prisma.user.findMany({
|
|
orderBy: [{ role: "asc" }, { name: "asc" }],
|
|
select: {
|
|
id: true,
|
|
role: true,
|
|
name: true,
|
|
email: true,
|
|
active: true,
|
|
lastLoginAt: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
return ok({ users });
|
|
} catch (err) {
|
|
return errorResponse(err);
|
|
}
|
|
}
|
|
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const actor = await requireRole("admin");
|
|
const body = await parseJson(req, CreateUserSchema);
|
|
|
|
if (body.role === "admin") {
|
|
const existing = await prisma.user.findUnique({ where: { email: body.email } });
|
|
if (existing) throw new ApiError(409, "duplicate", "Email already in use");
|
|
const passwordHash = await hashPassword(body.password);
|
|
const created = await prisma.user.create({
|
|
data: { role: "admin", name: body.name, email: body.email, passwordHash },
|
|
select: { id: true, role: true, name: true, email: true, active: true, createdAt: true },
|
|
});
|
|
await audit({
|
|
actorId: actor.id,
|
|
action: "create",
|
|
entity: "User",
|
|
entityId: created.id,
|
|
after: { role: "admin", name: created.name, email: created.email },
|
|
ipAddress: clientIp(req),
|
|
});
|
|
return ok({ user: created }, { status: 201 });
|
|
}
|
|
|
|
// operator: name must be unique (case-insensitive) so login tile grid is unambiguous
|
|
const nameNorm = body.name.trim();
|
|
const dupe = await prisma.user.findFirst({
|
|
where: {
|
|
role: "operator",
|
|
name: { equals: nameNorm },
|
|
},
|
|
});
|
|
if (dupe) throw new ApiError(409, "duplicate", "Operator name already in use");
|
|
|
|
const pinHash = await hashPin(body.pin);
|
|
const created = await prisma.user.create({
|
|
data: { role: "operator", name: nameNorm, pinHash },
|
|
select: { id: true, role: true, name: true, email: true, active: true, createdAt: true },
|
|
});
|
|
await audit({
|
|
actorId: actor.id,
|
|
action: "create",
|
|
entity: "User",
|
|
entityId: created.id,
|
|
after: { role: "operator", name: created.name },
|
|
ipAddress: clientIp(req),
|
|
});
|
|
return ok({ user: created }, { status: 201 });
|
|
} catch (err) {
|
|
return errorResponse(err);
|
|
}
|
|
}
|