Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import type { AuthUser } from './decorators/current-user.decorator';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export type DataScope = 'ALL' | 'DEPT' | 'SELF';
|
||||
|
||||
const RANK: Record<DataScope, number> = { SELF: 0, DEPT: 1, ALL: 2 };
|
||||
|
||||
export function mergeDataScope(scopes: string[]): DataScope {
|
||||
let best: DataScope = 'SELF';
|
||||
for (const s of scopes) {
|
||||
const v = s as DataScope;
|
||||
if (RANK[v] > RANK[best]) best = v;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export function effectiveScope(user: AuthUser): DataScope {
|
||||
if (user.roles.includes('admin') || user.roles.includes('owner')) return 'ALL';
|
||||
return user.dataScope || 'SELF';
|
||||
}
|
||||
|
||||
export async function descendantDeptIds(prisma: PrismaService, rootId?: string | null) {
|
||||
if (!rootId) return [] as string[];
|
||||
const all = await prisma.department.findMany({
|
||||
where: { tenantId: '1' },
|
||||
select: { id: true, parentId: true },
|
||||
});
|
||||
const ids = new Set<string>([rootId]);
|
||||
let grew = true;
|
||||
while (grew) {
|
||||
grew = false;
|
||||
for (const d of all) {
|
||||
if (d.parentId && ids.has(d.parentId) && !ids.has(d.id)) {
|
||||
ids.add(d.id);
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...ids];
|
||||
}
|
||||
|
||||
function empInScope(user: AuthUser, deptIds: string[]): Prisma.EmployeeWhereInput {
|
||||
return {
|
||||
OR: [
|
||||
{ departmentId: { in: deptIds } },
|
||||
...(user.employeeId
|
||||
? [{ managerId: user.employeeId }, { managerIds: { contains: user.employeeId } }]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** 本人 / 本部门及下级 / 全公司 */
|
||||
export async function ownerScope(
|
||||
prisma: PrismaService,
|
||||
user: AuthUser,
|
||||
owner: 'createdById' | 'applicantId',
|
||||
): Promise<Prisma.BidCaseWhereInput | Prisma.ExpenseClaimWhereInput | Record<string, never>> {
|
||||
const scope = effectiveScope(user);
|
||||
if (scope === 'ALL') return {};
|
||||
if (scope === 'SELF' || !user.departmentId) return { [owner]: user.id };
|
||||
const deptIds = await descendantDeptIds(prisma, user.departmentId);
|
||||
const rel = owner === 'createdById' ? 'createdBy' : 'applicant';
|
||||
return {
|
||||
OR: [{ [owner]: user.id }, { [rel]: { employee: empInScope(user, deptIds) } }],
|
||||
};
|
||||
}
|
||||
|
||||
export function withAnd<T extends { AND?: unknown }>(where: T, extra: Record<string, unknown>): T {
|
||||
if (!extra || !Object.keys(extra).length) return where;
|
||||
const prev = where.AND;
|
||||
const list = Array.isArray(prev) ? [...prev] : prev ? [prev] : [];
|
||||
list.push(extra);
|
||||
where.AND = list;
|
||||
return where;
|
||||
}
|
||||
|
||||
export async function canSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) {
|
||||
if (!ownerId) return true;
|
||||
const scope = effectiveScope(user);
|
||||
if (scope === 'ALL') return true;
|
||||
if (ownerId === user.id) return true;
|
||||
if (scope !== 'DEPT' || !user.departmentId) return false;
|
||||
const owner = await prisma.user.findUnique({
|
||||
where: { id: ownerId },
|
||||
include: { employee: true },
|
||||
});
|
||||
if (!owner?.employee) return false;
|
||||
const deptIds = await descendantDeptIds(prisma, user.departmentId);
|
||||
if (owner.employee.departmentId && deptIds.includes(owner.employee.departmentId)) return true;
|
||||
const extra = String(owner.employee.managerIds || '')
|
||||
.split(/[,,、\s]+/)
|
||||
.filter(Boolean);
|
||||
return Boolean(
|
||||
user.employeeId &&
|
||||
(owner.employee.managerId === user.employeeId || extra.includes(user.employeeId)),
|
||||
);
|
||||
}
|
||||
|
||||
export async function assertCanSeeOwner(prisma: PrismaService, user: AuthUser, ownerId?: string | null) {
|
||||
if (await canSeeOwner(prisma, user, ownerId)) return;
|
||||
throw new ForbiddenException('超出数据范围,无权查看');
|
||||
}
|
||||
Reference in New Issue
Block a user