flnxi-Wu/lib/material-permissions.ts
2026-05-28 23:27:15 +08:00

135 lines
3.3 KiB
TypeScript

import type { Prisma } from '@prisma/client'
import { prisma } from '@/lib/prisma'
import { buildDepartmentDescendants, isDepartmentWithinScope } from '@/lib/departments'
export type MaterialPermissionActor = {
id: string
role: string
isSuperAdmin: boolean
department?: string | null
managedDepartment?: string | null
}
export type MaterialPermissionTarget = {
userId?: string | null
createdByUserId?: string | null
ownerType?: string | null
ownerId?: string | null
user?: {
id?: string | null
department?: string | null
} | null
}
const normalizeDepartment = (department?: string | null) => (department || '').trim()
export async function getMaterialPermissionActor(session: unknown): Promise<MaterialPermissionActor | null> {
const userId = (session as any)?.user?.id
if (!userId) {
return null
}
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
role: true,
isSuperAdmin: true,
department: true,
managedDepartment: true,
},
})
if (!user) {
return null
}
return {
id: user.id,
role: user.role,
isSuperAdmin: Boolean(user.isSuperAdmin),
department: user.department,
managedDepartment: user.managedDepartment,
}
}
export function isMaterialOwnedByActor(material: MaterialPermissionTarget, actor: MaterialPermissionActor | null) {
if (!actor) {
return false
}
return (
material.userId === actor.id ||
(material.ownerType === 'USER' && material.ownerId === actor.id)
)
}
export async function canManageMaterial(
material: MaterialPermissionTarget,
actor: MaterialPermissionActor | null
) {
if (!actor) {
return false
}
if (actor.isSuperAdmin) {
return true
}
if (isMaterialOwnedByActor(material, actor)) {
return true
}
if (actor.role !== 'ADMIN') {
return false
}
const scopeDepartment = normalizeDepartment(actor.managedDepartment)
const targetDepartment = normalizeDepartment(material.user?.department)
if (!scopeDepartment || !targetDepartment) {
return false
}
if (targetDepartment === scopeDepartment || targetDepartment.startsWith(`${scopeDepartment}-`)) {
return true
}
const departments = await prisma.department.findMany({ select: { name: true } })
const descendantMap = buildDepartmentDescendants(departments)
return isDepartmentWithinScope(scopeDepartment, targetDepartment, descendantMap)
}
export async function buildMaterialManagementScopeWhere(
actor: MaterialPermissionActor | null
): Promise<Prisma.MaterialWhereInput> {
if (!actor) {
return { id: { in: [] } }
}
if (actor.isSuperAdmin) {
return {}
}
const scopeDepartment = normalizeDepartment(actor.managedDepartment)
const ownWhere: Prisma.MaterialWhereInput[] = [
{ userId: actor.id },
{ ownerType: 'USER', ownerId: actor.id },
]
if (actor.role !== 'ADMIN' || !scopeDepartment) {
return { OR: ownWhere }
}
const departments = await prisma.department.findMany({ select: { name: true } })
const descendants = buildDepartmentDescendants(departments).get(scopeDepartment)
const scopedDepartments = Array.from(descendants || new Set([scopeDepartment]))
return {
OR: [
...ownWhere,
{ user: { department: { in: scopedDepartments } } },
{ user: { department: { startsWith: `${scopeDepartment}-` } } },
],
}
}