define the controller and service for the airport management

This commit is contained in:
Sven Czarnian
2022-10-23 12:53:31 +02:00
parent 440477d741
commit a21eddda93
22 changed files with 796 additions and 1 deletions

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AirportController } from './airport.controller';
describe('AirportController', () => {
let controller: AirportController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AirportController],
}).compile();
controller = module.get<AirportController>(AirportController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,237 @@
import {
Body,
Controller,
Get,
Delete,
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';
@Controller('airport')
export class AirportController {
constructor(private readonly airportService: AirportService) {}
private static convertConstraint<T>(
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<T>(
constraints: Constraint[] | ConstraintDto[],
): T[] {
const retval: T[] = [];
constraints.forEach((constraint) =>
retval.push(AirportController.convertConstraint<T>(constraint)),
);
return retval;
}
private static convertArrivalRoutes<T, U>(
routes: ArrivalRoute[] | ArrivalRouteDto[],
): T[] {
const retval: T[] = [];
routes.forEach((route) =>
retval.push({
arrival: route.runway,
runway: route.runway,
constraints: AirportController.convertConstraints<U>(route.constraints),
} as T),
);
return retval;
}
private static convertRunwaySpacings<T>(
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<T>(
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<T, U>(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<U>(
planning.assignments,
),
} as T;
}
private static airportToAirportDto(airport: Airport): AirportDto {
if (!airport) return null;
return {
icao: airport.icao,
upperConstraints: AirportController.convertConstraints<ConstraintDto>(
airport.upperConstraints,
),
arrivalRoutes: AirportController.convertArrivalRoutes<
ArrivalRouteDto,
ConstraintDto
>(airport.arrivalRoutes),
spacings: AirportController.convertRunwaySpacings<RunwaySpacingDto>(
airport.spacings,
),
planning: AirportController.convertPlanning<PlanningDto, AssignmentDto>(
airport.planning,
),
};
}
private static airportDtoToAirport(airport: AirportDto): Airport {
if (!airport) return null;
return {
icao: airport.icao,
upperConstraints: AirportController.convertConstraints<Constraint>(
airport.upperConstraints,
),
arrivalRoutes: AirportController.convertArrivalRoutes<
ArrivalRoute,
Constraint
>(airport.arrivalRoutes),
spacings: AirportController.convertRunwaySpacings<RunwaySpacing>(
airport.spacings,
),
planning: AirportController.convertPlanning<Planning, Assignment>(
airport.planning,
),
};
}
@Get('/allCodes')
@ApiResponse({
status: 200,
description: 'All available airports',
type: [String],
})
async allCodes(): Promise<string[]> {
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<AirportDto> {
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',
})
async register(@Body('airport') airport: AirportDto): Promise<void> {
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<void> {
await this.airportService.airport(icao).then(async (airport) => {
if (!airport) {
throw new HttpException('Airport not found', HttpStatus.NOT_FOUND);
}
await this.airportService.deleteAirport(icao);
});
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { AirportSchema } from './models/airport.model';
import { AirportService } from './airport.service';
import { AirportController } from './airport.controller';
@Module({
imports: [
MongooseModule.forFeature([{ name: 'airport', schema: AirportSchema }]),
],
providers: [AirportService],
controllers: [AirportController],
exports: [AirportService],
})
export class AirportModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AirportService } from './airport.service';
describe('AirportService', () => {
let service: AirportService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AirportService],
}).compile();
service = module.get<AirportService>(AirportService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Airport, AirportDocument } from './models/airport.model';
@Injectable()
export class AirportService {
constructor(
@InjectModel('airport')
private readonly airportModel: Model<AirportDocument>,
) {}
private async airportExists(icao: string): Promise<boolean> {
return this.airportModel
.find({ icao })
.then((response) => response && response.length !== 0);
}
async airportsList(): Promise<string[]> {
return this.airportModel.find({}).then((response) => {
const icaoCodes: string[] = [];
response.forEach((airport) => icaoCodes.push(airport.icao));
return icaoCodes;
});
}
async airport(icao: string): Promise<Airport> {
return this.airportModel.find({ icao }).then((response) => {
if (!response || response.length !== 1) return null;
return response[0];
});
}
async registerAirport(airport: Airport): Promise<boolean> {
this.airportExists(airport.icao).then(async (exists) => {
if (!exists) {
await this.airportModel.create(airport);
}
return !exists;
});
return false;
}
async updateAirport(airport: Airport): Promise<boolean> {
this.airportExists(airport.icao).then(async (exists) => {
if (exists) {
await this.airportModel.findOneAndUpdate(
{ icao: airport.icao },
airport,
);
}
return exists;
});
return false;
}
async deleteAirport(icao: string): Promise<void> {
this.airportModel.deleteOne({ icao });
}
}

View File

@@ -0,0 +1,39 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ConstraintDto } from './constraint.dto';
import { ArrivalRouteDto } from './arrivalroute.dto';
import { RunwaySpacingDto } from './runwayspacing.dto';
import { PlanningDto } from './planning.dto';
export class AirportDto {
@IsNotEmpty()
@ApiProperty({
description: 'The unique ICAO code',
example: 'EDDB',
})
icao: string;
@IsOptional()
@ApiProperty({
description: 'The constraints in upper airspaces',
})
upperConstraints: ConstraintDto[];
@IsNotEmpty()
@ApiProperty({
description: 'The different arrival routes with potential constraints',
})
arrivalRoutes: ArrivalRouteDto[];
@IsNotEmpty()
@ApiProperty({
description: 'The spacings for the runways',
})
spacings: RunwaySpacingDto[];
@IsNotEmpty()
@ApiProperty({
description: 'The planning configuration',
})
planning: PlanningDto;
}

View File

@@ -0,0 +1,25 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ConstraintDto } from './constraint.dto';
export class ArrivalRouteDto {
@IsNotEmpty()
@ApiProperty({
description: 'The arrival route identifier',
example: 'KLF25L',
})
arrival: string;
@IsNotEmpty()
@ApiProperty({
description: 'The runway of the arrival route',
example: '25L',
})
runway: string;
@IsOptional()
@ApiProperty({
description: 'The constraints on the arrival route',
})
constraints: ConstraintDto[];
}

View File

@@ -0,0 +1,32 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AssignmentDto {
@IsNotEmpty()
@ApiProperty({
description: 'The runway identifier',
example: '25L',
})
runway: string;
@IsNotEmpty()
@ApiProperty({
description: 'The urgency of the assignment (possible: SHALL, SHOULD, MAY)',
example: 'MAY',
})
type: string;
@IsOptional()
@ApiProperty({
description: 'The aircrafts (ICAO codes) that require this assignment',
example: '[ "A346", "B748" ]',
})
aircrafts: string[];
@IsOptional()
@ApiProperty({
description: 'The assigned stands that require this assignment',
example: '[ "A01", "A02" ]',
})
assignedStands: string[];
}

View File

@@ -0,0 +1,26 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ConstraintTypeDto } from './constrainttype.dto';
export class ConstraintDto {
@IsNotEmpty()
@ApiProperty({
description: 'The identifier of the waypoint',
example: 'AKUDI',
})
waypoint: string;
@IsOptional()
@ApiProperty({
description: 'The altitude constraint in feet',
example: '{ constraint: 20000, mode: "BELOW" }',
})
altitude: ConstraintTypeDto;
@IsOptional()
@ApiProperty({
description: 'The speed constraint in knots',
example: '{ constraint: 250, mode: "EXACT" }',
})
speed: ConstraintTypeDto;
}

View File

@@ -0,0 +1,18 @@
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class ConstraintTypeDto {
@IsNotEmpty()
@ApiProperty({
description: 'The identifier of the waypoint',
example: 20000,
})
constraint: number;
@IsNotEmpty()
@ApiProperty({
description: 'The constraint mode (possible: BELOW, EXACT, ABOVE)',
example: 'EXACT',
})
mode: string;
}

View File

@@ -0,0 +1,34 @@
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class OptimizationDto {
@IsNotEmpty()
@ApiProperty({
description: 'The window length in seconds',
example: 120,
})
windowSize: number;
@IsNotEmpty()
@ApiProperty({
description: 'The optimized windows for the sequence',
example: 3,
})
windowOverlap: number;
@IsNotEmpty()
@ApiProperty({
description:
'The final approach sequence before the IAF is reached in minutes',
example: 10,
})
fixedBeforeInitialApproachFix: number;
@IsNotEmpty()
@ApiProperty({
description:
'The maximum distance to the airport before the optimization is started',
example: 300,
})
maximumAirportDistance: number;
}

View File

@@ -0,0 +1,18 @@
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { OptimizationDto } from './optimization.dto';
import { AssignmentDto } from './assignment.dto';
export class PlanningDto {
@IsNotEmpty()
@ApiProperty({
description: 'The optimization parameters',
})
optimization: OptimizationDto;
@IsNotEmpty()
@ApiProperty({
description: 'The runway assignments',
})
assignments: AssignmentDto[];
}

View File

@@ -0,0 +1,18 @@
import { IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class RunwaySpacingDto {
@IsNotEmpty()
@ApiProperty({
description: 'The runway identifier',
example: '25L',
})
runway: string;
@IsOptional()
@ApiProperty({
description: 'The touch-down sequence in nautical miles',
example: 4,
})
spacing: number;
}

View File

@@ -0,0 +1,42 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { ArrivalRoute, ArrivalRouteSchema } from './arrivalroute.model';
import { Constraint, ConstraintSchema } from './constraint.model';
import { Planning, PlanningSchema } from './planning.model';
import { RunwaySpacing, RunwaySpacingSchema } from './runwayspacing.model';
export type AirportDocument = Airport & Document;
@Schema()
export class Airport {
@Prop({
required: true,
type: String,
})
icao: string;
@Prop({
type: [ConstraintSchema],
})
upperConstraints: Constraint[];
@Prop({
required: true,
type: [ArrivalRouteSchema],
})
arrivalRoutes: ArrivalRoute[];
@Prop({
required: true,
type: [RunwaySpacingSchema],
})
spacings: RunwaySpacing[];
@Prop({
required: true,
type: PlanningSchema,
})
planning: Planning;
}
export const AirportSchema = SchemaFactory.createForClass(Airport);

View File

@@ -0,0 +1,27 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { Constraint, ConstraintSchema } from './constraint.model';
export type ArrivalRouteDocument = ArrivalRoute & Document;
@Schema()
export class ArrivalRoute {
@Prop({
required: true,
type: String,
})
arrival: string;
@Prop({
required: true,
type: String,
})
runway: string;
@Prop({
type: [ConstraintSchema],
})
constraints: Constraint[];
}
export const ArrivalRouteSchema = SchemaFactory.createForClass(ArrivalRoute);

View File

@@ -0,0 +1,32 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type AssignmentDocument = Assignment & Document;
@Schema()
export class Assignment {
@Prop({
required: true,
type: String,
})
runway: string;
@Prop({
required: true,
type: String,
enum: ['SHALL', 'SHOULD', 'MAY'],
})
type: string;
@Prop({
type: [String],
})
aircrafts: string[];
@Prop({
type: [String],
})
assignedStands: string[];
}
export const AssignmentSchema = SchemaFactory.createForClass(Assignment);

View File

@@ -0,0 +1,26 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { ConstraintType, ConstraintTypeSchema } from './constrainttype.model';
export type ConstraintDocument = Constraint & Document;
@Schema()
export class Constraint {
@Prop({
required: true,
type: String,
})
waypoint: string;
@Prop({
type: ConstraintTypeSchema,
})
altitude: ConstraintType;
@Prop({
type: ConstraintTypeSchema,
})
speed: ConstraintType;
}
export const ConstraintSchema = SchemaFactory.createForClass(Constraint);

View File

@@ -0,0 +1,22 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type ConstraintTypeDocument = ConstraintType & Document;
@Schema()
export class ConstraintType {
@Prop({
type: Number,
})
constraint: number;
@Prop({
type: String,
default: 'EXACT',
enum: ['BELOW', 'EXACT', 'ABOVE'],
})
mode: string;
}
export const ConstraintTypeSchema =
SchemaFactory.createForClass(ConstraintType);

View File

@@ -0,0 +1,37 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type OptimizationDocument = Optimization & Document;
@Schema()
export class Optimization {
@Prop({
required: true,
type: Number,
default: 120,
})
windowSize: number;
@Prop({
required: true,
type: Number,
default: 3,
})
windowOverlap: number;
@Prop({
required: true,
type: Number,
default: 10,
})
fixedBeforeInitialApproachFix: number;
@Prop({
required: true,
type: Number,
default: 300,
})
maximumAirportDistance: number;
}
export const OptimizationSchema = SchemaFactory.createForClass(Optimization);

View File

@@ -0,0 +1,23 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { Assignment, AssignmentSchema } from './assignment.model';
import { Optimization, OptimizationSchema } from './optimization.model';
export type PlanningDocument = Planning & Document;
@Schema()
export class Planning {
@Prop({
required: true,
type: OptimizationSchema,
})
optimization: Optimization;
@Prop({
required: true,
type: [AssignmentSchema],
})
assignments: Assignment[];
}
export const PlanningSchema = SchemaFactory.createForClass(Planning);

View File

@@ -0,0 +1,21 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type RunwaySpacingDocument = RunwaySpacing & Document;
@Schema()
export class RunwaySpacing {
@Prop({
required: true,
type: String,
})
runway: string;
@Prop({
required: true,
type: Number,
})
spacing: number;
}
export const RunwaySpacingSchema = SchemaFactory.createForClass(RunwaySpacing);

View File

@@ -4,8 +4,10 @@ import { MongooseModule } from '@nestjs/mongoose';
import Configuration from './config/configuration'; import Configuration from './config/configuration';
import { VersioningModule } from './versioning/versioning.module'; import { VersioningModule } from './versioning/versioning.module';
import { PerformanceModule } from './performance/performance.module'; import { PerformanceModule } from './performance/performance.module';
import { AirportModule } from './airport/airport.module';
import { LoggingModule } from './logging/logging.module'; import { LoggingModule } from './logging/logging.module';
import { LoggingController } from './logging/logging.controller'; import { LoggingController } from './logging/logging.controller';
import { AirportController } from './airport/airport.controller';
@Module({ @Module({
imports: [ imports: [
@@ -26,8 +28,9 @@ import { LoggingController } from './logging/logging.controller';
}), }),
VersioningModule, VersioningModule,
PerformanceModule, PerformanceModule,
AirportModule,
LoggingModule, LoggingModule,
], ],
controllers: [LoggingController], controllers: [LoggingController, AirportController],
}) })
export class AppModule {} export class AppModule {}