import { Body, Controller, Get, Delete, Patch, Post, Query, HttpException, HttpStatus, } from '@nestjs/common'; import { ApiBody, ApiQuery, ApiResponse } from '@nestjs/swagger'; import { AirportService } from './airport.service'; import { Airport } from './models/airport.model'; import { AirportDto } from './dto/airport.dto'; import { ArrivalRouteDto } from './dto/arrivalroute.dto'; import { AssignmentDto } from './dto/assignment.dto'; import { ConstraintDto } from './dto/constraint.dto'; import { PlanningDto } from './dto/planning.dto'; import { RunwaySpacingDto } from './dto/runwayspacing.dto'; import { ArrivalRoute } from './models/arrivalroute.model'; import { Assignment } from './models/assignment.model'; import { Constraint } from './models/constraint.model'; import { Planning } from './models/planning.model'; import { RunwaySpacing } from './models/runwayspacing.model'; import { Waypoint } from '../generic/models/waypoint.model'; import { WaypointDto } from '../generic/dto/waypoint.dto'; import { Coordinate } from '../generic/models/coordinate.model'; import { CoordinateDto } from '../generic/dto/coordinate.dto'; import { CoordinateConverter, WaypointConverter } from '../generic/converters'; import { ActiveRunwaysDto } from './dto/activerunways.dto'; import { Runway } from './models/runway.model'; import { RunwayDto } from './dto/runway.dto'; @Controller('airport') export class AirportController { constructor(private readonly airportService: AirportService) {} private static convertConstraint( constraint: Constraint | ConstraintDto, ): T { return { waypoint: constraint.waypoint, altitude: { constraint: constraint.altitude.constraint, mode: constraint.altitude.mode, }, speed: { constraint: constraint.speed.constraint, mode: constraint.speed.mode, }, } as T; } private static convertConstraints( constraints: Constraint[] | ConstraintDto[], ): T[] { const retval: T[] = []; constraints.forEach((constraint) => retval.push(AirportController.convertConstraint(constraint)), ); return retval; } private static convertArrivalRoutes( routes: ArrivalRoute[] | ArrivalRouteDto[], ): T[] { const retval: T[] = []; routes.forEach((route) => retval.push({ arrival: route.runway, runway: route.runway, waypoints: WaypointConverter.convertList(route.waypoints), constraints: AirportController.convertConstraints(route.constraints), } as T), ); return retval; } private static convertRunwaySpacings( spacings: RunwaySpacing[] | RunwaySpacingDto[], ): T[] { const retval: T[] = []; spacings.forEach((spacing) => retval.push({ runway: spacing.runway, spacing: spacing.spacing, } as T), ); return retval; } private static convertAssignments( assignments: Assignment[] | AssignmentDto[], ): T[] { const retval: T[] = []; assignments.forEach((assignment) => retval.push({ runway: assignment.runway, type: assignment.type, aircrafts: assignment.aircrafts, assignedStands: assignment.assignedStands, } as T), ); return retval; } private static convertPlanning(planning: Planning | PlanningDto): T { return { optimization: { windowSize: planning.optimization.windowSize, windowOverlap: planning.optimization.windowOverlap, fixedBeforeInitialApproachFix: planning.optimization.fixedBeforeInitialApproachFix, maximumAirportDistance: planning.optimization.maximumAirportDistance, }, assignments: AirportController.convertAssignments( planning.assignments, ), } as T; } private static convertRunways(runways: Runway[] | RunwayDto[]): T[] { const retval: T[] = []; runways.forEach((runway) => retval.push({ identifier: runway.identifier, threshold: CoordinateConverter.convert(runway.threshold), } as T), ); return retval; } private static airportToAirportDto(airport: Airport): AirportDto { if (!airport) return null; return { icao: airport.icao, location: WaypointConverter.convert( airport.location, ), elevation: airport.elevation, runways: AirportController.convertRunways(airport.runways), upperConstraints: AirportController.convertConstraints( airport.upperConstraints, ), arrivalRoutes: AirportController.convertArrivalRoutes< ArrivalRouteDto, ConstraintDto, WaypointDto, CoordinateDto >(airport.arrivalRoutes), spacings: AirportController.convertRunwaySpacings( airport.spacings, ), planning: AirportController.convertPlanning( airport.planning, ), activeRunways: airport.activeRunways, arrivalMode: airport.arrivalMode, }; } private static airportDtoToAirport(airport: AirportDto): Airport { if (!airport) return null; return { icao: airport.icao, location: WaypointConverter.convert( airport.location, ), elevation: airport.elevation, runways: AirportController.convertRunways(airport.runways), upperConstraints: AirportController.convertConstraints( airport.upperConstraints, ), arrivalRoutes: AirportController.convertArrivalRoutes< ArrivalRoute, Constraint, Waypoint, Coordinate >(airport.arrivalRoutes), spacings: AirportController.convertRunwaySpacings( airport.spacings, ), planning: AirportController.convertPlanning( airport.planning, ), activeRunways: airport.activeRunways, arrivalMode: airport.arrivalMode, }; } private static validateAirportEnums(airport: AirportDto): void { airport.planning.assignments.forEach((assignment) => { if ( assignment.type !== 'SHALL' && assignment.type !== 'SHOULD' && assignment.type !== 'MAY' ) { throw new HttpException( 'Invalid assignment type', HttpStatus.UNPROCESSABLE_ENTITY, ); } }); } private static validateConstraint(constraint: ConstraintDto): void { if ( constraint.altitude.mode !== 'BELOW' && constraint.altitude.mode !== 'EXACT' && constraint.altitude.mode !== 'ABOVE' ) { throw new HttpException( 'Invalid altitude constraint mode', HttpStatus.UNPROCESSABLE_ENTITY, ); } if ( constraint.speed.mode !== 'BELOW' && constraint.speed.mode !== 'EXACT' && constraint.speed.mode !== 'ABOVE' ) { throw new HttpException( 'Invalid speed constraint mode', HttpStatus.UNPROCESSABLE_ENTITY, ); } } private static validateAirportConstraints(airport: AirportDto): void { airport.upperConstraints.forEach((constraint) => AirportController.validateConstraint(constraint), ); airport.arrivalRoutes.forEach((route) => { route.constraints.forEach((constraint) => AirportController.validateConstraint(constraint), ); }); } @Get('/allCodes') @ApiResponse({ status: 200, description: 'All available airports', type: [String], }) async allCodes(): Promise { return this.airportService.airportsList(); } @Get('/get') @ApiQuery({ name: 'icao', description: 'The ICAO code of the airport', type: String, }) @ApiResponse({ status: 200, description: 'The airport configuration', type: [AirportDto], }) @ApiResponse({ status: 404, description: 'No airport found', }) async get(@Query('icao') icao: string): Promise { return this.airportService.airport(icao).then((airport) => { if (!airport) { throw new HttpException('Airport not found', HttpStatus.NOT_FOUND); } return AirportController.airportToAirportDto(airport); }); } @Post('/register') @ApiBody({ description: 'The airport definition', type: AirportDto, }) @ApiResponse({ status: 201, description: 'The airport is registered', }) @ApiResponse({ status: 409, description: 'The airport is already registered', }) @ApiResponse({ status: 422, description: 'The assignment type is invalid', }) @ApiResponse({ status: 422, description: 'The constraint type is invalid', }) async register(@Body('airport') airport: AirportDto): Promise { AirportController.validateAirportConstraints(airport); AirportController.validateAirportEnums(airport); await this.airportService .registerAirport(AirportController.airportDtoToAirport(airport)) .then((registered) => { if (!registered) { throw new HttpException( 'Airport already available', HttpStatus.CONFLICT, ); } }); } @Delete('/remove') @ApiQuery({ name: 'icao', description: 'The ICAO code of the airport', type: String, }) @ApiResponse({ status: 200, description: 'All log entries are deleted', }) @ApiResponse({ status: 404, description: 'Could not find the airport', }) async remove(@Query('icao') icao: string): Promise { await this.airportService.airport(icao).then(async (airport) => { if (!airport) { throw new HttpException('Airport not found', HttpStatus.NOT_FOUND); } await this.airportService.deleteAirport(icao); }); } @Patch('/activateRunways') @ApiBody({ description: 'The airport definition', type: ActiveRunwaysDto, }) @ApiResponse({ status: 200, description: 'The active runways are set', }) @ApiResponse({ status: 404, description: 'The airport or the runways are not found', }) @ApiResponse({ status: 422, description: 'The arrival mode is invalid', }) async activeRunways( @Body('runways') runways: ActiveRunwaysDto, ): Promise { if (runways.mode !== 'STAGGERED' && runways.mode !== 'IPA') { throw new HttpException( 'Invalid arrival mode', HttpStatus.UNPROCESSABLE_ENTITY, ); } await this.airportService.activateRunways(runways).then((updated) => { if (!updated) { throw new HttpException('Airport not found', HttpStatus.NOT_FOUND); } }); } }