146 lines
4.8 KiB
TypeScript
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}`
|
|
}
|