mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 09:28:19 +00:00
54 lines
2.7 KiB
TypeScript
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);
|
|
}
|
|
}
|