gongxiangp2/lib/material-authorization.ts

104 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}