Files
edr-platform/apps/edr-passenger-api/src/modules/search/group-search.controller.ts

54 lines
2.7 KiB
TypeScript

import { Body, Controller, Post } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { SearchService } from './search.service';
import { SearchTripsDto } from './search.dto';
import { PassengerWrite } from '../../common/passenger-guards';
import { PASSENGER_PERMS } from '../../seed/passenger-permissions.registry';
/**
* The staff group-booking search channel.
*
* Deliberately a SEPARATE controller from `SearchController` rather than another route on it.
* `SearchController` carries a class-level `@IsPublic()`, and `JwtGuard` resolves that key with
* `getAllAndOverride([handler, class])` — a handler with no key of its own inherits the class's
* `true` and stays public. Worse, `JwtGuard` returns early for a public route and never
* populates `request.user`, so an in-handler permission check there is not merely awkward, it is
* impossible: there is no user to check.
*
* Splitting the controller is what makes the guard real. The channel is therefore decided by
* which route the caller can reach, not by a field they send — which matters because this
* channel exposes unpublished DRAFT schedules.
*/
@ApiTags('Search')
@Controller('search')
export class GroupSearchController {
constructor(private service: SearchService) {}
@Post('group')
@PassengerWrite(PASSENGER_PERMS.bookings.create, PASSENGER_PERMS.bookings.manage)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Search trips as staff for a group booking — includes unpublished DRAFT trips',
description: `Same request body and response shape as the public \`POST /search\`, but run on the staff channel.
The staff channel is a strict **superset** of the public one:
| Schedule state | Public \`POST /search\` | This route |
| --- | --- | --- |
| \`DRAFT\` (not yet published) | hidden | **visible** |
| \`SCHEDULED\`/\`BOARDING\`/\`EN_ROUTE\`, ordinary | visible | visible |
| \`SCHEDULED\`/\`BOARDING\`/\`EN_ROUTE\`, \`isGroupBookingOnly\` | hidden | **visible** |
| \`CANCELLED\`/\`ARRIVED\`/\`DELAYED\` | hidden | hidden |
| \`isPackageOnly\` | hidden | hidden |
So a group can be booked onto an ordinary scheduled service, onto a trip reserved for groups, or onto a draft trip that has not gone on sale yet — all through the same seat inventory as normal booking.
Replaces the old \`channel: 'GROUP_BOOKING'\` body field on \`POST /search\`, which was unguarded.`,
})
@ApiResponse({ status: 200, description: 'Matching schedules, including DRAFT and group-reserved ones' })
@ApiResponse({ status: 403, description: 'Caller lacks bookings:create / bookings:manage' })
searchTripsForGroup(@Body() dto: SearchTripsDto) {
return this.service.searchTrips(dto, true);
}
}