92 lines
3.2 KiB
TypeScript
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}`
|
|
}
|