Files
gongxue-base/apps/server/src/common/campus-scope.ts

81 lines
2.5 KiB
TypeScript

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { In } from 'typeorm';
import { Request } from 'express';
import { DepartmentsService } from '../departments/departments.service';
export interface CampusRequest extends Request {
user?: {
id: number;
isSuperAdmin?: boolean;
};
campusScope?: CampusScope;
}
@Injectable({ scope: Scope.REQUEST })
export class CampusScope {
constructor(
@Inject(REQUEST) private req: CampusRequest,
private departmentsService: DepartmentsService,
) {}
get userId(): number | undefined {
return this.req.user?.id;
}
get isSuperAdmin(): boolean {
return this.req.user?.isSuperAdmin ?? false;
}
get currentDepartmentId(): number | null {
const raw = this.req.headers?.['x-campus-id'];
if (raw === undefined) return null;
const str = Array.isArray(raw) ? raw[0] : raw;
if (!str) return null;
const id = parseInt(str, 10);
return Number.isNaN(id) ? null : id;
}
/** Appends departmentId filter to TypeORM find where conditions */
async filter<T extends Record<string, unknown>>(where: T): Promise<T> {
// Super admin with no campus selected → no filtering
if (this.isSuperAdmin && !this.currentDepartmentId) {
return where;
}
const ids = await this.getEffectiveScopeIds();
if (ids.length === 0) {
// Non-super-admin with no scoping → match nothing, never leak unfiltered data
if (!this.isSuperAdmin) {
return { ...where, departmentId: In([]) };
}
return where;
}
return { ...where, departmentId: In(ids) };
}
/** Returns department IDs for QueryBuilder .andWhere() usage. null = no filtering needed. */
async getScopeDepartmentIds(): Promise<number[] | null> {
if (this.isSuperAdmin && !this.currentDepartmentId) return null;
const ids = await this.getEffectiveScopeIds();
return ids.length > 0 ? ids : null;
}
private async getEffectiveScopeIds(): Promise<number[]> {
// Specific campus selected → campus + descendants
if (this.currentDepartmentId) {
return this.departmentsService.getDescendantIds(this.currentDepartmentId);
}
// No campus selected → all user departments + descendants
if (!this.userId) return [];
const userDeptIds = await this.departmentsService.getUserDepartments(this.userId);
const allIds = await Promise.all(
userDeptIds.map((id) => this.departmentsService.getDescendantIds(id)),
);
return [...new Set(allIds.flat())];
}
}