import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, ParseIntPipe } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; import { RoutesService } from './routes.service'; import { CreateRouteDto, AddRouteStopDto, UpdateRouteDto, SetRouteCoachTemplateDto } from './routes.dto'; import { PassengerAdmin, PassengerStaff } from '../../common/passenger-guards'; import { PASSENGER_PERMS } from '../../seed/passenger-permissions.registry'; @ApiTags('Routes') @Controller('routes') export class RoutesController { constructor(private service: RoutesService) {} // ── Routes ───────────────────────────────────────────────────────────────── @Post() @PassengerStaff([PASSENGER_PERMS.routes.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Create a reusable route with its ordered stops', description: `Define the physical corridor once (e.g. ADD→ADM→AWS→DDW→AYS→DJI). Schedules reference this route via routeId and supply actual planned times per stop. Route stops carry distanceKm for fare-by-distance calculations.`, }) @ApiResponse({ status: 201, description: 'Route created with stops' }) @ApiResponse({ status: 409, description: 'Route code already exists or duplicate sequences' }) @ApiResponse({ status: 400, description: 'Fewer than 2 stops or invalid station IDs' }) createRoute(@Body() dto: CreateRouteDto) { return this.service.createRoute(dto); } @Get() @ApiOperation({ summary: 'List all routes' }) @ApiQuery({ name: 'activeOnly', required: false, type: Boolean, description: 'Filter to active routes only' }) @ApiResponse({ status: 200, description: 'Array of routes with stop count' }) listRoutes(@Query('activeOnly') activeOnly?: string) { return this.service.listRoutes(activeOnly === 'true'); } @Get(':id') @ApiOperation({ summary: 'Get route with all stops and station details' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Route with enriched stop list (station name, code, city)' }) @ApiResponse({ status: 404, description: 'Route not found' }) getRoute(@Param('id') id: string) { return this.service.getRoute(id); } @Patch(':id') @PassengerStaff([PASSENGER_PERMS.routes.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Update route metadata (name, description, active flag, effectiveFrom, effectiveUntil)' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Route updated' }) @ApiResponse({ status: 404, description: 'Route not found' }) updateRoute(@Param('id') id: string, @Body() dto: UpdateRouteDto) { return this.service.updateRoute(id, dto); } @Delete(':id') @PassengerAdmin() @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Delete a route' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiQuery({ name: 'cascade', required: false, type: Boolean, description: 'Force delete with all related data' }) @ApiResponse({ status: 200, description: 'Route deleted' }) @ApiResponse({ status: 404, description: 'Route not found' }) deleteRoute(@Param('id') id: string, @Query('cascade') cascade?: string) { return this.service.deleteRoute(id, cascade === 'true'); } // ── Route Stops ──────────────────────────────────────────────────────────── @Get(':id/stops') @ApiOperation({ summary: 'List all stops for a route ordered by sequence' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Ordered stop list with station details' }) @ApiResponse({ status: 404, description: 'Route not found' }) getStops(@Param('id') id: string) { return this.service.getStops(id); } @Post(':id/stops') @PassengerStaff([PASSENGER_PERMS.routes.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Add a stop to an existing route' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 201, description: 'Stop added' }) @ApiResponse({ status: 409, description: 'Sequence already exists on this route' }) @ApiResponse({ status: 404, description: 'Route or station not found' }) addStop(@Param('id') id: string, @Body() dto: AddRouteStopDto) { return this.service.addStop(id, dto); } @Delete(':id/stops/:sequence') @PassengerAdmin() @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Remove a stop from a route by sequence number' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiParam({ name: 'sequence', description: 'Stop sequence number to remove' }) @ApiResponse({ status: 200, description: 'Stop removed' }) @ApiResponse({ status: 400, description: 'Cannot remove — route would have fewer than 2 stops' }) @ApiResponse({ status: 404, description: 'Stop not found' }) removeStop(@Param('id') id: string, @Param('sequence', ParseIntPipe) sequence: number) { return this.service.removeStop(id, sequence); } // ── Schedules for a Route ────────────────────────────────────────────────── @Get(':id/schedules') @ApiOperation({ summary: 'List all train schedules that use this route' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Schedules with train and terminal station details' }) @ApiResponse({ status: 404, description: 'Route not found' }) getSchedules(@Param('id') id: string) { return this.service.getSchedulesForRoute(id); } // ── Route Coach Template ─────────────────────────────────────────────────── @Get(':id/coaches') @ApiOperation({ summary: 'Get the default coach lineup for this route' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Ordered coach template with coach and coach type details' }) @ApiResponse({ status: 404, description: 'Route not found' }) getCoachTemplate(@Param('id') id: string) { return this.service.getRouteCoachTemplate(id); } @Put(':id/coaches') @PassengerStaff([PASSENGER_PERMS.routes.manage, PASSENGER_PERMS.admin]) @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Set the default coach lineup for this route', description: 'Replaces the entire coach template. Coaches are auto-assigned in this order when a new schedule is created for this route.', }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Updated coach template' }) @ApiResponse({ status: 400, description: 'Duplicate positions or inactive coach' }) @ApiResponse({ status: 404, description: 'Route or coach not found' }) setCoachTemplate(@Param('id') id: string, @Body() dto: SetRouteCoachTemplateDto) { return this.service.setRouteCoachTemplate(id, dto); } @Delete(':id/coaches') @PassengerAdmin() @ApiBearerAuth('IAM-auth') @ApiOperation({ summary: 'Clear the default coach lineup for this route' }) @ApiParam({ name: 'id', description: 'Route UUID' }) @ApiResponse({ status: 200, description: 'Template cleared' }) @ApiResponse({ status: 404, description: 'Route not found' }) clearCoachTemplate(@Param('id') id: string) { return this.service.removeRouteCoachTemplate(id); } }