Feature/33 api fails when no pilots or logbook entries (#35)

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* api no longer fails when no pilots or logbook entries

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* refactoring

* refactoring

* refactoring

* refactoring

* adding test environment

* adding test environment
This commit was merged in pull request #35.
This commit is contained in:
2025-02-02 23:24:48 +00:00
committed by GitHub
parent 2199c6d016
commit 468837c985
97 changed files with 13360 additions and 27219 deletions

View File

@@ -3,54 +3,23 @@ import {
Get,
Headers,
Query,
Res,
StreamableFile
StreamableFile,
UseGuards
} from '@nestjs/common';
import {
AppConfigService,
MsGraphService,
MsGraphClient
} from '@noahspan/noahspan-modules';
import { FeatureFlagValue } from '@azure/app-configuration';
import { Public } from '@noahspan/noahspan-modules';
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
import { MsGraphService } from './msGraph/ms-graph.service'
import { Person } from '@microsoft/microsoft-graph-types';
import { AppService } from './app.service';
import { createReadStream } from 'fs';
import { join } from 'path';
import { arrayBuffer } from 'stream/consumers';
import type { Response } from 'express';
import { AuthGuard } from '@nestjs/passport';
@Controller()
@UseGuards(AuthGuard('azure-ad'))
export class AppController {
constructor(
private readonly appService: AppService,
private readonly appConfigService: AppConfigService,
private readonly msGraphService: MsGraphService
) {}
@Public()
@Get('featureFlags')
async getFeatureFlags(
@Query() query: any
): Promise<{ key: string; enabled: boolean }[]> {
try {
const featureFlagKeys: string[] =
query.keys && query.keys.toString().includes(';')
? query.keys.split(';')
: [query.keys];
const featureFlagLabel: string = query.label;
const featureFlags: { key: string; enabled: boolean }[] =
await this.appConfigService.getFeatureFlags(
featureFlagKeys,
featureFlagLabel
);
return featureFlags;
} catch (error) {
return error;
}
}
@Get('userPhoto')
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
try {
@@ -106,7 +75,6 @@ export class AppController {
}
}
@Public()
@Get('hello')
async getHello(): Promise<string> {
return this.appService.getHello();

View File

@@ -1,47 +1,49 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {
AppConfigModule,
AuthModule,
AuthGuard,
MsGraphModule
} from '@noahspan/noahspan-modules';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { LogbookModule } from './logbook/logbook.module';
import { AuthModule } from './auth/auth.module';
import { MsGraphModule } from './msGraph/ms-graph.module';
import { FeatureFlagModule } from './featureFlag/feature-flag.module'
import { LogModule } from './log/log.module';
import { PilotModule } from './pilot/pilot.module';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot(),
AppConfigModule.register({
url: process.env.APP_CONFIG_URL,
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
AuthModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
clientId: configService.get<string>('clientId'),
clientSecret: configService.get<string>('clientSecret'),
tenantId: configService.get<string>('tenantId')
};
},
inject: [ConfigService]
}),
AuthModule.register({
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
ConfigModule.forRoot({
load: [configuration]
}),
MsGraphModule.register({
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
FeatureFlagModule,
LogModule,
MsGraphModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
clientId: configService.get<string>('clientId'),
clientSecret: configService.get<string>('clientSecret'),
tenantId: configService.get<string>('tenantId')
};
},
inject: [ConfigService]
}),
LogbookModule,
PilotModule
],
controllers: [AppController],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard
},
{
provide: APP_FILTER,
useClass: HttpExceptionFilter

View File

@@ -1,12 +1,14 @@
import { Injectable } from '@nestjs/common';
import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
// import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
import { MsGraphService } from './msGraph/ms-graph.service';
@Injectable()
export class AppService {
constructor(private readonly msGraphService: MsGraphService) {}
getHello(): string {
return 'Hello World!';
return JSON.stringify(process.env);
}
async getPersonSearchResults(

View File

@@ -0,0 +1,5 @@
export interface AuthModuleOptions {
tenantId: string;
clientId: string;
clientSecret: string;
}

View File

@@ -0,0 +1,4 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { AuthModuleOptions } from './auth.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<AuthModuleOptions>().build()

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { AzureAdStrategy } from './auth.strategy';
import { ConfigurableModuleClass } from './auth.module-definition';
@Module({
imports: [
PassportModule.register({
defaultStrategy: 'azure-ad'
})
],
providers: [AzureAdStrategy]
})
export class AuthModule extends ConfigurableModuleClass {}

View File

@@ -0,0 +1,26 @@
import { Inject, Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { AuthModuleOptions } from './auth.interface'
import { MODULE_OPTIONS_TOKEN } from "./auth.module-definition";
import { BearerStrategy } from 'passport-azure-ad'
@Injectable()
export class AzureAdStrategy extends PassportStrategy(
BearerStrategy,
'azure-ad'
) {
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
console.log(authModuleOptions)
super({
identityMetadata: `https://login.microsoftonline.com/${authModuleOptions.tenantId}/.well-known/openid-configuration`,
clientID: authModuleOptions.clientId,
audience: `api://${authModuleOptions.clientId}`,
loggingLevel: 'info',
loggingNoPII: false
})
}
async validate(data: any): Promise<any> {
return data;
}
}

View File

@@ -0,0 +1,6 @@
export default () => ({
azureStorageConnectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
tenantId: process.env.TENANT_ID
})

View File

@@ -6,4 +6,4 @@ export class CustomError extends Error {
this.name = name;
this.statusCode = statusCode;
}
}
}

View File

@@ -0,0 +1,41 @@
import {
Controller,
Get,
HttpException,
Param,
UseGuards,
} from '@nestjs/common';
import { FeatureFlagService } from './feature-flag.service';
import { CustomError } from '../error/customError';
import { AuthGuard } from '@nestjs/passport';
@Controller('featureFlags')
@UseGuards(AuthGuard('azure-ad'))
export class FeatureFlagController {
constructor(private readonly featureFlagService: FeatureFlagService) {}
@Get(':partitionKey/:rowKey')
async find(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string
) {
try {
return await this.featureFlagService.find(partitionKey, rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.featureFlagService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,5 @@
export class FeatureFlagDto {
partitionKey: string;
rowKey: string;
active: string;
}

View File

@@ -0,0 +1,7 @@
import { EntityString } from '@noahspan/azure-database';
export class FeatureFlag {
@EntityString() partitionKey: string;
@EntityString() rowKey: string;
@EntityString() active: string;
}

View File

@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { FeatureFlagController } from './feature-flag.controller';
import { FeatureFlagService } from './feature-flag.service';
import { AzureTableStorageModule } from '@noahspan/azure-database';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { FeatureFlag } from './feature-flag.entity';
@Module({
imports: [
AzureTableStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
connectionString: configService.get<string>('azureStorageConnectionString')
};
},
inject: [ConfigService]
}),
AzureTableStorageModule.forFeature(FeatureFlag, {
createTableIfNotExists: false,
table: 'featureFlags'
}),
],
controllers: [FeatureFlagController],
providers: [FeatureFlagService]
})
export class FeatureFlagModule {}

View File

@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository, Repository } from '@noahspan/azure-database';
import { FeatureFlag } from './feature-flag.entity';
@Injectable()
export class FeatureFlagService {
constructor(
@InjectRepository(FeatureFlag) private readonly featureFlagRepository: Repository<FeatureFlag>
) {}
async find(partitionKey: string, rowKey: string): Promise<FeatureFlag> {
return await this.featureFlagRepository.find(partitionKey, rowKey);
}
async findAll(): Promise<FeatureFlag[]> {
return await this.featureFlagRepository.findAll();
}
}

