104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import { prisma } from '@/lib/prisma'
|
||
import { canAccessMaterialPrivacy, TASK_DETAIL_PRIVACY_SELECT, type TaskDetailPrivacyMaterial } from '@/lib/task-detail-privacy'
|
||
import {
|
||
isSuperAdmin,
|
||
isSystemAdmin,
|
||
} from '@/lib/department-permission'
|
||
|
||
export interface MaterialAuthorizationUser {
|
||
id: string
|
||
role?: string | null
|
||
isSuperAdmin?: boolean | null
|
||
/** 所属部门 ID。未提供时会在需要判定托管权限的场景下按需查库 */
|
||
departmentId?: string | null
|
||
}
|
||
|
||
export interface MaterialAuthorizationTarget extends TaskDetailPrivacyMaterial {
|
||
/** 素材 ID。当调用方未查询 custodyDepartmentId 时,用它补查 */
|
||
id?: string
|
||
userId: string
|
||
/**
|
||
* 素材托管部门。
|
||
* `undefined` 表示调用方没有查这个字段,`null` 表示确认未处于托管状态。
|
||
*/
|
||
custodyDepartmentId?: string | null
|
||
}
|
||
|
||
/**
|
||
* 解析用户所属部门 ID。
|
||
*
|
||
* 优先取 departmentId 外键;部分历史账号只填了部门名称字符串而没有回填
|
||
* departmentId,这里按名称兜底反查,避免这些人无法接手本部门托管的素材。
|
||
*/
|
||
async function resolveUserDepartmentId(userId: string): Promise<string | null> {
|
||
const user = await prisma.user.findUnique({
|
||
where: { id: userId },
|
||
select: { departmentId: true, department: true },
|
||
})
|
||
if (!user) return null
|
||
if (user.departmentId) return user.departmentId
|
||
|
||
const name = user.department?.trim()
|
||
if (!name) return null
|
||
|
||
const department = await prisma.department.findUnique({
|
||
where: { name },
|
||
select: { id: true },
|
||
})
|
||
return department?.id ?? null
|
||
}
|
||
|
||
/**
|
||
* 素材写操作的最小统一授权:
|
||
* - 素材所有者
|
||
* - 素材托管部门的成员(原归属人离职后由该部门接手维护)
|
||
* - 系统管理员(兼容 ADMIN / SYSTEM_ADMIN)
|
||
* - 超级管理员
|
||
*
|
||
* 部门查看权限不能自动升级为素材写权限。
|
||
*/
|
||
export async function canManageMaterial(
|
||
user: MaterialAuthorizationUser,
|
||
material: MaterialAuthorizationTarget,
|
||
): Promise<boolean> {
|
||
let privacyMaterial: TaskDetailPrivacyMaterial = material
|
||
if (material.taskDetailPrivate === undefined && material.id) {
|
||
const stored = await prisma.material.findUnique({
|
||
where: { id: material.id },
|
||
select: TASK_DETAIL_PRIVACY_SELECT,
|
||
})
|
||
if (!stored) return false
|
||
privacyMaterial = stored
|
||
}
|
||
if (!(await canAccessMaterialPrivacy(privacyMaterial, user.id))) return false
|
||
if (
|
||
material.userId === user.id ||
|
||
isSystemAdmin(user.role) ||
|
||
user.isSuperAdmin === true
|
||
) {
|
||
return true
|
||
}
|
||
|
||
// 多数调用方只 select 了 id / userId,没有查托管字段。此处按需补查,
|
||
// 避免因为某个调用点漏 select 就让托管权限静默失效——调用方若已带上
|
||
// custodyDepartmentId(包括显式的 null),这里不会产生任何额外查询。
|
||
let custodyDepartmentId = material.custodyDepartmentId
|
||
if (custodyDepartmentId === undefined && material.id) {
|
||
const row = await prisma.material.findUnique({
|
||
where: { id: material.id },
|
||
select: { custodyDepartmentId: true },
|
||
})
|
||
custodyDepartmentId = row?.custodyDepartmentId ?? null
|
||
}
|
||
|
||
if (custodyDepartmentId) {
|
||
const userDepartmentId =
|
||
user.departmentId ?? (await resolveUserDepartmentId(user.id))
|
||
if (userDepartmentId && userDepartmentId === custodyDepartmentId) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return isSuperAdmin(user.id)
|
||
}
|