mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
Throttling configuration from the backoffice added
This commit is contained in:
@@ -4,7 +4,8 @@ import {
|
||||
NestModule,
|
||||
OnApplicationBootstrap,
|
||||
} from '@nestjs/common';
|
||||
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||
import { ThrottlerModule } from '@nestjs/throttler';
|
||||
import { DynamicThrottlerGuard } from './common/dynamic-throttler.guard';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
@@ -135,7 +136,8 @@ import { TasksModule } from './modules/tasks/tasks.module';
|
||||
TasksModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_GUARD, useClass: ThrottlerGuard },
|
||||
{ provide: APP_GUARD, useClass: DynamicThrottlerGuard },
|
||||
DynamicThrottlerGuard,
|
||||
EdrPassengerOrgSeeder,
|
||||
PassengerStaffUsersSeeder,
|
||||
],
|
||||
|
||||
34
apps/edr-passenger-api/src/common/dynamic-throttler.guard.ts
Normal file
34
apps/edr-passenger-api/src/common/dynamic-throttler.guard.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Injectable, ExecutionContext, Inject } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ThrottlerGuard, ThrottlerStorage, getOptionsToken, getStorageToken } from '@nestjs/throttler';
|
||||
import { SystemConfigService, CONFIG_KEYS } from '../modules/system-config/system-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicThrottlerGuard extends ThrottlerGuard {
|
||||
constructor(
|
||||
@Inject(getOptionsToken()) options: any,
|
||||
@Inject(getStorageToken()) storageService: ThrottlerStorage,
|
||||
reflector: Reflector,
|
||||
private readonly systemConfig: SystemConfigService,
|
||||
) {
|
||||
super(options, storageService, reflector);
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const [authLimit, authTtl, strictLimit, strictTtl, defaultLimit, defaultTtl] =
|
||||
await Promise.all([
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_AUTH_LIMIT),
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_AUTH_TTL_MS),
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_STRICT_LIMIT),
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_STRICT_TTL_MS),
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_DEFAULT_LIMIT),
|
||||
this.systemConfig.getNumber(CONFIG_KEYS.THROTTLE_DEFAULT_TTL_MS),
|
||||
]);
|
||||
|
||||
this.throttlers = [
|
||||
{ name: 'default', ttl: defaultTtl, limit: defaultLimit },
|
||||
];
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
|
||||
import { PackagesService } from './packages.service';
|
||||
import { CreatePackageDto, BookPackageDto, CreatePriceTierDto, UpdatePriceTierDto } from './packages.dto';
|
||||
import { IamGuard } from '../../common/iam-adapter';
|
||||
import { JwtGuard } from '../../common/jwt.guard';
|
||||
import { OptionalJwtGuard } from '../verifayda/optional-jwt.guard';
|
||||
|
||||
@@ -19,8 +20,9 @@ export class PackagesController {
|
||||
}
|
||||
|
||||
@Get('all')
|
||||
@IsPublic()
|
||||
@ApiOperation({ summary: 'List all packages' })
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'List all packages (backoffice)' })
|
||||
listAll(@Query('page') page?: string, @Query('pageSize') pageSize?: string) {
|
||||
return this.service.listAll(page ? +page : 1, pageSize ? +pageSize : 20);
|
||||
}
|
||||
@@ -48,56 +50,64 @@ export class PackagesController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Create package (admin)' })
|
||||
create(@Body() dto: CreatePackageDto) {
|
||||
return this.service.create(dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Update package (admin)' })
|
||||
update(@Param('id') id: string, @Body() dto: Partial<CreatePackageDto>) {
|
||||
return this.service.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Delete package (admin)' })
|
||||
remove(@Param('id') id: string) {
|
||||
return this.service.remove(id);
|
||||
}
|
||||
|
||||
@Patch(':id/activate')
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Activate package (admin)' })
|
||||
activate(@Param('id') id: string) {
|
||||
return this.service.activate(id);
|
||||
}
|
||||
|
||||
@Patch(':id/deactivate')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Deactivate package (admin)' })
|
||||
deactivate(@Param('id') id: string) {
|
||||
return this.service.deactivate(id);
|
||||
}
|
||||
|
||||
@Post(':id/tiers')
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-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')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-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')
|
||||
@UseGuards(IamGuard)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({ summary: 'Delete price tier (admin)' })
|
||||
deleteTier(@Param('tierId') tierId: string) {
|
||||
return this.service.deleteTier(tierId);
|
||||
|
||||
@@ -20,7 +20,7 @@ export class PackagesService {
|
||||
listActive() {
|
||||
const now = new Date();
|
||||
return this.prisma.travelPackage.findMany({
|
||||
where: { status: 'ACTIVE', validFrom: { lte: now }, validUntil: { gte: now } },
|
||||
where: { status: 'ACTIVE', validUntil: { gte: now } },
|
||||
include: {
|
||||
priceTiers: true,
|
||||
outboundSchedule: { include: { originStation: true, destinationStation: true } },
|
||||
@@ -145,6 +145,12 @@ export class PackagesService {
|
||||
return this.prisma.travelPackage.update({ where: { id }, data: { status: 'ACTIVE' } });
|
||||
}
|
||||
|
||||
async deactivate(id: string) {
|
||||
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: { status: 'DRAFT' } });
|
||||
}
|
||||
|
||||
async book(dto: BookPackageDto, passengerId?: string) {
|
||||
const pkg = await this.prisma.travelPackage.findUnique({
|
||||
where: { id: dto.packageId },
|
||||
@@ -152,7 +158,6 @@ export class PackagesService {
|
||||
});
|
||||
if (!pkg) throw new NotFoundException('Package not found');
|
||||
if (pkg.status !== 'ACTIVE') throw new BadRequestException('Package is not available for booking');
|
||||
if (new Date() > pkg.validUntil) throw new BadRequestException('Package has expired');
|
||||
|
||||
const tier = pkg.priceTiers.find((t) => t.id === dto.priceTierId);
|
||||
if (!tier) throw new NotFoundException('Price tier not found');
|
||||
|
||||
@@ -4,11 +4,23 @@ import { PrismaService } from '../../common/prisma.service';
|
||||
export const CONFIG_KEYS = {
|
||||
SEAT_HOLD_DURATION_MINUTES: 'seat_hold_duration_minutes',
|
||||
HOLD_CUTOFF_HOURS_BEFORE_DEPARTURE: 'hold_cutoff_hours_before_departure',
|
||||
THROTTLE_AUTH_LIMIT: 'throttle_auth_limit',
|
||||
THROTTLE_AUTH_TTL_MS: 'throttle_auth_ttl_ms',
|
||||
THROTTLE_STRICT_LIMIT: 'throttle_strict_limit',
|
||||
THROTTLE_STRICT_TTL_MS: 'throttle_strict_ttl_ms',
|
||||
THROTTLE_DEFAULT_LIMIT: 'throttle_default_limit',
|
||||
THROTTLE_DEFAULT_TTL_MS: 'throttle_default_ttl_ms',
|
||||
} as const;
|
||||
|
||||
const DEFAULTS: Record<string, string> = {
|
||||
[CONFIG_KEYS.SEAT_HOLD_DURATION_MINUTES]: '5',
|
||||
[CONFIG_KEYS.HOLD_CUTOFF_HOURS_BEFORE_DEPARTURE]: '2',
|
||||
[CONFIG_KEYS.THROTTLE_AUTH_LIMIT]: '5',
|
||||
[CONFIG_KEYS.THROTTLE_AUTH_TTL_MS]: '60000',
|
||||
[CONFIG_KEYS.THROTTLE_STRICT_LIMIT]: '20',
|
||||
[CONFIG_KEYS.THROTTLE_STRICT_TTL_MS]: '60000',
|
||||
[CONFIG_KEYS.THROTTLE_DEFAULT_LIMIT]: '100',
|
||||
[CONFIG_KEYS.THROTTLE_DEFAULT_TTL_MS]: '60000',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
||||
Reference in New Issue
Block a user