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. No campus selected = no filtering for super admin; empty result for others. */ async filter>(where: T): Promise { 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(). null = no filtering. */ async getScopeDepartmentIds(): Promise { if (this.isSuperAdmin && !this.currentDepartmentId) return null; const ids = await this.getEffectiveScopeIds(); return ids.length > 0 ? ids : null; } private async getEffectiveScopeIds(): Promise { if (this.currentDepartmentId) { return this.departmentsService.getDescendantIds(this.currentDepartmentId); } 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())]; } }