import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { PrismaService } from './prisma.service.js'; @Injectable() export class TourRoleGuard implements CanActivate { constructor(private prisma: PrismaService) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const user = request.user; // Giả sử đã qua AuthGuard (Passport/JWT) // Lấy tourId từ URL params (:id hoặc :tourId) const tourId = parseInt(request.params.id || request.params.tourId); const path = request.url; if (!user || isNaN(tourId)) { throw new ForbiddenException("Thông tin xác thực hoặc mã Tour không hợp lệ."); } /** * TỐI ƯU: Chỉ truy vấn Database 1 lần duy nhất để lấy thông tin thành viên. * Chúng ta lưu kết quả vào request object để các interceptor hoặc controller * sau này có thể dùng lại mà không cần query lại. */ const membership = await this.prisma.tourMember.findUnique({ where: { tourId_userId: { tourId: tourId, userId: user.id, }, }, }); if (!membership) { throw new ForbiddenException("Bạn không phải là thành viên của tour này."); } // Gắn thông tin vào request để sử dụng ở tầng Controller request.tourMembership = membership; const role = membership.role; const isPlanPath = path.includes('/plans'); const isExpensePath = path.includes('/expenses'); // 1. Chặn MEMBER_PHOTO_ONLY và VIEWER_EXTERNAL truy cập Plans & Expenses if ( (role === 'MEMBER_PHOTO_ONLY' || role === 'VIEWER_EXTERNAL') && (isPlanPath || isExpensePath) ) { throw new ForbiddenException("Bạn không có quyền xem kế hoạch và chi phí của tour này."); } return true; } }