update rezerv
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { BusinessServiceClassEntity } from '../../entities/business-service-class.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 { BusinessServiceClassesService } from './business-service-classes.service';
|
||||
import { CreateBusinessServiceClassDto } from './dto/create-business-service-class.dto';
|
||||
import { UpdateBusinessServiceClassDto } from './dto/update-business-service-class.dto';
|
||||
|
||||
@Controller('business-service-classes')
|
||||
export class BusinessServiceClassesController {
|
||||
constructor(private readonly service: BusinessServiceClassesService) {}
|
||||
|
||||
@Get()
|
||||
findAll(
|
||||
@Query('businessId') businessId?: string,
|
||||
@Query('isActive') isActive?: string,
|
||||
): Promise<BusinessServiceClassEntity[]> {
|
||||
const parsedIsActive =
|
||||
typeof isActive === 'string' ? isActive === 'true' : undefined;
|
||||
|
||||
return this.service.findAll({ businessId, isActive: parsedIsActive });
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string): Promise<BusinessServiceClassEntity> {
|
||||
return this.service.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER)
|
||||
create(
|
||||
@Body() payload: CreateBusinessServiceClassDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<BusinessServiceClassEntity> {
|
||||
return this.service.create(payload, user);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER)
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() payload: UpdateBusinessServiceClassDto,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<BusinessServiceClassEntity> {
|
||||
return this.service.update(id, payload, user);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER)
|
||||
async remove(
|
||||
@Param('id') id: string,
|
||||
@CurrentUser() user: AuthUser,
|
||||
): Promise<{ success: true }> {
|
||||
await this.service.remove(id, user);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BusinessEntity } from '../../entities/business.entity';
|
||||
import { BusinessServiceClassEntity } from '../../entities/business-service-class.entity';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { BusinessServiceClassesController } from './business-service-classes.controller';
|
||||
import { BusinessServiceClassesService } from './business-service-classes.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([BusinessServiceClassEntity, BusinessEntity]),
|
||||
AuthModule,
|
||||
],
|
||||
controllers: [BusinessServiceClassesController],
|
||||
providers: [BusinessServiceClassesService],
|
||||
exports: [BusinessServiceClassesService],
|
||||
})
|
||||
export class BusinessServiceClassesModule {}
|
||||
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { BusinessEntity } from '../../entities/business.entity';
|
||||
import { BusinessServiceClassEntity } from '../../entities/business-service-class.entity';
|
||||
import { UserRole } from '../../entities/user.entity';
|
||||
import { AuthUser } from '../auth/auth.types';
|
||||
import { CreateBusinessServiceClassDto } from './dto/create-business-service-class.dto';
|
||||
import { UpdateBusinessServiceClassDto } from './dto/update-business-service-class.dto';
|
||||
|
||||
type FindAllFilters = {
|
||||
businessId?: string;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class BusinessServiceClassesService {
|
||||
constructor(
|
||||
@InjectRepository(BusinessServiceClassEntity)
|
||||
private readonly repo: Repository<BusinessServiceClassEntity>,
|
||||
@InjectRepository(BusinessEntity)
|
||||
private readonly businessRepo: Repository<BusinessEntity>,
|
||||
) {}
|
||||
|
||||
async findAll(filters: FindAllFilters): Promise<BusinessServiceClassEntity[]> {
|
||||
const query = this.repo
|
||||
.createQueryBuilder('c')
|
||||
.orderBy('c.sortOrder', 'ASC')
|
||||
.addOrderBy('c.createdAt', 'ASC');
|
||||
|
||||
if (filters.businessId) {
|
||||
query.andWhere('c.businessId = :businessId', {
|
||||
businessId: filters.businessId,
|
||||
});
|
||||
}
|
||||
if (typeof filters.isActive === 'boolean') {
|
||||
query.andWhere('c.isActive = :isActive', { isActive: filters.isActive });
|
||||
}
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<BusinessServiceClassEntity> {
|
||||
const row = await this.repo.findOne({ where: { id } });
|
||||
if (!row) {
|
||||
throw new NotFoundException('Service class not found');
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
async create(
|
||||
dto: CreateBusinessServiceClassDto,
|
||||
actor?: AuthUser,
|
||||
): Promise<BusinessServiceClassEntity> {
|
||||
const business = await this.businessRepo.findOne({
|
||||
where: { id: dto.businessId },
|
||||
});
|
||||
if (!business) {
|
||||
throw new NotFoundException('Business not found');
|
||||
}
|
||||
this.assertCanManageBusiness(business, actor);
|
||||
|
||||
const dup = await this.repo.findOne({
|
||||
where: { businessId: dto.businessId, title: dto.title.trim() },
|
||||
});
|
||||
if (dup) {
|
||||
throw new ConflictException('A class with this title already exists');
|
||||
}
|
||||
|
||||
let sortOrder = dto.sortOrder;
|
||||
if (sortOrder === undefined) {
|
||||
const max = await this.repo
|
||||
.createQueryBuilder('c')
|
||||
.select('MAX(c.sortOrder)', 'max')
|
||||
.where('c.businessId = :businessId', { businessId: dto.businessId })
|
||||
.getRawOne<{ max: string | null }>();
|
||||
sortOrder = (max?.max ? Number.parseInt(max.max, 10) : -1) + 1;
|
||||
}
|
||||
|
||||
const entity = this.repo.create({
|
||||
businessId: dto.businessId,
|
||||
title: dto.title.trim(),
|
||||
description: dto.description?.trim() || null,
|
||||
sortOrder,
|
||||
isActive: dto.isActive ?? true,
|
||||
});
|
||||
|
||||
return this.repo.save(entity);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateBusinessServiceClassDto,
|
||||
actor?: AuthUser,
|
||||
): Promise<BusinessServiceClassEntity> {
|
||||
const row = await this.findOne(id);
|
||||
const business = await this.businessRepo.findOne({
|
||||
where: { id: row.businessId },
|
||||
});
|
||||
if (!business) {
|
||||
throw new NotFoundException('Business not found');
|
||||
}
|
||||
this.assertCanManageBusiness(business, actor);
|
||||
|
||||
if (dto.title !== undefined) {
|
||||
const title = dto.title.trim();
|
||||
if (!title) {
|
||||
throw new BadRequestException('title cannot be empty');
|
||||
}
|
||||
const dup = await this.repo.findOne({
|
||||
where: { businessId: row.businessId, title },
|
||||
});
|
||||
if (dup && dup.id !== row.id) {
|
||||
throw new ConflictException('A class with this title already exists');
|
||||
}
|
||||
row.title = title;
|
||||
}
|
||||
if (dto.description !== undefined) {
|
||||
row.description = dto.description?.trim() || null;
|
||||
}
|
||||
if (dto.sortOrder !== undefined) {
|
||||
row.sortOrder = dto.sortOrder;
|
||||
}
|
||||
if (dto.isActive !== undefined) {
|
||||
row.isActive = dto.isActive;
|
||||
}
|
||||
|
||||
return this.repo.save(row);
|
||||
}
|
||||
|
||||
async remove(id: string, actor?: AuthUser): Promise<void> {
|
||||
const row = await this.findOne(id);
|
||||
const business = await this.businessRepo.findOne({
|
||||
where: { id: row.businessId },
|
||||
});
|
||||
if (!business) {
|
||||
throw new NotFoundException('Business not found');
|
||||
}
|
||||
this.assertCanManageBusiness(business, actor);
|
||||
|
||||
const result = await this.repo.delete(id);
|
||||
if (!result.affected) {
|
||||
throw new NotFoundException('Service class not found');
|
||||
}
|
||||
}
|
||||
|
||||
private assertCanManageBusiness(
|
||||
business: BusinessEntity,
|
||||
actor?: AuthUser,
|
||||
): void {
|
||||
if (!actor || actor.role === UserRole.ADMIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
actor.role !== UserRole.BUSINESS_OWNER ||
|
||||
business.ownerId !== actor.id
|
||||
) {
|
||||
throw new ForbiddenException('You can only manage your own business');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
|
||||
|
||||
export class CreateBusinessServiceClassDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID()
|
||||
businessId!: string;
|
||||
|
||||
@ApiProperty({ example: 'کلاس ۱ — مینی و کوچک' })
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
title!: string;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ default: 0 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
sortOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({ default: true })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
|
||||
|
||||
export class UpdateBusinessServiceClassDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
title?: string;
|
||||
|
||||
@ApiPropertyOptional({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string | null;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
sortOrder?: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user