View File

@@ -13,7 +13,6 @@ export class HttpExceptionFilter implements ExceptionFilter {
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = excpetion.getStatus();
console.log(excpetion.cause);
response.status(status).json({
name: excpetion.cause,

View File

@@ -0,0 +1,97 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { LogDto } from './log.dto';
import { Log } from './log.entity';
import { LogService } from './log.service';
import { CustomError } from '../error/customError';
import { AuthGuard } from '@nestjs/passport';
@Controller('logs')
@UseGuards(AuthGuard('azure-ad'))
export class LogController {
constructor(private readonly logService: LogService) {}
@Get(':partitionKey/:rowKey')
async find(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string
): Promise<Log> {
try {
return await this.logService.find(partitionKey, rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
// @Public()
@Get()
async findAll(): Promise<Log[]> {
try {
return await this.logService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
async create(@Body() logDto: LogDto): Promise<Log> {
try {
const log = new Log();
Object.assign(log, logDto);
return await this.logService.create(log);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':partitionKey/:rowKey')
async update(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string,
@Body() logDto: LogDto
): Promise<Log> {
try {
const log = new Log();
Object.assign(log, logDto);
return await this.logService.update(partitionKey, rowKey, log);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':partitionKey/:rowKey')
async delete(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string
): Promise<void> {
try {
return await this.logService.delete(partitionKey, rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -1,4 +1,4 @@
export class LogbookDto {
export class LogDto {
pilotId: string;
pilotName: string;
date: string;

View File

@@ -1,4 +1,4 @@
export class LogbookEntity {
export class Log {
partitionKey: string;
rowKey: string;
pilotId: string;

27
api/src/log/log.module.ts Normal file
View File

@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { LogController } from './log.controller';
import { LogService } from './log.service';
import { AzureTableStorageModule } from '@noahspan/azure-database';
import { Log } from './log.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
AzureTableStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
connectionString: configService.get<string>('azureStorageConnectionString')
};
},
inject: [ConfigService]
}),
AzureTableStorageModule.forFeature(Log, {
createTableIfNotExists: false,
table: 'logs'
}),
],
controllers: [LogController],
providers: [LogService]
})
export class LogModule {}

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository, Repository } from '@noahspan/azure-database';
import { Log } from './log.entity';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class LogService {
constructor(
@InjectRepository(Log) private readonly logRepository: Repository<Log>
) {}
async find(partitionKey: string, rowKey: string): Promise<Log> {
return await this.logRepository.find(partitionKey, rowKey);
}
async findAll(): Promise<Log[]> {
return await this.logRepository.findAll();
}
async create(log: Log): Promise<Log> {
log.partitionKey = 'log';
log.rowKey = uuidv4();
return await this.logRepository.create(log);
}
async update(partitionKey: string, rowKey: string, log: Log): Promise<Log> {
return await this.logRepository.update(partitionKey, rowKey, log);
}
async delete(partitionKey: string, rowKey: string): Promise<void> {
await this.logRepository.delete(partitionKey, rowKey);
}
}

View File

@@ -1,96 +0,0 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put
} from '@nestjs/common';
import { LogbookService } from './logbook.service';
import { LogbookDto } from './logbook.dto';
import { CustomError } from '../customError/CustomError';
import { TableInsertEntityHeaders } from '@azure/data-tables';
import { LogbookEntity } from './logbook.entity';
@Controller('logbook')
export class LogbookController {
constructor(private readonly logbookService: LogbookService) {}
@Get(':entryId')
async find(@Param() params: any): Promise<LogbookEntity> {
try {
const entry: LogbookEntity = await this.logbookService.find(
params.entryId
);
return entry;
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
}
}
@Get()
async findAll(): Promise<LogbookEntity[]> {
try {
const logbookEntries: LogbookEntity[] =
await this.logbookService.findAll();
return logbookEntries;
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
}
}
@Post()
async create(@Body() logbookData: LogbookDto): Promise<void> {
try {
const response: TableInsertEntityHeaders =
await this.logbookService.create(logbookData);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
}
}
@Put(':entryId')
async update(
@Param() params: any,
@Body() logbookData: LogbookDto
): Promise<void> {
try {
return await this.logbookService.update(logbookData);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
}
}
@Delete(':rowKey')
async delete(@Param() params: any): Promise<void> {
try {
await this.logbookService.delete(params.rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
}
}
}

View File

@@ -1,20 +0,0 @@
import { Module } from '@nestjs/common';
import { LogbookController } from './logbook.controller';
import { LogbookService } from './logbook.service';
import { TableModule } from '@noahspan/noahspan-modules';
@Module({
imports: [
TableModule.register({
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
allowInsecureConnection: Boolean(
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
)
})
],
controllers: [LogbookController],
providers: [LogbookService]
})
export class LogbookModule {}

View File

@@ -1,183 +0,0 @@
import { Injectable } from '@nestjs/common';
import { TableClient, TableService } from '@noahspan/noahspan-modules';
import { LogbookDto } from './logbook.dto';
import { LogbookEntity } from './logbook.entity';
import { RestError, TableInsertEntityHeaders } from '@azure/data-tables';
import { CustomError } from '../customError/CustomError';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class LogbookService {
private readonly tableName = 'Logbook';
constructor(private readonly tableService: TableService) {}
async getLogbookEntries(filter: string): Promise<LogbookEntity[]> {
try {
const client: TableClient = await this.tableService.getTableClient(
this.tableName
);
const entities = await client.listEntities({
queryOptions: { filter: filter }
});
const logbookEntries: LogbookEntity[] = [];
for await (const entity of entities) {
const logbookEntry = {
partitionKey: entity.partitionKey.toString(),
rowKey: entity.rowKey.toString(),
pilotId: entity.pilotId.toString(),
pilotName: entity.pilotName.toString(),
date: entity.date.toString(),
aircraftMakeModel: entity.aircraftMakeModel.toString(),
aircraftIdentity: entity.aircraftIdentity.toString(),
routeFrom: entity.routeFrom.toString(),
routeTo: entity.routeTo.toString(),
durationOfFlight: Number(entity.durationOfFlight),
singleEngineLand: entity.singleEngineLand
? Number(entity.singleEngineLand)
: null,
simulatorAtd: entity.simulatorAtd
? Number(entity.simulatorAtd)
: null,
landingsDay: entity.landingsDay ? Number(entity.landingsDay) : null,
landingsNight: entity.landingsNight
? Number(entity.landingsNight)
: null,
groundTrainingReceived: entity.groundTrainingReceived
? Number(entity.groundTrainingReceived)
: null,
flightTrainingReceived: entity.flightTrainingReceived
? Number(entity.flightTrainingReceived)
: null,
crossCountry: entity.crossCountry
? Number(entity.crossCountry)
: null,
night: entity.night ? Number(entity.night) : null,
solo: entity.solo ? Number(entity.solo) : null,
pilotInCommand: entity.pilotInCommand
? Number(entity.pilotInCommand)
: null,
instrumentActual: entity.instrumentActual
? Number(entity.instrumentActual)
: null,
instrumentSimulated: entity.instrumentSimulated
? Number(entity.instrumentSimulated)
: null,
instrumentApproaches: entity.instrumentApproaches
? Number(entity.instrumentApproaches)
: null,
instrumentHolds: entity.instrumentHolds
? Number(entity.instrumentHolds)
: null,
instrumentNavTrack: entity.instrumentNavTrack
? Number(entity.instrumentNavTrack)
: null,
notes: entity.notes ? entity.notes.toString() : ''
};
logbookEntries.push(logbookEntry);
}
return logbookEntries;
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async find(entryId: string): Promise<LogbookEntity> {
try {
const filter: string = `PartitionKey eq 'entry' and RowKey eq '${entryId}'`;
const entries: LogbookEntity[] = await this.getLogbookEntries(filter);
return entries[0];
} catch (error) {
throw error;
}
}
async findAll(): Promise<LogbookEntity[]> {
try {
const filter: string = `PartitionKey eq 'entry'`;
const entries: LogbookEntity[] = await this.getLogbookEntries(filter);
return entries;
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async create(logbookData: LogbookDto): Promise<TableInsertEntityHeaders> {
const client: TableClient = await this.tableService.getTableClient(
this.tableName
);
const logbook: LogbookEntity = new LogbookEntity();
Object.assign(logbook, logbookData);
logbook.partitionKey = 'entry';
logbook.rowKey = uuidv4();
try {
return await client.createEntity(logbook);
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async update(logbookData: LogbookDto): Promise<void> {
try {
const client: TableClient = await this.tableService.getTableClient(
this.tableName
);
const logbook: LogbookEntity = new LogbookEntity();
Object.assign(logbook, logbookData);
await client.upsertEntity(logbook, 'Replace');
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async delete(rowKey: string): Promise<void> {
try {
const client: TableClient = await this.tableService.getTableClient(
this.tableName
);
await client.deleteEntity('entry', rowKey);
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
}

View File

@@ -1,11 +0,0 @@
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
export async function createApp(): Promise<INestApplication> {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
await app.init();
return app;
}

View File

@@ -3,13 +3,16 @@ import { AppModule } from './app.module';
import { HttpService } from '@nestjs/axios';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { InternalServerErrorException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
async function bootstrap() {
const httpService = new HttpService();
const app = await NestFactory.create(AppModule);
app.enableCors();
app.setGlobalPrefix('api');
app.useGlobalFilters(new HttpExceptionFilter());
httpService.axiosRef.interceptors.response.use(
(response) => {
return response;
@@ -20,6 +23,7 @@ async function bootstrap() {
throw new InternalServerErrorException();
}
);
await app.listen(3000);
}

View File

@@ -0,0 +1,5 @@
export interface MsGraphModuleOptions {
tenantId: string;
clientId: string;
clientSecret: string;
}

View File

@@ -0,0 +1,4 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { MsGraphModuleOptions } from './ms-graph.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<MsGraphModuleOptions>().build()

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { MsGraphService } from './ms-graph.service';
import { ConfigurableModuleClass } from './ms-graph.module-definition';
@Module({
providers: [MsGraphService],
exports: [MsGraphService]
})
export class MsGraphModule extends ConfigurableModuleClass {}

View File

@@ -0,0 +1,41 @@
import { Inject, Injectable } from '@nestjs/common';
import { MsGraphModuleOptions } from './ms-graph.interface';
import { Client } from '@microsoft/microsoft-graph-client';
import { AuthenticationResult, ConfidentialClientApplication, OnBehalfOfRequest } from '@azure/msal-node';
import { MODULE_OPTIONS_TOKEN } from './ms-graph.module-definition';
@Injectable()
export class MsGraphService {
constructor(@Inject(MODULE_OPTIONS_TOKEN) private msGraphModuleOptions: MsGraphModuleOptions) {}
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
try {
const oboRequest: OnBehalfOfRequest = {
oboAssertion: accessToken,
scopes: scopes
}
const cca = new ConfidentialClientApplication({
auth: {
clientId: this.msGraphModuleOptions.clientId,
clientSecret: this.msGraphModuleOptions.clientSecret,
authority: `https://login.microsoftonline.com/${this.msGraphModuleOptions.tenantId}`
}
});
const authenticationResult: AuthenticationResult = await cca.acquireTokenOnBehalfOf(oboRequest);
return authenticationResult.accessToken
} catch (error) {
return error
}
}
async getMsGraphClientDelegated(accessToken): Promise<Client> {
const client = await Client.init({
authProvider: (done) => {
done(null, accessToken);
}
});
return client;
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Repository, InjectRepository } from '@noahspan/azure-database';
import { Certificate } from './certificate.entity';
@Injectable()

View File

@@ -1,4 +1,4 @@
import { InjectRepository, Repository } from '@nestjs/azure-database';
import { InjectRepository, Repository } from '@noahspan/azure-database';
import { Injectable } from '@nestjs/common';
import { Endorsement } from './endorsement.entity';

View File

@@ -1,12 +0,0 @@
export class PilotInfoEntity {
partitionKey: string;
rowKey: string;
id: string;
name: string;
address?: string;
city?: string;
state?: string;
postalCode?: string;
email?: string;
phone?: string;
}

View File

@@ -1,117 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PilotInfoDto } from './pilot-info.dto';
import { PilotInfoEntity } from './pilot-info.entity';
import { TableClient, TableService } from '@noahspan/noahspan-modules';
import { odata, RestError, TableInsertEntityHeaders } from '@azure/data-tables';
import { CustomError } from '../../customError/CustomError';
@Injectable()
export class PilotInfoService {
private readonly partitionKey: string = 'info';
constructor(private readonly tableService: TableService) {}
async find(pilotId: string): Promise<PilotInfoEntity> {
try {
const client: TableClient =
await this.tableService.getTableClient('Pilots');
const entities = await client.listEntities({
queryOptions: {
filter: odata`PartitionKey eq 'pilot' and RowKey eq '${pilotId}'`
}
});
let pilot: PilotInfoEntity;
console.log(entities);
for await (const entity of entities) {
pilot = {
partitionKey: entity.partitionKey,
rowKey: entity.rowKey,
id: entity.id.toString(),
name: entity.name.toString(),
address: entity.address.toString(),
city: entity.city.toString(),
state: entity.state.toString(),
postalCode: entity.postalCode.toString(),
email: entity.email.toString(),
phone: entity.phone.toString()
};
}
return pilot;
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async findAll(): Promise<PilotInfoEntity[]> {
try {
const client: TableClient =
await this.tableService.getTableClient('Pilots');
const entities = await client.listEntities({
queryOptions: { filter: odata`PartitionKey eq 'pilot'` }
});
const pilots: PilotInfoEntity[] = [];
for await (const entity of entities) {
const pilot: PilotInfoEntity = {
partitionKey: entity.partitionKey,
rowKey: entity.rowKey,
id: entity.id.toString(),
name: entity.name.toString()
};
pilots.push(pilot);
}
return pilots;
} catch (error) {
const restError: RestError = error as RestError;
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
async create(pilotInfoData: PilotInfoDto): Promise<TableInsertEntityHeaders> {
try {
const client: TableClient =
await this.tableService.getTableClient('Pilots');
const pilotInfo: PilotInfoEntity = new PilotInfoEntity();
Object.assign(pilotInfo, pilotInfoData);
pilotInfo.partitionKey = 'pilot';
pilotInfo.rowKey = pilotInfo.id;
console.log(pilotInfo);
return await client.createEntity(pilotInfo);
} catch (error) {
const restError: RestError = error as RestError;
console.log(restError);
throw new CustomError(
restError.details['odataError']['message']['value'],
restError.details['odataError']['code'],
restError.statusCode
);
}
}
// async update(rowKey: string, pilotInfo: PilotInfo): Promise<PilotInfo> {
// return await this.pilotInfoRepository.update(
// this.partitionKey,
// rowKey,
// pilotInfo
// );
// }
// async delete(rowKey: string): Promise<void> {
// await this.pilotInfoRepository.delete(this.partitionKey, rowKey);
// }
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Repository, InjectRepository } from '@noahspan/azure-database';
import { Medical } from './medical.entity';
@Injectable()

View File

@@ -1,71 +1,95 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
UseInterceptors
Put,
UseGuards,
} from '@nestjs/common';
import { PilotInfoService } from './info/pilot-info.service';
import { PilotInfoDto } from './info/pilot-info.dto';
import { TableInsertEntityHeaders } from '@azure/data-tables';
import { CustomError } from '../customError/CustomError';
import { PilotInfoEntity } from './info/pilot-info.entity';
import { PilotInterceptor } from 'src/pilot/interceptors/pilot.interceptor';
import { Public } from '@noahspan/noahspan-modules';
import { PilotDto } from './pilot.dto';
import { Pilot } from './pilot.entity';
import { PilotService } from './pilot.service';
import { CustomError } from '../error/customError';
import { AuthGuard } from '@nestjs/passport'
@Controller('pilots')
@UseGuards(AuthGuard('azure-ad'))
export class PilotController {
constructor(private readonly pilotInfoService: PilotInfoService) {}
constructor(private readonly pilotService: PilotService) {}
@Get(':pilotId')
@Public()
@UseInterceptors(PilotInterceptor)
async find(@Param() params: any): Promise<PilotInfoEntity> {
@Get(':partitionKey/:rowKey')
async find(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string
) {
try {
const pilot: PilotInfoEntity = await this.pilotInfoService.find(
params.pilotId
);
return pilot;
return await this.pilotService.find(partitionKey, rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
@Public()
@UseInterceptors(PilotInterceptor)
async findAll(): Promise<PilotInfoEntity[]> {
async findAll() {
try {
const pilots: PilotInfoEntity[] = await this.pilotInfoService.findAll();
return pilots;
return await this.pilotService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
async create(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
async create(@Body() pilotDto: PilotDto) {
try {
const response: TableInsertEntityHeaders =
await this.pilotInfoService.create(pilotInfoData);
const pilot = new Pilot();
Object.assign(pilot, pilotDto);
return await this.pilotService.create(pilot);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode, {
cause: customError.name
});
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':partitionKey/:rowKey')
async update(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string,
@Body() pilotDto: PilotDto
) {
try {
const pilot = new Pilot();
Object.assign(pilot, pilotDto);
return await this.pilotService.update(partitionKey, rowKey, pilot);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':partitionKey/:rowKey')
async delete(
@Param('partitionKey') partitionKey: string,
@Param('rowKey') rowKey: string
) {
try {
return await this.pilotService.delete(partitionKey, rowKey);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -1,4 +1,6 @@
export class PilotInfoDto {
export class PilotDto {
partitionKey: string;
rowKey: string;
id: string;
name: string;
address: string;

View File

@@ -0,0 +1,14 @@
import { EntityString } from '@noahspan/azure-database';
export class Pilot {
@EntityString() partitionKey: string;
@EntityString() rowKey: string;
@EntityString() id: string;
@EntityString() name: string;
@EntityString() address?: string;
@EntityString() city?: string;
@EntityString() state?: string;
@EntityString() postalCode?: string;
@EntityString() email?: string;
@EntityString() phone?: string;
}

View File

@@ -1,20 +1,27 @@
import { Module } from '@nestjs/common';
import { PilotController } from './pilot.controller';
import { TableModule } from '@noahspan/noahspan-modules';
import { PilotInfoService } from './info/pilot-info.service';
import { PilotService } from './pilot.service';
import { AzureTableStorageModule } from '@noahspan/azure-database';
import { Pilot } from './pilot.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
TableModule.register({
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
allowInsecureConnection: Boolean(
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
)
})
AzureTableStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
connectionString: configService.get<string>('azureStorageConnectionString')
};
},
inject: [ConfigService]
}),
AzureTableStorageModule.forFeature(Pilot, {
createTableIfNotExists: false,
table: 'pilots'
}),
],
controllers: [PilotController],
providers: [PilotInfoService]
providers: [PilotService]
})
export class PilotModule {}

View File

@@ -0,0 +1,40 @@
import { InjectRepository, Repository } from '@noahspan/azure-database';
import { Injectable } from '@nestjs/common';
import { Pilot } from './pilot.entity';
@Injectable()
export class PilotService {
constructor(
@InjectRepository(Pilot)
private readonly pilotRepository: Repository<Pilot>
) {}
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
return await this.pilotRepository.find(partitionKey, rowKey);
}
async findAll(): Promise<Pilot[]> {
return await this.pilotRepository.findAll();
}
async create(pilot: Pilot): Promise<Pilot> {
// try {
// return await this.pilotRepository.create(pilot);
// } catch (error) {
// throw new Error(error);
// }
return await this.pilotRepository.create(pilot);
}
async update(
partitionKey: string,
rowKey: string,
pilot: Pilot
): Promise<Pilot> {
return await this.pilotRepository.update(partitionKey, rowKey, pilot);
}
async delete(partitionKey: string, rowKey: string): Promise<void> {
await this.pilotRepository.delete(partitionKey, rowKey);
}
}