import type { AdminDiagnosticsDto, AdminPermissionOptionDto, AdminRoleDto, AdminRoleInput, AdminUserDto, AdminUserInput, AuditEventDto, } from "@mrp/shared"; import fs from "node:fs/promises"; import { env } from "../../config/env.js"; import { paths } from "../../config/paths.js"; import { logAuditEvent } from "../../lib/audit.js"; import { hashPassword } from "../../lib/password.js"; import { prisma } from "../../lib/prisma.js"; import { getLatestStartupReport } from "../../lib/startup-state.js"; function mapAuditEvent(record: { id: string; actorId: string | null; entityType: string; entityId: string | null; action: string; summary: string; metadataJson: string; createdAt: Date; actor: { firstName: string; lastName: string; } | null; }): AuditEventDto { return { id: record.id, actorId: record.actorId, actorName: record.actor ? `${record.actor.firstName} ${record.actor.lastName}`.trim() : null, entityType: record.entityType, entityId: record.entityId, action: record.action, summary: record.summary, metadataJson: record.metadataJson, createdAt: record.createdAt.toISOString(), }; } function mapRole(record: { id: string; name: string; description: string; createdAt: Date; updatedAt: Date; rolePermissions: Array<{ permission: { key: string; }; }>; _count: { userRoles: number; }; }): AdminRoleDto { return { id: record.id, name: record.name, description: record.description, permissionKeys: record.rolePermissions.map((rolePermission) => rolePermission.permission.key).sort(), userCount: record._count.userRoles, createdAt: record.createdAt.toISOString(), updatedAt: record.updatedAt.toISOString(), }; } function mapUser(record: { id: string; email: string; firstName: string; lastName: string; isActive: boolean; createdAt: Date; updatedAt: Date; userRoles: Array<{ role: { id: string; name: string; rolePermissions: Array<{ permission: { key: string; }; }>; }; }>; }): AdminUserDto { const permissionKeys = new Set(); for (const userRole of record.userRoles) { for (const rolePermission of userRole.role.rolePermissions) { permissionKeys.add(rolePermission.permission.key); } } return { id: record.id, email: record.email, firstName: record.firstName, lastName: record.lastName, isActive: record.isActive, roleIds: record.userRoles.map((userRole) => userRole.role.id), roleNames: record.userRoles.map((userRole) => userRole.role.name), permissionKeys: [...permissionKeys].sort(), createdAt: record.createdAt.toISOString(), updatedAt: record.updatedAt.toISOString(), }; } async function validatePermissionKeys(permissionKeys: string[]) { const uniquePermissionKeys = [...new Set(permissionKeys)]; const permissions = await prisma.permission.findMany({ where: { key: { in: uniquePermissionKeys, }, }, select: { id: true, key: true, }, }); if (permissions.length !== uniquePermissionKeys.length) { return { ok: false as const, reason: "One or more selected permissions are invalid." }; } return { ok: true as const, permissions }; } async function validateRoleIds(roleIds: string[]) { const uniqueRoleIds = [...new Set(roleIds)]; const roles = await prisma.role.findMany({ where: { id: { in: uniqueRoleIds, }, }, select: { id: true, name: true, }, }); if (roles.length !== uniqueRoleIds.length) { return { ok: false as const, reason: "One or more selected roles are invalid." }; } return { ok: true as const, roles }; } export async function listAdminPermissions(): Promise { const permissions = await prisma.permission.findMany({ orderBy: [{ key: "asc" }], }); return permissions.map((permission) => ({ key: permission.key, description: permission.description, })); } export async function listAdminRoles(): Promise { const roles = await prisma.role.findMany({ include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, _count: { select: { userRoles: true, }, }, }, orderBy: [{ name: "asc" }], }); return roles.map(mapRole); } export async function createAdminRole(payload: AdminRoleInput, actorId?: string | null) { const validatedPermissions = await validatePermissionKeys(payload.permissionKeys); if (!validatedPermissions.ok) { return { ok: false as const, reason: validatedPermissions.reason }; } const role = await prisma.role.create({ data: { name: payload.name.trim(), description: payload.description, rolePermissions: { create: validatedPermissions.permissions.map((permission) => ({ permissionId: permission.id, })), }, }, include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, _count: { select: { userRoles: true, }, }, }, }); await logAuditEvent({ actorId, entityType: "role", entityId: role.id, action: "created", summary: `Created role ${role.name}.`, metadata: { name: role.name, permissionKeys: role.rolePermissions.map((rolePermission) => rolePermission.permission.key), }, }); return { ok: true as const, role: mapRole(role) }; } export async function updateAdminRole(roleId: string, payload: AdminRoleInput, actorId?: string | null) { const existingRole = await prisma.role.findUnique({ where: { id: roleId }, select: { id: true, name: true }, }); if (!existingRole) { return { ok: false as const, reason: "Role was not found." }; } const validatedPermissions = await validatePermissionKeys(payload.permissionKeys); if (!validatedPermissions.ok) { return { ok: false as const, reason: validatedPermissions.reason }; } const role = await prisma.role.update({ where: { id: roleId }, data: { name: payload.name.trim(), description: payload.description, rolePermissions: { deleteMany: {}, create: validatedPermissions.permissions.map((permission) => ({ permissionId: permission.id, })), }, }, include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, _count: { select: { userRoles: true, }, }, }, }); await logAuditEvent({ actorId, entityType: "role", entityId: role.id, action: "updated", summary: `Updated role ${role.name}.`, metadata: { previousName: existingRole.name, name: role.name, permissionKeys: role.rolePermissions.map((rolePermission) => rolePermission.permission.key), }, }); return { ok: true as const, role: mapRole(role) }; } export async function listAdminUsers(): Promise { const users = await prisma.user.findMany({ include: { userRoles: { include: { role: { include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, }, }, }, }, }, orderBy: [{ firstName: "asc" }, { lastName: "asc" }, { email: "asc" }], }); return users.map(mapUser); } export async function createAdminUser(payload: AdminUserInput, actorId?: string | null) { if (!payload.password || payload.password.trim().length < 8) { return { ok: false as const, reason: "A password with at least 8 characters is required for new users." }; } const validatedRoles = await validateRoleIds(payload.roleIds); if (!validatedRoles.ok) { return { ok: false as const, reason: validatedRoles.reason }; } const user = await prisma.user.create({ data: { email: payload.email.trim().toLowerCase(), firstName: payload.firstName.trim(), lastName: payload.lastName.trim(), isActive: payload.isActive, passwordHash: await hashPassword(payload.password.trim()), userRoles: { create: validatedRoles.roles.map((role) => ({ roleId: role.id, assignedBy: actorId ?? null, })), }, }, include: { userRoles: { include: { role: { include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, }, }, }, }, }, }); await logAuditEvent({ actorId, entityType: "user", entityId: user.id, action: "created", summary: `Created user account for ${user.email}.`, metadata: { email: user.email, isActive: user.isActive, roleNames: user.userRoles.map((userRole) => userRole.role.name), }, }); return { ok: true as const, user: mapUser(user) }; } export async function updateAdminUser(userId: string, payload: AdminUserInput, actorId?: string | null) { const existingUser = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, email: true, }, }); if (!existingUser) { return { ok: false as const, reason: "User was not found." }; } const validatedRoles = await validateRoleIds(payload.roleIds); if (!validatedRoles.ok) { return { ok: false as const, reason: validatedRoles.reason }; } const data = { email: payload.email.trim().toLowerCase(), firstName: payload.firstName.trim(), lastName: payload.lastName.trim(), isActive: payload.isActive, ...(payload.password?.trim() ? { passwordHash: await hashPassword(payload.password.trim()), } : {}), userRoles: { deleteMany: {}, create: validatedRoles.roles.map((role) => ({ roleId: role.id, assignedBy: actorId ?? null, })), }, }; const user = await prisma.user.update({ where: { id: userId }, data, include: { userRoles: { include: { role: { include: { rolePermissions: { include: { permission: { select: { key: true, }, }, }, }, }, }, }, }, }, }); await logAuditEvent({ actorId, entityType: "user", entityId: user.id, action: "updated", summary: `Updated user account for ${user.email}.`, metadata: { previousEmail: existingUser.email, email: user.email, isActive: user.isActive, roleNames: user.userRoles.map((userRole) => userRole.role.name), passwordReset: Boolean(payload.password?.trim()), }, }); return { ok: true as const, user: mapUser(user) }; } export async function getAdminDiagnostics(): Promise { const startupReport = getLatestStartupReport(); const [ companyProfile, userCount, activeUserCount, roleCount, permissionCount, customerCount, vendorCount, inventoryItemCount, warehouseCount, workOrderCount, projectCount, purchaseOrderCount, salesQuoteCount, salesOrderCount, shipmentCount, attachmentCount, auditEventCount, recentAuditEvents, ] = await Promise.all([ prisma.companyProfile.findFirst({ where: { isActive: true }, select: { id: true } }), prisma.user.count(), prisma.user.count({ where: { isActive: true } }), prisma.role.count(), prisma.permission.count(), prisma.customer.count(), prisma.vendor.count(), prisma.inventoryItem.count(), prisma.warehouse.count(), prisma.workOrder.count(), prisma.project.count(), prisma.purchaseOrder.count(), prisma.salesQuote.count(), prisma.salesOrder.count(), prisma.shipment.count(), prisma.fileAttachment.count(), prisma.auditEvent.count(), prisma.auditEvent.findMany({ include: { actor: { select: { firstName: true, lastName: true, }, }, }, orderBy: [{ createdAt: "desc" }], take: 25, }), ]); await Promise.all([fs.access(paths.dataDir), fs.access(paths.uploadsDir)]); return { serverTime: new Date().toISOString(), nodeVersion: process.version, databaseUrl: env.DATABASE_URL, dataDir: paths.dataDir, uploadsDir: paths.uploadsDir, clientOrigin: env.CLIENT_ORIGIN, companyProfilePresent: Boolean(companyProfile), userCount, activeUserCount, roleCount, permissionCount, customerCount, vendorCount, inventoryItemCount, warehouseCount, workOrderCount, projectCount, purchaseOrderCount, salesDocumentCount: salesQuoteCount + salesOrderCount, shipmentCount, attachmentCount, auditEventCount, startupStatus: startupReport.status, startupChecks: startupReport.checks, recentAuditEvents: recentAuditEvents.map(mapAuditEvent), }; }