gongxiangp2/lib/permission-groups.ts

92 lines
3.2 KiB
TypeScript

import { Prisma } from '@prisma/client'
import { prisma } from './prisma'
import {
findNearestGroupMemberships,
normalizeOrganizationPath,
organizationPathChain,
unionPermissionPacks,
type PermissionPackMap,
} from './organization-material-permissions'
import { packItemsToMap } from './user-permission-inherit'
function isMissingGroupTable(error: unknown) {
return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2021'
}
const PACK_ITEM_SELECT = {
materialType: true,
canView: true,
canDownload: true,
canDownloadSource: true,
canShare: true,
canEditAssets: true,
canAccessPrivate: true,
} as const
/** Shared by both admin views. Historical snapshots never grant access. */
export async function loadPermissionGroupDepartments() {
return prisma.materialPermissionPackDepartment.findMany({
orderBy: { createdAt: 'asc' },
select: {
id: true, packId: true, departmentPath: true, organizationUnitId: true,
organizationUnit: { select: { id: true, path: true, name: true, isActive: true } },
},
})
}
export async function loadDepartmentGroupPack(path: string): Promise<PermissionPackMap | null> {
const packs = await loadDepartmentGroupPacks([path])
return packs.get(normalizeOrganizationPath(path)) ?? null
}
export async function loadDepartmentGroupPacks(paths: string[]): Promise<Map<string, PermissionPackMap>> {
const normalizedPaths = Array.from(new Set(paths.map(normalizeOrganizationPath).filter(Boolean)))
const chains = new Map(normalizedPaths.map((path) => [path, organizationPathChain(path)]))
const candidatePaths = Array.from(new Set(Array.from(chains.values()).flat()))
const result = new Map<string, PermissionPackMap>()
if (candidatePaths.length === 0) return result
try {
const memberships = await prisma.materialPermissionPackDepartment.findMany({
where: { organizationUnit: { isActive: true, path: { in: candidatePaths } } },
select: {
organizationUnit: { select: { path: true, isActive: true } },
pack: {
select: {
items: { select: PACK_ITEM_SELECT },
},
},
},
})
const currentMemberships = memberships.flatMap((row) => row.organizationUnit?.isActive
? [{ ...row, departmentPath: row.organizationUnit.path }] : [])
for (const path of normalizedPaths) {
const nearest = findNearestGroupMemberships(path, currentMemberships)
if (!nearest) continue
result.set(path, unionPermissionPacks(...nearest.items.map((row) => packItemsToMap(row.pack.items))))
}
return result
} catch (error) {
if (isMissingGroupTable(error)) return result
throw error
}
}
export async function findConflictingDepartmentGroup(organizationUnitId: string, packId: string) {
const existing = await prisma.materialPermissionPackDepartment.findFirst({
where: { organizationUnitId, NOT: { packId } },
select: { pack: { select: { slug: true, name: true } } },
})
return existing?.pack ?? null
}
export function makePermissionGroupSlug(name: string): string {
const ascii = name
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
const suffix = Date.now().toString(36)
return ascii ? `${ascii}-${suffix}` : `group-${suffix}`
}