Files
travelplanning/rbac.middleware.ts
T

55 lines
1.9 KiB
TypeScript

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<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user; // Giả sử đã qua AuthGuard (Passport/JWT)
// UUID không cần parseInt
const tourId = request.params.id || request.params.tourId;
const path = request.url;
if (!user || !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 participation = await this.prisma.tourParticipant.findUnique({
where: {
tourId_userId: {
tourId: tourId,
userId: user.id,
},
},
});
if (!participation) {
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.tourParticipation = participation;
const role = participation.role;
const isPlanPath = path.includes('/plans');
const isExpensePath = path.includes('/expenses');
// Theo định nghĩa mới: MEMBER_NO_FINANCE và VIEWER_ONLY bị hạn chế
if (
(role === 'MEMBER_NO_FINANCE' || role === 'VIEWER_ONLY') &&
(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;
}
}