gongxiangp2/lib/permission-groups.ts

146 lines
4.8 KiB
TypeScript

import { Prisma } from '@prisma/client'
import { prisma } from './prisma'
import {
findNearestGroupMemberships,
lastOrganizationPathSegment,
LEGACY_DEPARTMENT_ALIASES,
normalizeOrganizationPath,
isUnderOrganizationPath,
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
function packTargetDepartment(slug: string): string | null {
if (slug === 'operations') return '运营中心'
if (slug === 'visual') return '视觉中心'
return null
}
function unitMatchesPack(unitName: string, target: string): boolean {
return unitName === target || LEGACY_DEPARTMENT_ALIASES[unitName] === target
}
function pickCanonicalGroupDepartments(paths: string[]): string[] {
const unique = Array.from(new Set(paths.map(normalizeOrganizationPath).filter(Boolean)))
return unique.filter(
(path) => !unique.some((other) => other !== path && isUnderOrganizationPath(path, other)),
)
}
export async function seedDefaultGroupDepartments() {
try {
const packs = await prisma.materialPermissionPack.findMany({
where: { slug: { in: ['operations', 'visual'] } },
select: {
id: true,
slug: true,
_count: { select: { departments: true } },
},
})
const needsSeed = packs.filter((pack) => pack._count.departments === 0)
if (needsSeed.length === 0) return
const [units, occupiedRows] = await Promise.all([
prisma.organizationUnit.findMany({
where: { isActive: true },
select: { path: true, name: true },
}),
prisma.materialPermissionPackDepartment.findMany({
select: { departmentPath: true },
}),
])
const occupied = new Set(occupiedRows.map((row) => row.departmentPath))
for (const pack of needsSeed) {
const target = packTargetDepartment(pack.slug)
if (!target) continue
const matched = units
.filter((unit) => unitMatchesPack(lastOrganizationPathSegment(unit.path) || unit.name, target))
.map((unit) => unit.path)
const canonical = pickCanonicalGroupDepartments(matched).filter((path) => !occupied.has(path))
if (canonical.length === 0) continue
await prisma.materialPermissionPackDepartment.createMany({
data: canonical.map((departmentPath) => ({
packId: pack.id,
departmentPath,
})),
skipDuplicates: true,
})
for (const path of canonical) occupied.add(path)
}
} catch (error) {
if (isMissingGroupTable(error)) return
throw error
}
}
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: { departmentPath: { in: candidatePaths } },
select: {
departmentPath: true,
pack: {
select: {
items: { select: PACK_ITEM_SELECT },
},
},
},
})
for (const path of normalizedPaths) {
const nearest = findNearestGroupMemberships(path, memberships)
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(path: string, packId: string) {
const existing = await prisma.materialPermissionPackDepartment.findFirst({
where: { departmentPath: path, 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}`
}