IAM, package, luggage, app health, rate limit, and more

This commit is contained in:
Stephanos A
2026-06-24 14:02:51 +03:00
parent e10b013b62
commit 86760933e8
63 changed files with 3475 additions and 280 deletions

View File

@@ -1,7 +1,7 @@
import { Body, Controller, Get, Param, Post, Patch, UseGuards, Request, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Patch, Delete, UseGuards, Request, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { PackagesService } from './packages.service';
import { CreatePackageDto, BookPackageDto } from './packages.dto';
import { CreatePackageDto, BookPackageDto, CreatePriceTierDto, UpdatePriceTierDto } from './packages.dto';
import { JwtGuard } from '../../common/jwt.guard';
import { OptionalJwtGuard } from '../verifayda/optional-jwt.guard';
@@ -52,6 +52,14 @@ export class PackagesController {
return this.service.create(dto);
}
@Patch(':id')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Update package (admin)' })
update(@Param('id') id: string, @Body() dto: Partial<CreatePackageDto>) {
return this.service.update(id, dto);
}
@Patch(':id/activate')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@@ -60,6 +68,30 @@ export class PackagesController {
return this.service.activate(id);
}
@Post(':id/tiers')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Add price tier to package (admin)' })
addTier(@Param('id') id: string, @Body() dto: CreatePriceTierDto) {
return this.service.addTier(id, dto);
}
@Patch('tiers/:tierId')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Update price tier (admin)' })
updateTier(@Param('tierId') tierId: string, @Body() dto: UpdatePriceTierDto) {
return this.service.updateTier(tierId, dto);
}
@Delete('tiers/:tierId')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Delete price tier (admin)' })
deleteTier(@Param('tierId') tierId: string) {
return this.service.deleteTier(tierId);
}
@Post('book')
@UseGuards(OptionalJwtGuard)
@ApiBearerAuth('JWT-auth')

View File

@@ -16,6 +16,13 @@ export class CreatePriceTierDto {
@IsInt() @Min(0) availableSeats: number;
}
export class UpdatePriceTierDto {
@ApiPropertyOptional() @IsOptional() @IsString() seatType?: string;
@ApiPropertyOptional() @IsOptional() @IsString() label?: string;
@ApiPropertyOptional() @IsOptional() @IsInt() @Min(0) priceMinor?: number;
@ApiPropertyOptional() @IsOptional() @IsInt() @Min(0) availableSeats?: number;
}
export class CreatePackageDto {
@ApiProperty({ example: 'KULUBBI-2025' })
@IsString() code: string;

View File

@@ -1,7 +1,7 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../common/prisma.service';
import { CurrencyService } from '../currency/currency.service';
import { CreatePackageDto, BookPackageDto } from './packages.dto';
import { CreatePackageDto, BookPackageDto, UpdatePriceTierDto, CreatePriceTierDto } from './packages.dto';
import { Currency } from '@prisma/client';
function generateRef(): string {
@@ -70,6 +70,53 @@ export class PackagesService {
});
}
async update(id: string, dto: Partial<CreatePackageDto>) {
const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
if (!pkg) throw new NotFoundException('Package not found');
return this.prisma.travelPackage.update({
where: { id },
data: {
...(dto.code && { code: dto.code }),
...(dto.name && { name: dto.name }),
...(dto.description !== undefined && { description: dto.description }),
...(dto.outboundScheduleId && { outboundScheduleId: dto.outboundScheduleId }),
...(dto.returnScheduleId && { returnScheduleId: dto.returnScheduleId }),
...(dto.originStationId && { originStationId: dto.originStationId }),
...(dto.destinationStationId && { destinationStationId: dto.destinationStationId }),
...(dto.boardingTime && { boardingTime: new Date(dto.boardingTime) }),
...(dto.departureTime && { departureTime: new Date(dto.departureTime) }),
...(dto.arrivalTime && { arrivalTime: new Date(dto.arrivalTime) }),
...(dto.totalCapacity && { totalCapacity: dto.totalCapacity }),
...(dto.coachConfiguration !== undefined && { coachConfiguration: dto.coachConfiguration }),
...(dto.includedServices && { includedServices: dto.includedServices }),
...(dto.busTransferIncluded !== undefined && { busTransferIncluded: dto.busTransferIncluded }),
...(dto.busTransferRoute !== undefined && { busTransferRoute: dto.busTransferRoute }),
...(dto.validFrom && { validFrom: new Date(dto.validFrom) }),
...(dto.validUntil && { validUntil: new Date(dto.validUntil) }),
},
include: { priceTiers: true },
});
}
async addTier(packageId: string, dto: CreatePriceTierDto) {
const pkg = await this.prisma.travelPackage.findUnique({ where: { id: packageId } });
if (!pkg) throw new NotFoundException('Package not found');
return this.prisma.packagePriceTier.create({ data: { ...dto, packageId } });
}
async updateTier(tierId: string, dto: UpdatePriceTierDto) {
const tier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } });
if (!tier) throw new NotFoundException('Price tier not found');
return this.prisma.packagePriceTier.update({ where: { id: tierId }, data: dto });
}
async deleteTier(tierId: string) {
const tier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } });
if (!tier) throw new NotFoundException('Price tier not found');
if (tier.bookedSeats > 0) throw new BadRequestException('Cannot delete a tier that has bookings');
return this.prisma.packagePriceTier.delete({ where: { id: tierId } });
}
async activate(id: string) {
const pkg = await this.prisma.travelPackage.findUnique({ where: { id } });
if (!pkg) throw new NotFoundException('Package not found');