add auth roles and guards 050220
This commit is contained in:
@@ -7,8 +7,15 @@ import {
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { BookingEntity, BookingStatus } from '../../entities/booking.entity';
|
||||
import { UserRole } from '../../entities/user.entity';
|
||||
import { AuthUser } from '../auth/auth.types';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { BookingsService } from './bookings.service';
|
||||
import { CreateBookingDto } from './dto/create-booking.dto';
|
||||
import { UpdateBookingDto } from './dto/update-booking.dto';
|
||||
@@ -18,33 +25,51 @@ export class BookingsController {
|
||||
constructor(private readonly service: BookingsService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER, UserRole.CUSTOMER)
|
||||
findAll(
|
||||
@Query('customerId') customerId?: string,
|
||||
@Query('businessId') businessId?: string,
|
||||
@Query('status') status?: BookingStatus,
|
||||
@CurrentUser() user?: AuthUser,
|
||||
): Promise<BookingEntity[]> {
|
||||
return this.service.findAll({ customerId, businessId, status });
|
||||
return this.service.findAll({ customerId, businessId, status }, user);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string): Promise<BookingEntity> {
|
||||
return this.service.findOne(id);
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER, UserRole.CUSTOMER)
|
||||
findOne(
|
||||
@Param('id') id: string,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<BookingEntity> {
|
||||
return this.service.findOne(id, user);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@Body() payload: CreateBookingDto): Promise<BookingEntity> {
|
||||
return this.service.create(payload);
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.CUSTOMER)
|
||||
create(
|
||||
@Body() payload: CreateBookingDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<BookingEntity> {
|
||||
return this.service.create(payload, user);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER)
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() payload: UpdateBookingDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<BookingEntity> {
|
||||
return this.service.update(id, payload);
|
||||
return this.service.update(id, payload, user);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
async remove(@Param('id') id: string): Promise<{ success: true }> {
|
||||
await this.service.remove(id);
|
||||
return { success: true };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
@@ -9,7 +10,8 @@ import { Repository } from 'typeorm';
|
||||
import { BookingEntity, BookingStatus } from '../../entities/booking.entity';
|
||||
import { BusinessServiceEntity } from '../../entities/business-service.entity';
|
||||
import { BusinessEntity } from '../../entities/business.entity';
|
||||
import { UserEntity } from '../../entities/user.entity';
|
||||
import { UserEntity, UserRole } from '../../entities/user.entity';
|
||||
import { AuthUser } from '../auth/auth.types';
|
||||
import { CreateBookingDto } from './dto/create-booking.dto';
|
||||
import { UpdateBookingDto } from './dto/update-booking.dto';
|
||||
|
||||
@@ -39,7 +41,10 @@ export class BookingsService {
|
||||
this.minLeadMinutes = Number.isFinite(parsed) && parsed >= 0 ? parsed : 60;
|
||||
}
|
||||
|
||||
async findAll(filters: FindAllFilters): Promise<BookingEntity[]> {
|
||||
async findAll(
|
||||
filters: FindAllFilters,
|
||||
actor?: AuthUser,
|
||||
): Promise<BookingEntity[]> {
|
||||
const qb = this.repo
|
||||
.createQueryBuilder('b')
|
||||
.leftJoinAndSelect('b.customer', 'customer')
|
||||
@@ -48,7 +53,15 @@ export class BookingsService {
|
||||
.leftJoinAndSelect('businessService.serviceTemplate', 'template')
|
||||
.orderBy('b.scheduledAt', 'ASC');
|
||||
|
||||
if (filters.customerId) {
|
||||
if (actor?.role === UserRole.CUSTOMER) {
|
||||
qb.andWhere('b.customerId = :actorCustomerId', {
|
||||
actorCustomerId: actor.id,
|
||||
});
|
||||
} else if (actor?.role === UserRole.BUSINESS_OWNER) {
|
||||
qb.andWhere('business.ownerId = :actorOwnerId', {
|
||||
actorOwnerId: actor.id,
|
||||
});
|
||||
} else if (filters.customerId) {
|
||||
qb.andWhere('b.customerId = :customerId', {
|
||||
customerId: filters.customerId,
|
||||
});
|
||||
@@ -65,7 +78,7 @@ export class BookingsService {
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<BookingEntity> {
|
||||
async findOne(id: string, actor?: AuthUser): Promise<BookingEntity> {
|
||||
const row = await this.repo.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
@@ -77,10 +90,18 @@ export class BookingsService {
|
||||
if (!row) {
|
||||
throw new NotFoundException('Booking not found');
|
||||
}
|
||||
this.assertCanViewBooking(row, actor);
|
||||
return row;
|
||||
}
|
||||
|
||||
async create(dto: CreateBookingDto): Promise<BookingEntity> {
|
||||
async create(dto: CreateBookingDto, actor?: AuthUser): Promise<BookingEntity> {
|
||||
if (
|
||||
actor?.role === UserRole.CUSTOMER &&
|
||||
dto.customerId !== actor.id
|
||||
) {
|
||||
throw new ForbiddenException('Customers can only book for themselves');
|
||||
}
|
||||
|
||||
const customer = await this.userRepo.findOne({
|
||||
where: { id: dto.customerId },
|
||||
});
|
||||
@@ -124,8 +145,13 @@ export class BookingsService {
|
||||
return this.repo.save(entity);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateBookingDto): Promise<BookingEntity> {
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateBookingDto,
|
||||
actor?: AuthUser,
|
||||
): Promise<BookingEntity> {
|
||||
const row = await this.findOne(id);
|
||||
this.assertCanManageBooking(row, actor);
|
||||
|
||||
if (dto.scheduledAt !== undefined) {
|
||||
const scheduledAt = this.parseScheduledAt(dto.scheduledAt);
|
||||
@@ -168,4 +194,38 @@ export class BookingsService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private assertCanViewBooking(row: BookingEntity, actor?: AuthUser): void {
|
||||
if (!actor || actor.role === UserRole.ADMIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (actor.role === UserRole.CUSTOMER && row.customerId === actor.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
actor.role === UserRole.BUSINESS_OWNER &&
|
||||
row.business?.ownerId === actor.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ForbiddenException('You cannot access this booking');
|
||||
}
|
||||
|
||||
private assertCanManageBooking(row: BookingEntity, actor?: AuthUser): void {
|
||||
if (!actor || actor.role === UserRole.ADMIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
actor.role === UserRole.BUSINESS_OWNER &&
|
||||
row.business?.ownerId === actor.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ForbiddenException('You can only manage your own business bookings');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user