import { CallHandler, ExecutionContext, ForbiddenException, Injectable, NestInterceptor, } from '@nestjs/common'; import type { Request } from 'express'; import type { Observable } from 'rxjs'; import type { Actor } from '../common/record-scope'; const VIEW_AS_MESSAGE = 'در حالت مشاهده به‌جای کاربر دیگر امکان تغییر وجود ندارد.'; function normalizePath(url: string): string { const path = (url.split('?')[0] || '').replace(/\/+$/, '') || '/'; return path; } /** مسیرهایی که در حالت مشاهده مجاز به تغییرند (خروج از مشاهده و لنز بایگانی). */ function isAllowedWhileViewing(method: string, url: string): boolean { const path = normalizePath(url); const m = method.toUpperCase(); if (m === 'POST' && (path.endsWith('/auth/view-as') || path.endsWith('/auth/view-as/stop'))) { return true; } if (m === 'PATCH' && path.endsWith('/auth/me/prior-archive')) { return true; } return false; } @Injectable() export class ViewAsReadOnlyInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const req = context.switchToHttp().getRequest(); const method = (req.method || 'GET').toUpperCase(); if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') { return next.handle(); } const actor = req.user; if (!actor?.viewAsReadOnly) { return next.handle(); } const url = req.originalUrl || req.url || ''; if (isAllowedWhileViewing(method, url)) { return next.handle(); } throw new ForbiddenException(VIEW_AS_MESSAGE); } }