remove throttler

This commit is contained in:
SennayT
2026-07-14 10:30:22 +03:00
parent 3aa4451e00
commit 8f45d7ae1f
7 changed files with 1094 additions and 760 deletions

View File

@@ -1,17 +1,42 @@
import { Body, Controller, Get, Param, Post, UseGuards, Query, Request, UnauthorizedException, Patch, Delete, SetMetadata } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { SkipThrottle, Throttle } from '@nestjs/throttler';
import { PassengersService } from './passengers.service';
import { CreateTravelerProfileDto, CreateSavedRouteDto, VerifyFaydaDto, SavePassengersDto, RegisterPassengerDto } from './passengers.dto';
import { JwtGuard } from '../../common/jwt.guard';
import { PassengerAdmin } from '../../common/passenger-guards';
import { VerifaydaService } from '../verifayda/verifayda.service';
import { OptionalJwtGuard } from '../verifayda/optional-jwt.guard';
import { PrismaService } from '../../common/prisma.service';
import {
Body,
Controller,
Get,
Param,
Post,
UseGuards,
Query,
Request,
UnauthorizedException,
Patch,
Delete,
SetMetadata,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiBearerAuth,
ApiResponse,
ApiQuery,
} from "@nestjs/swagger";
import { SkipThrottle, Throttle } from "@nestjs/throttler";
import { PassengersService } from "./passengers.service";
import {
CreateTravelerProfileDto,
CreateSavedRouteDto,
VerifyFaydaDto,
SavePassengersDto,
RegisterPassengerDto,
} from "./passengers.dto";
import { JwtGuard } from "../../common/jwt.guard";
import { PassengerAdmin } from "../../common/passenger-guards";
import { VerifaydaService } from "../verifayda/verifayda.service";
import { OptionalJwtGuard } from "../verifayda/optional-jwt.guard";
import { PrismaService } from "../../common/prisma.service";
@ApiTags('Passengers')
@Controller('passengers')
@Throttle({ strict: { limit: 20, ttl: 60_000 } })
@ApiTags("Passengers")
@Controller("passengers")
// @Throttle({ strict: { limit: 20, ttl: 60_000 } })
export class PassengersController {
constructor(
private service: PassengersService,
@@ -20,9 +45,9 @@ export class PassengersController {
) {}
@Get()
@SetMetadata('isPublic', true)
@ApiOperation({
summary: 'List all travelers with filters (Admin/Agent)',
@SetMetadata("isPublic", true)
@ApiOperation({
summary: "List all travelers with filters (Admin/Agent)",
description: `**Returns paginated list of all travelers in the system**
---
@@ -54,90 +79,114 @@ export class PassengersController {
- **faydaVerified**: Whether verified via Verifayda
- **loyaltyTier/loyaltyPoints**: If linked to user account
- **totalBookings**: Number of bookings
- **createdAt**: When traveler was first added to system`
- **createdAt**: When traveler was first added to system`,
})
@ApiQuery({
name: "search",
required: false,
description: "Search by name, email, or phone",
})
@ApiQuery({
name: "gender",
required: false,
description: "Filter by gender (Male, Female, Other)",
})
@ApiQuery({
name: "dateFrom",
required: false,
description: "Filter by creation date from (YYYY-MM-DD)",
})
@ApiQuery({
name: "dateTo",
required: false,
description: "Filter by creation date to (YYYY-MM-DD)",
})
@ApiQuery({
name: "page",
required: false,
description: "Page number (default: 1)",
})
@ApiQuery({
name: "pageSize",
required: false,
description: "Items per page (default: 20)",
})
@ApiQuery({ name: 'search', required: false, description: 'Search by name, email, or phone' })
@ApiQuery({ name: 'gender', required: false, description: 'Filter by gender (Male, Female, Other)' })
@ApiQuery({ name: 'dateFrom', required: false, description: 'Filter by creation date from (YYYY-MM-DD)' })
@ApiQuery({ name: 'dateTo', required: false, description: 'Filter by creation date to (YYYY-MM-DD)' })
@ApiQuery({ name: 'page', required: false, description: 'Page number (default: 1)' })
@ApiQuery({ name: 'pageSize', required: false, description: 'Items per page (default: 20)' })
@ApiResponse({
status: 200,
description: 'Travelers retrieved successfully',
description: "Travelers retrieved successfully",
schema: {
example: {
items: [
{
id: 'uuid-123',
fullName: 'Abebe Kebede',
email: 'abebe@example.com',
phone: '+251911234567',
gender: 'Male',
dateOfBirth: '1985-03-15',
nationality: 'Ethiopian',
id: "uuid-123",
fullName: "Abebe Kebede",
email: "abebe@example.com",
phone: "+251911234567",
gender: "Male",
dateOfBirth: "1985-03-15",
nationality: "Ethiopian",
faydaVerified: true,
loyaltyTier: 'SILVER',
loyaltyTier: "SILVER",
loyaltyPoints: 1500,
totalBookings: 5,
createdAt: '2024-01-10T12:00:00.000Z'
}
createdAt: "2024-01-10T12:00:00.000Z",
},
],
meta: {
page: 1,
pageSize: 20,
total: 150,
totalPages: 8
}
}
}
totalPages: 8,
},
},
},
})
@ApiResponse({
status: 200,
description: 'Travelers retrieved successfully',
description: "Travelers retrieved successfully",
schema: {
example: {
items: [
{
id: 'uuid-123',
fullName: 'Abebe Kebede',
email: 'abebe@example.com',
phone: '+251911234567',
gender: 'Male',
dateOfBirth: '1985-03-15',
nationality: 'Ethiopian',
nationalityCode: 'ET',
id: "uuid-123",
fullName: "Abebe Kebede",
email: "abebe@example.com",
phone: "+251911234567",
gender: "Male",
dateOfBirth: "1985-03-15",
nationality: "Ethiopian",
nationalityCode: "ET",
faydaVerified: true,
faydaVerifiedAt: '2024-01-15T10:30:00.000Z',
faydaVerifiedAt: "2024-01-15T10:30:00.000Z",
passportNumber: null,
passportCountry: null,
passportExpiryDate: null,
idDocumentType: 'NATIONAL_ID',
idDocumentType: "NATIONAL_ID",
verified: true,
lastLoginAt: '2024-01-20T08:15:00.000Z',
role: 'PASSENGER',
lastLoginAt: "2024-01-20T08:15:00.000Z",
role: "PASSENGER",
loyalty: {
tier: 'SILVER',
tier: "SILVER",
pointsBalance: 1500,
lifetimePoints: 3000
lifetimePoints: 3000,
},
wallet: {
balanceMinor: 50000,
currency: 'ETB'
currency: "ETB",
},
loyaltyTier: 'SILVER',
loyaltyTier: "SILVER",
loyaltyPoints: 1500,
totalBookings: 5,
createdAt: '2024-01-10T12:00:00.000Z'
createdAt: "2024-01-10T12:00:00.000Z",
},
{
id: 'uuid-456',
fullName: 'Sara Ketsela',
id: "uuid-456",
fullName: "Sara Ketsela",
email: null,
phone: null,
gender: 'Female',
dateOfBirth: '1990-08-22',
nationality: 'Ethiopian',
gender: "Female",
dateOfBirth: "1990-08-22",
nationality: "Ethiopian",
nationalityCode: null,
faydaVerified: false,
faydaVerifiedAt: null,
@@ -150,54 +199,59 @@ export class PassengersController {
role: null,
loyalty: null,
wallet: null,
loyaltyTier: 'BRONZE',
loyaltyTier: "BRONZE",
loyaltyPoints: 0,
totalBookings: 1,
createdAt: '2024-01-18T14:30:00.000Z'
}
createdAt: "2024-01-18T14:30:00.000Z",
},
],
meta: {
page: 1,
pageSize: 20,
total: 150,
totalPages: 8
}
}
}
totalPages: 8,
},
},
},
})
findAll(
@Query('search') search?: string,
@Query('gender') gender?: string,
@Query('dateFrom') dateFrom?: string,
@Query('dateTo') dateTo?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query("search") search?: string,
@Query("gender") gender?: string,
@Query("dateFrom") dateFrom?: string,
@Query("dateTo") dateTo?: string,
@Query("page") page?: string,
@Query("pageSize") pageSize?: string,
) {
return this.service.findAll({
search,
return this.service.findAll({
search,
gender,
dateFrom,
dateTo,
page: page ? parseInt(page) : 1,
pageSize: pageSize ? parseInt(pageSize) : 20
page: page ? parseInt(page) : 1,
pageSize: pageSize ? parseInt(pageSize) : 20,
});
}
@Get('me')
@Get("me")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({
summary: 'Get current passenger profile',
description: 'Returns complete profile for authenticated passenger including passport details and verification status. Returns null if no passenger profile exists.'
@ApiBearerAuth("JWT-auth")
@ApiOperation({
summary: "Get current passenger profile",
description:
"Returns complete profile for authenticated passenger including passport details and verification status. Returns null if no passenger profile exists.",
})
@ApiResponse({
status: 200,
description: 'Passenger profile retrieved successfully or null if not found'
@ApiResponse({
status: 200,
description:
"Passenger profile retrieved successfully or null if not found",
})
@ApiResponse({
status: 401,
description: "Unauthorized - Invalid or missing token",
})
@ApiResponse({ status: 401, description: 'Unauthorized - Invalid or missing token' })
async getMe(@Request() req: any) {
if (!req.user || !req.user.id) {
throw new UnauthorizedException('User not authenticated');
throw new UnauthorizedException("User not authenticated");
}
try {
@@ -211,26 +265,26 @@ export class PassengersController {
}
}
@Get(':id/profile')
@Get(":id/profile")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get passenger profile' })
getProfile(@Param('id') id: string) {
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Get passenger profile" })
getProfile(@Param("id") id: string) {
return this.service.getProfile(id);
}
@Get(':id/stats')
@Get(":id/stats")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get passenger stats' })
getStats(@Param('id') id: string) {
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Get passenger stats" })
getStats(@Param("id") id: string) {
return this.service.getStats(id);
}
@Post('verify-fayda')
@SetMetadata('isPublic', true)
@Post("verify-fayda")
@SetMetadata("isPublic", true)
@ApiOperation({
summary: 'Verify Ethiopian national ID via Verifayda 2.0',
summary: "Verify Ethiopian national ID via Verifayda 2.0",
description: `**Standalone endpoint for pre-verification of Ethiopian national IDs**
---
@@ -277,32 +331,35 @@ Pre-verify national ID to auto-fill passenger registration form before submissio
- **Public endpoint** (no authentication required)
- Can be called before login/registration`,
})
@ApiResponse({
status: 200,
description: 'Verification successful with passenger data',
@ApiResponse({
status: 200,
description: "Verification successful with passenger data",
schema: {
example: {
verified: true,
passengerData: {
fullName: 'Abebe Kebede',
dateOfBirth: '1985-03-15T00:00:00.000Z',
gender: 'Male',
nationality: 'Ethiopian'
}
}
}
fullName: "Abebe Kebede",
dateOfBirth: "1985-03-15T00:00:00.000Z",
gender: "Male",
nationality: "Ethiopian",
},
},
},
})
@ApiResponse({
status: 400,
description: "Verification failed or Verifayda disabled",
})
@ApiResponse({ status: 400, description: 'Verification failed or Verifayda disabled' })
verifyFayda(@Body() dto: VerifyFaydaDto) {
return this.verifaydaService.verifyNationalId(dto.nationalId);
}
@Post('register')
@SetMetadata('isPublic', true)
@Post("register")
@SetMetadata("isPublic", true)
@UseGuards(OptionalJwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiBearerAuth("JWT-auth")
@ApiOperation({
summary: 'Universal passenger registration endpoint',
summary: "Universal passenger registration endpoint",
description: `**Single endpoint for all passenger registration scenarios**
---
@@ -357,45 +414,45 @@ The API automatically detects:
### Replaces
- Manual verification + save flows`,
})
@ApiResponse({
status: 201,
description: 'Passenger registered successfully',
@ApiResponse({
status: 201,
description: "Passenger registered successfully",
schema: {
example: {
id: 'uuid-123',
passengerName: 'Abebe Kebede',
dateOfBirth: '1985-03-15T00:00:00.000Z',
nationality: 'Ethiopian',
id: "uuid-123",
passengerName: "Abebe Kebede",
dateOfBirth: "1985-03-15T00:00:00.000Z",
nationality: "Ethiopian",
verified: true,
linked: false,
message: 'Passenger details saved for guest booking'
}
}
message: "Passenger details saved for guest booking",
},
},
})
@ApiResponse({
status: 400,
description: 'Validation error or verification failed',
@ApiResponse({
status: 400,
description: "Validation error or verification failed",
schema: {
example: {
statusCode: 400,
message: 'Validation failed',
error: 'Bad Request'
}
}
message: "Validation failed",
error: "Bad Request",
},
},
})
@ApiResponse({
status: 401,
description: 'Invalid JWT token (only if token provided but invalid)'
@ApiResponse({
status: 401,
description: "Invalid JWT token (only if token provided but invalid)",
})
registerPassenger(@Body() dto: RegisterPassengerDto, @Request() req: any) {
const userId = req.user?.id;
return this.service.registerPassenger({ ...dto, userId });
}
@Post('save-details')
@SetMetadata('isPublic', true)
@Post("save-details")
@SetMetadata("isPublic", true)
@ApiOperation({
summary: 'Bulk save passenger details from booking flow',
summary: "Bulk save passenger details from booking flow",
description: `**Endpoint for saving multiple passengers in a single booking**
---
@@ -427,105 +484,117 @@ Save all passenger details for a multi-passenger booking before proceeding to se
### Response
Returns saved passenger details with generated IDs and confirmation.`,
})
@ApiResponse({
status: 201,
description: 'All passenger details saved successfully',
@ApiResponse({
status: 201,
description: "All passenger details saved successfully",
schema: {
example: {
count: 2,
passengerIds: ['uuid-1', 'uuid-2'],
passengerIds: ["uuid-1", "uuid-2"],
passengers: [
{
id: 'uuid-1',
passengerName: 'Abebe Kebede',
dateOfBirth: '1985-03-15T00:00:00.000Z',
nationality: 'Ethiopian',
nationalId: 'ET123456789'
id: "uuid-1",
passengerName: "Abebe Kebede",
dateOfBirth: "1985-03-15T00:00:00.000Z",
nationality: "Ethiopian",
nationalId: "ET123456789",
},
{
id: 'uuid-2',
passengerName: 'Sara Ketsela',
dateOfBirth: '1990-08-22T00:00:00.000Z',
nationality: 'Ethiopian',
nationalId: 'ET987654321'
}
id: "uuid-2",
passengerName: "Sara Ketsela",
dateOfBirth: "1990-08-22T00:00:00.000Z",
nationality: "Ethiopian",
nationalId: "ET987654321",
},
],
message: 'Passenger details saved successfully'
}
}
message: "Passenger details saved successfully",
},
},
})
@ApiResponse({
status: 400,
description: "Validation error - passengers array required",
})
@ApiResponse({ status: 400, description: 'Validation error - passengers array required' })
savePassengers(@Body() dto: SavePassengersDto) {
return this.service.savePassengers(dto.passengers, dto.userId, dto.deviceId);
return this.service.savePassengers(
dto.passengers,
dto.userId,
dto.deviceId,
);
}
@Post('traveler-profiles')
@Post("traveler-profiles")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Add traveler profile (family member)' })
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Add traveler profile (family member)" })
createTravelerProfile(@Body() dto: CreateTravelerProfileDto) {
return this.service.createTravelerProfile(dto);
}
@Get(':id/traveler-profiles')
@Get(":id/traveler-profiles")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get traveler profiles for passenger' })
getTravelerProfiles(@Param('id') id: string) {
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Get traveler profiles for passenger" })
getTravelerProfiles(@Param("id") id: string) {
return this.service.getTravelerProfiles(id);
}
@Post('saved-routes')
@Post("saved-routes")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Save a route' })
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Save a route" })
createSavedRoute(@Body() dto: CreateSavedRouteDto) {
return this.service.createSavedRoute(dto);
}
@Get(':id/saved-routes')
@Get(":id/saved-routes")
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@ApiOperation({ summary: 'Get saved routes' })
getSavedRoutes(@Param('id') id: string) {
@ApiBearerAuth("JWT-auth")
@ApiOperation({ summary: "Get saved routes" })
getSavedRoutes(@Param("id") id: string) {
return this.service.getSavedRoutes(id);
}
@Patch(':id')
@SetMetadata('isPublic', true)
@ApiOperation({
summary: 'Update passenger details',
description: 'Updates passenger information for admin/agent operations'
@Patch(":id")
@SetMetadata("isPublic", true)
@ApiOperation({
summary: "Update passenger details",
description: "Updates passenger information for admin/agent operations",
})
@ApiResponse({ status: 200, description: 'Passenger updated successfully' })
@ApiResponse({ status: 404, description: 'Passenger not found' })
updatePassenger(@Param('id') id: string, @Body() dto: any) {
@ApiResponse({ status: 200, description: "Passenger updated successfully" })
@ApiResponse({ status: 404, description: "Passenger not found" })
updatePassenger(@Param("id") id: string, @Body() dto: any) {
return this.service.updatePassenger(id, dto);
}
@Delete(':id')
@Delete(":id")
@PassengerAdmin()
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Delete passenger (admin only)',
description: 'Permanently deletes a passenger record and associated data'
@ApiBearerAuth("IAM-auth")
@ApiOperation({
summary: "Delete passenger (admin only)",
description: "Permanently deletes a passenger record and associated data",
})
@ApiQuery({ name: 'cascade', required: false, type: Boolean, description: 'Force delete with all related bookings and data' })
@ApiResponse({ status: 200, description: 'Passenger deleted successfully' })
@ApiResponse({ status: 404, description: 'Passenger not found' })
deletePassenger(@Param('id') id: string, @Query('cascade') cascade?: string) {
return this.service.deletePassenger(id, cascade === 'true');
@ApiQuery({
name: "cascade",
required: false,
type: Boolean,
description: "Force delete with all related bookings and data",
})
@ApiResponse({ status: 200, description: "Passenger deleted successfully" })
@ApiResponse({ status: 404, description: "Passenger not found" })
deletePassenger(@Param("id") id: string, @Query("cascade") cascade?: string) {
return this.service.deletePassenger(id, cascade === "true");
}
@Get(':id/usage')
@SetMetadata('isPublic', true)
@ApiOperation({
summary: 'Check if passenger is in use',
description: 'Returns list of modules/data that reference this passenger'
@Get(":id/usage")
@SetMetadata("isPublic", true)
@ApiOperation({
summary: "Check if passenger is in use",
description: "Returns list of modules/data that reference this passenger",
})
@ApiResponse({ status: 200, description: 'Usage information retrieved' })
@ApiResponse({ status: 404, description: 'Passenger not found' })
checkUsage(@Param('id') id: string) {
@ApiResponse({ status: 200, description: "Usage information retrieved" })
@ApiResponse({ status: 404, description: "Passenger not found" })
checkUsage(@Param("id") id: string) {
return this.service.checkPassengerUsage(id);
}
}