From e1605992b0a0592491b996d1bd631a4ae6ca6f20 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Thu, 7 Aug 2025 14:59:06 -0500 Subject: [PATCH] switching to sqlite --- .DS_Store | Bin 8196 -> 8196 bytes .../1754234179211-initial_migration.ts | 64 + api/package.json | 8 +- api/src/app.module.ts | 14 +- api/src/certificate/certificate.controller.ts | 0 api/src/certificate/certificate.dto.ts | 0 api/src/certificate/certificate.module.ts | 0 api/src/certificate/certificate.service.ts | 0 api/src/config/configuration.ts | 1 - api/src/config/typeorm-cli.config.ts | 20 + api/src/endorsement/endorsement.controller.ts | 65 + api/src/endorsement/endorsement.dto.ts | 3 + api/src/endorsement/endorsement.module.ts | 0 api/src/endorsement/endorsement.service.ts | 32 + api/src/file/file.service.ts | 5 +- api/src/log/log.controller.ts | 94 +- api/src/log/log.dto.ts | 43 +- api/src/log/log.entity.ts | 128 +- api/src/log/log.module.ts | 35 +- api/src/log/log.service.ts | 56 +- api/src/medical/medical.controller.ts | 71 + api/src/medical/medical.dto.ts | 6 + api/src/medical/medical.module.ts | 0 api/src/medical/medical.service.ts | 33 + api/src/pilot/pilot.controller.ts | 73 +- api/src/pilot/pilot.dto.ts | 10 +- api/src/pilot/pilot.module.ts | 63 +- api/src/pilot/pilot.service.ts | 66 +- api/src/track/track.controller.ts | 133 + api/src/track/track.dto.ts | 5 + api/src/track/track.entity.ts | 18 + api/src/track/track.module.ts | 20 + api/src/track/track.service.ts | 88 + app/package.json | 8 +- app/src/components/logForm/ILogFormProps.ts | 2 +- app/src/components/logForm/ILogFormState.ts | 2 +- app/src/components/logForm/LogForm.tsx | 31 +- app/src/components/logForm/reducer.tsx | 8 +- app/src/components/logTracks/LogTracks.tsx | 2 +- app/src/components/logbook/ILogbookEntry.ts | 4 - app/src/components/logbook/ILogbookState.ts | 2 +- app/src/components/logbook/Logbook.tsx | 36 +- app/src/components/logbook/reducer.ts | 16 +- app/src/components/pilotForm/PilotForm.tsx | 19 +- app/src/components/pilots/Pilot.interface.ts | 2 - app/src/components/pilots/Pilots.tsx | 11 +- docker-compose.yaml | 20 +- package-lock.json | 21759 ++++++++++++++++ package.json | 7 +- pnpm-lock.yaml | 13428 ---------- pnpm-workspace.yaml | 3 - 51 files changed, 22651 insertions(+), 13863 deletions(-) create mode 100644 api/migrations/1754234179211-initial_migration.ts create mode 100644 api/src/certificate/certificate.controller.ts create mode 100644 api/src/certificate/certificate.dto.ts create mode 100644 api/src/certificate/certificate.module.ts create mode 100644 api/src/certificate/certificate.service.ts create mode 100644 api/src/config/typeorm-cli.config.ts create mode 100644 api/src/endorsement/endorsement.controller.ts create mode 100644 api/src/endorsement/endorsement.dto.ts create mode 100644 api/src/endorsement/endorsement.module.ts create mode 100644 api/src/endorsement/endorsement.service.ts create mode 100644 api/src/medical/medical.controller.ts create mode 100644 api/src/medical/medical.dto.ts create mode 100644 api/src/medical/medical.module.ts create mode 100644 api/src/medical/medical.service.ts create mode 100644 api/src/track/track.controller.ts create mode 100644 api/src/track/track.dto.ts create mode 100644 api/src/track/track.entity.ts create mode 100644 api/src/track/track.module.ts create mode 100644 api/src/track/track.service.ts create mode 100644 package-lock.json delete mode 100644 pnpm-lock.yaml delete mode 100644 pnpm-workspace.yaml diff --git a/.DS_Store b/.DS_Store index 2cc11474f95f707de7555a11617c7478f3244648..729a6ec30cd10e6f37daf57ee6b97a5a11038be1 100644 GIT binary patch delta 97 zcmZp1XmOa}&nUhzU^hRb_+}n~ASTYVl;Y&1{QMlo%^^ZEjEoGE3x&O mI0=Z08B!6db_(xd+`L=VhiPJi*k*Q#Z!D9~imZSN2m=6L93CnF delta 174 zcmZp1XmOa}&nUJrU^hRb*k&GqAg0ZQLPCs;jFVf0y*IxR-pOb!%uv9P2gJDyx(wwE z`3yx2*$l-%ej-CMLn?zFLnV-v%aG%llb@WFlb^)Ez#zcDz?9Cwz{t0GmS{NBW_F2h OER%IbD=0K6gb4trSSVxw diff --git a/api/migrations/1754234179211-initial_migration.ts b/api/migrations/1754234179211-initial_migration.ts new file mode 100644 index 0000000..7a27a57 --- /dev/null +++ b/api/migrations/1754234179211-initial_migration.ts @@ -0,0 +1,64 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class InitialMigration1754234179211 implements MigrationInterface { + name = 'InitialMigration1754234179211' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "certificates" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "number" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`CREATE TABLE "endorsements" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`CREATE TABLE "medical" ("id" varchar PRIMARY KEY NOT NULL, "class" varchar NOT NULL, "expirationDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`CREATE TABLE "pilots" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "address" varchar NOT NULL, "city" varchar NOT NULL, "state" varchar NOT NULL, "postalCode" varchar NOT NULL, "email" varchar NOT NULL, "phone" varchar NOT NULL)`); + await queryRunner.query(`CREATE TABLE "logs" ("id" varchar PRIMARY KEY NOT NULL, "date" datetime NOT NULL, "aircraftMakeModel" varchar NOT NULL, "aircraftIdentity" varchar NOT NULL, "routeFrom" varchar NOT NULL, "routeTo" varchar NOT NULL, "durationOfFlight" integer NOT NULL, "singleEngineLand" integer, "simulatorAtd" integer, "landingsDay" integer, "landingsNight" integer, "groundTrainingReceived" integer, "flightTrainingReceived" integer, "crossCountry" integer, "night" integer, "solo" integer, "pilotInCommand" integer, "instrumentActual" integer, "instrumentSimulated" integer, "instrumentApproaches" integer, "instrumentHolds" integer, "instrumentNavTrack" integer, "notes" varchar, "pilotId" varchar)`); + await queryRunner.query(`CREATE TABLE "tracks" ("id" varchar PRIMARY KEY NOT NULL, "url" varchar NOT NULL, "order" integer NOT NULL, "logId" varchar)`); + await queryRunner.query(`CREATE TABLE "temporary_certificates" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "number" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar, CONSTRAINT "FK_05a68997dc2d27dfcc642a4cf51" FOREIGN KEY ("pilotId") REFERENCES "pilots" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_certificates"("id", "type", "number", "issueDate", "pilotId") SELECT "id", "type", "number", "issueDate", "pilotId" FROM "certificates"`); + await queryRunner.query(`DROP TABLE "certificates"`); + await queryRunner.query(`ALTER TABLE "temporary_certificates" RENAME TO "certificates"`); + await queryRunner.query(`CREATE TABLE "temporary_endorsements" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar, CONSTRAINT "FK_96a69a0bfbdeccce6bffe34002d" FOREIGN KEY ("pilotId") REFERENCES "pilots" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_endorsements"("id", "type", "issueDate", "pilotId") SELECT "id", "type", "issueDate", "pilotId" FROM "endorsements"`); + await queryRunner.query(`DROP TABLE "endorsements"`); + await queryRunner.query(`ALTER TABLE "temporary_endorsements" RENAME TO "endorsements"`); + await queryRunner.query(`CREATE TABLE "temporary_medical" ("id" varchar PRIMARY KEY NOT NULL, "class" varchar NOT NULL, "expirationDate" datetime NOT NULL, "pilotId" varchar, CONSTRAINT "FK_cb1f5d88fa2b105cc77513e9082" FOREIGN KEY ("pilotId") REFERENCES "pilots" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_medical"("id", "class", "expirationDate", "pilotId") SELECT "id", "class", "expirationDate", "pilotId" FROM "medical"`); + await queryRunner.query(`DROP TABLE "medical"`); + await queryRunner.query(`ALTER TABLE "temporary_medical" RENAME TO "medical"`); + await queryRunner.query(`CREATE TABLE "temporary_logs" ("id" varchar PRIMARY KEY NOT NULL, "date" datetime NOT NULL, "aircraftMakeModel" varchar NOT NULL, "aircraftIdentity" varchar NOT NULL, "routeFrom" varchar NOT NULL, "routeTo" varchar NOT NULL, "durationOfFlight" integer NOT NULL, "singleEngineLand" integer, "simulatorAtd" integer, "landingsDay" integer, "landingsNight" integer, "groundTrainingReceived" integer, "flightTrainingReceived" integer, "crossCountry" integer, "night" integer, "solo" integer, "pilotInCommand" integer, "instrumentActual" integer, "instrumentSimulated" integer, "instrumentApproaches" integer, "instrumentHolds" integer, "instrumentNavTrack" integer, "notes" varchar, "pilotId" varchar, CONSTRAINT "FK_19598551658f7625a82c7f029c8" FOREIGN KEY ("pilotId") REFERENCES "pilots" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_logs"("id", "date", "aircraftMakeModel", "aircraftIdentity", "routeFrom", "routeTo", "durationOfFlight", "singleEngineLand", "simulatorAtd", "landingsDay", "landingsNight", "groundTrainingReceived", "flightTrainingReceived", "crossCountry", "night", "solo", "pilotInCommand", "instrumentActual", "instrumentSimulated", "instrumentApproaches", "instrumentHolds", "instrumentNavTrack", "notes", "pilotId") SELECT "id", "date", "aircraftMakeModel", "aircraftIdentity", "routeFrom", "routeTo", "durationOfFlight", "singleEngineLand", "simulatorAtd", "landingsDay", "landingsNight", "groundTrainingReceived", "flightTrainingReceived", "crossCountry", "night", "solo", "pilotInCommand", "instrumentActual", "instrumentSimulated", "instrumentApproaches", "instrumentHolds", "instrumentNavTrack", "notes", "pilotId" FROM "logs"`); + await queryRunner.query(`DROP TABLE "logs"`); + await queryRunner.query(`ALTER TABLE "temporary_logs" RENAME TO "logs"`); + await queryRunner.query(`CREATE TABLE "temporary_tracks" ("id" varchar PRIMARY KEY NOT NULL, "url" varchar NOT NULL, "order" integer NOT NULL, "logId" varchar, CONSTRAINT "FK_71881df31cfff2362e39accc4b2" FOREIGN KEY ("logId") REFERENCES "logs" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_tracks"("id", "url", "order", "logId") SELECT "id", "url", "order", "logId" FROM "tracks"`); + await queryRunner.query(`DROP TABLE "tracks"`); + await queryRunner.query(`ALTER TABLE "temporary_tracks" RENAME TO "tracks"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tracks" RENAME TO "temporary_tracks"`); + await queryRunner.query(`CREATE TABLE "tracks" ("id" varchar PRIMARY KEY NOT NULL, "url" varchar NOT NULL, "order" integer NOT NULL, "logId" varchar)`); + await queryRunner.query(`INSERT INTO "tracks"("id", "url", "order", "logId") SELECT "id", "url", "order", "logId" FROM "temporary_tracks"`); + await queryRunner.query(`DROP TABLE "temporary_tracks"`); + await queryRunner.query(`ALTER TABLE "logs" RENAME TO "temporary_logs"`); + await queryRunner.query(`CREATE TABLE "logs" ("id" varchar PRIMARY KEY NOT NULL, "date" datetime NOT NULL, "aircraftMakeModel" varchar NOT NULL, "aircraftIdentity" varchar NOT NULL, "routeFrom" varchar NOT NULL, "routeTo" varchar NOT NULL, "durationOfFlight" integer NOT NULL, "singleEngineLand" integer, "simulatorAtd" integer, "landingsDay" integer, "landingsNight" integer, "groundTrainingReceived" integer, "flightTrainingReceived" integer, "crossCountry" integer, "night" integer, "solo" integer, "pilotInCommand" integer, "instrumentActual" integer, "instrumentSimulated" integer, "instrumentApproaches" integer, "instrumentHolds" integer, "instrumentNavTrack" integer, "notes" varchar, "pilotId" varchar)`); + await queryRunner.query(`INSERT INTO "logs"("id", "date", "aircraftMakeModel", "aircraftIdentity", "routeFrom", "routeTo", "durationOfFlight", "singleEngineLand", "simulatorAtd", "landingsDay", "landingsNight", "groundTrainingReceived", "flightTrainingReceived", "crossCountry", "night", "solo", "pilotInCommand", "instrumentActual", "instrumentSimulated", "instrumentApproaches", "instrumentHolds", "instrumentNavTrack", "notes", "pilotId") SELECT "id", "date", "aircraftMakeModel", "aircraftIdentity", "routeFrom", "routeTo", "durationOfFlight", "singleEngineLand", "simulatorAtd", "landingsDay", "landingsNight", "groundTrainingReceived", "flightTrainingReceived", "crossCountry", "night", "solo", "pilotInCommand", "instrumentActual", "instrumentSimulated", "instrumentApproaches", "instrumentHolds", "instrumentNavTrack", "notes", "pilotId" FROM "temporary_logs"`); + await queryRunner.query(`DROP TABLE "temporary_logs"`); + await queryRunner.query(`ALTER TABLE "medical" RENAME TO "temporary_medical"`); + await queryRunner.query(`CREATE TABLE "medical" ("id" varchar PRIMARY KEY NOT NULL, "class" varchar NOT NULL, "expirationDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`INSERT INTO "medical"("id", "class", "expirationDate", "pilotId") SELECT "id", "class", "expirationDate", "pilotId" FROM "temporary_medical"`); + await queryRunner.query(`DROP TABLE "temporary_medical"`); + await queryRunner.query(`ALTER TABLE "endorsements" RENAME TO "temporary_endorsements"`); + await queryRunner.query(`CREATE TABLE "endorsements" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`INSERT INTO "endorsements"("id", "type", "issueDate", "pilotId") SELECT "id", "type", "issueDate", "pilotId" FROM "temporary_endorsements"`); + await queryRunner.query(`DROP TABLE "temporary_endorsements"`); + await queryRunner.query(`ALTER TABLE "certificates" RENAME TO "temporary_certificates"`); + await queryRunner.query(`CREATE TABLE "certificates" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar NOT NULL, "number" varchar NOT NULL, "issueDate" datetime NOT NULL, "pilotId" varchar)`); + await queryRunner.query(`INSERT INTO "certificates"("id", "type", "number", "issueDate", "pilotId") SELECT "id", "type", "number", "issueDate", "pilotId" FROM "temporary_certificates"`); + await queryRunner.query(`DROP TABLE "temporary_certificates"`); + await queryRunner.query(`DROP TABLE "tracks"`); + await queryRunner.query(`DROP TABLE "logs"`); + await queryRunner.query(`DROP TABLE "pilots"`); + await queryRunner.query(`DROP TABLE "medical"`); + await queryRunner.query(`DROP TABLE "endorsements"`); + await queryRunner.query(`DROP TABLE "certificates"`); + } + +} diff --git a/api/package.json b/api/package.json index 1a0f140..77c46ea 100644 --- a/api/package.json +++ b/api/package.json @@ -16,7 +16,11 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "typeorm": "npm run build && npx typeorm -d dist/config/typeorm-cli.config.js", + "migration:generate": "npm run typeorm -- migration:generate", + "migration:run": "npm run typeorm -- migration:run", + "migration:revert": "npm run typeorm -- migration:revert" }, "dependencies": { "@azure/storage-blob": "^12.27.0", @@ -32,7 +36,7 @@ "@noahspan/noahspan-modules": "^1.1.5", "@schematics/angular": "^17.3.7", "@types/multer": "^1.4.12", - "dotenv": "^16.4.7", + "dotenv": "^16.6.1", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "sqlite3": "^5.1.7", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 81cc209..262c905 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -8,6 +8,8 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { AuthGuard, AuthModule, UserModule } from '@noahspan/noahspan-modules'; import configuration from './config/configuration'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { dataSourceOptions } from './config/typeorm-cli.config'; +import { TrackModule } from './track/track.module'; @Module({ imports: [ @@ -23,20 +25,14 @@ import { TypeOrmModule } from '@nestjs/typeorm'; }, }), ConfigModule.forRoot({ + isGlobal: true, load: [configuration] }), FeatureFlagModule, LogModule, PilotModule, - TypeOrmModule.forRootAsync({ - inject: [ConfigService], - useFactory: (configService: ConfigService) => ({ - type: 'sqlite', - database: configService.get('dbPath'), - entities: [__dirname + "/**/*.entity{.ts,.js}"], - synchronize: false - }) - }), + TrackModule, + TypeOrmModule.forRoot(dataSourceOptions), UserModule.registerAsync({ inject: [ConfigService], imports: [ConfigModule], diff --git a/api/src/certificate/certificate.controller.ts b/api/src/certificate/certificate.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/certificate/certificate.dto.ts b/api/src/certificate/certificate.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/certificate/certificate.module.ts b/api/src/certificate/certificate.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/certificate/certificate.service.ts b/api/src/certificate/certificate.service.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/config/configuration.ts b/api/src/config/configuration.ts index c163e65..2354131 100644 --- a/api/src/config/configuration.ts +++ b/api/src/config/configuration.ts @@ -1,6 +1,5 @@ export default () => ({ azureStorageConnectionString: process.env.AZURE_STORAGE_CONNECTION_STRING, - dbPath: process.env.DB_PATH, clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, tenantId: process.env.TENANT_ID diff --git a/api/src/config/typeorm-cli.config.ts b/api/src/config/typeorm-cli.config.ts new file mode 100644 index 0000000..d715bb5 --- /dev/null +++ b/api/src/config/typeorm-cli.config.ts @@ -0,0 +1,20 @@ +import { DataSource, DataSourceOptions } from 'typeorm'; +import { config } from 'dotenv'; +import { ConfigService } from '@nestjs/config'; +import configuration from './configuration'; + +config(); + +const configService = new ConfigService(); + +export const dataSourceOptions: DataSourceOptions = { + type: 'sqlite', + database: configService.get('DB_PATH'), + entities: ['dist/**/*.entity.js'], + migrations: ['dist/migrations/*.js'], + synchronize: configService.get('DB_SYNC') +} + +const dataSource = new DataSource(dataSourceOptions); + +export default dataSource; \ No newline at end of file diff --git a/api/src/endorsement/endorsement.controller.ts b/api/src/endorsement/endorsement.controller.ts new file mode 100644 index 0000000..0b1712b --- /dev/null +++ b/api/src/endorsement/endorsement.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, HttpException, Param, Post, Put } from "@nestjs/common"; +import { EndorsementService } from "./endorsement.service"; +import { CustomError } from "@noahspan/noahspan-modules"; +import { EndorsementDto } from "./endorsement.dto"; + + +@Controller('endorsements') +export class EndorsementController { + constructor(private readonly endorsementService: EndorsementService) {} + + @Get(':id') + async find(@Param('id') id: string) { + try { + return await this.endorsementService.find(id); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Get() + async findAll() { + try { + return await this.endorsementService.findAll(); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Post() + async create(@Body() endorsementDto: EndorsementDto) { + try { + return await this.endorsementService.create(endorsementDto); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Put(':id') + async update(@Param('id') id: string, @Body() endorsementDto: EndorsementDto) { + try { + return await this.endorsementService.update(id, endorsementDto); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode) + } + } + + @Delete(':id') + async delete(@Param('id') id: string) { + try { + return await this.endorsementService.delete(id); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode) + } + } +} \ No newline at end of file diff --git a/api/src/endorsement/endorsement.dto.ts b/api/src/endorsement/endorsement.dto.ts new file mode 100644 index 0000000..7332afa --- /dev/null +++ b/api/src/endorsement/endorsement.dto.ts @@ -0,0 +1,3 @@ +export class EndorsementDto { + +} \ No newline at end of file diff --git a/api/src/endorsement/endorsement.module.ts b/api/src/endorsement/endorsement.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/endorsement/endorsement.service.ts b/api/src/endorsement/endorsement.service.ts new file mode 100644 index 0000000..16cb895 --- /dev/null +++ b/api/src/endorsement/endorsement.service.ts @@ -0,0 +1,32 @@ +import { InjectRepository } from "@nestjs/typeorm"; +import { EndorsementEntity } from "./endorsement.entity"; +import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; +import { Injectable } from "@nestjs/common"; +import { EndorsementDto } from "./endorsement.dto"; + +@Injectable() +export class EndorsementService { + constructor( + @InjectRepository(EndorsementEntity) private readonly endorsementRepository: Repository + ) {} + + async find(id: string): Promise { + return await this.endorsementRepository.findOneBy({ id }); + } + + async findAll(): Promise { + return await this.endorsementRepository.find(); + } + + async create(endorsement: EndorsementDto): Promise { + return await this.endorsementRepository.insert(endorsement); + } + + async update(id: string, endorsement: EndorsementDto): Promise { + return await this.endorsementRepository.update(id, endorsement); + } + + async delete(id: string): Promise { + return await this.endorsementRepository.delete({ id }); + } +} \ No newline at end of file diff --git a/api/src/file/file.service.ts b/api/src/file/file.service.ts index 163cd0d..52e6d88 100644 --- a/api/src/file/file.service.ts +++ b/api/src/file/file.service.ts @@ -37,10 +37,11 @@ import { ConfigService } from '@nestjs/config'; return blockBlobClient; } - async uploadFile(file: Express.Multer.File, containerName: string, rowKey: string): Promise { + async uploadFile(file: Express.Multer.File, containerName: string, logId: string): Promise { + console.log(file) this.containerName = containerName; - const blockBlobClient = await this.getBlobClient(`${rowKey}/${file.originalname}`); + const blockBlobClient = await this.getBlobClient(`${logId}/${file.originalname}`); const fileUrl = blockBlobClient.url; await blockBlobClient.uploadData(file.buffer); diff --git a/api/src/log/log.controller.ts b/api/src/log/log.controller.ts index 773c0cd..8f2dbfe 100644 --- a/api/src/log/log.controller.ts +++ b/api/src/log/log.controller.ts @@ -7,20 +7,17 @@ import { Param, Post, Put, - Query, - StreamableFile, - UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common'; import { LogDto } from './log.dto'; -import { Log } from './log.entity'; +import { LogEntity } from './log.entity'; import { LogService } from './log.service'; import { CustomError } from '../error/customError'; import { AuthGuard } from '@noahspan/noahspan-modules'; import { LogInterceptor } from './interceptors/log.interceptor'; import { FileService } from '../file/file.service'; -import { FileInterceptor } from '@nestjs/platform-express'; +import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; @Controller('logs') export class LogController { @@ -30,14 +27,13 @@ export class LogController { ) {} - @Get(':partitionKey/:rowKey') + @Get(':id') @UseInterceptors(new LogInterceptor()) async find( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string - ): Promise { + @Param('id') id: string, + ): Promise { try { - return await this.logService.find(partitionKey, rowKey); + return await this.logService.find(id); } catch (error) { const customError = error as CustomError; @@ -47,7 +43,7 @@ export class LogController { @Get() @UseInterceptors(new LogInterceptor()) - async findAll(): Promise { + async findAll(): Promise { try { return await this.logService.findAll(); } catch (error) { @@ -57,35 +53,26 @@ export class LogController { } } - @UseGuards(AuthGuard) + // @UseGuards(AuthGuard) @Post() - async create(@Body() logDto: LogDto): Promise { + async create(@Body() logDto: LogDto): Promise { try { - const log = new Log(); - - Object.assign(log, logDto); - - return await this.logService.create(log); + return await this.logService.create(logDto); } catch (error) { const customError = error as CustomError; - + console.log(error) throw new HttpException(customError.message, customError.statusCode); } } - @UseGuards(AuthGuard) - @Put(':partitionKey/:rowKey') + // @UseGuards(AuthGuard) + @Put(':id') async update( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string, + @Param('id') id: string, @Body() logDto: LogDto - ): Promise { + ): Promise { try { - const log = new Log(); - - Object.assign(log, logDto); - - return await this.logService.update(partitionKey, rowKey, log); + return await this.logService.update(id, logDto); } catch (error) { const customError = error as CustomError; @@ -93,52 +80,13 @@ export class LogController { } } - @UseGuards(AuthGuard) - @Delete(':partitionKey/:rowKey') + // @UseGuards(AuthGuard) + @Delete(':id') async delete( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string - ): Promise { + @Param('id') id: string, + ): Promise { try { - return await this.logService.delete(partitionKey, rowKey); - } catch (error) { - const customError = error as CustomError; - - throw new HttpException(customError.message, customError.statusCode); - } - } - - @UseGuards(AuthGuard) - @Post(':partitionKey/:rowKey/track') - @UseInterceptors(FileInterceptor('file')) - async createTrack(@Param('rowKey') rowKey: string, @UploadedFile() file: Express.Multer.File) { - try { - const containerName = 'tracks'; - const url = await this.fileService.uploadFile(file, containerName, rowKey); - - return { url } - } catch (error) { - const customError = error as CustomError; - - throw new HttpException(customError.message, customError.statusCode); - } - } - - @Get(':partitionKey/:rowKey/track') - async downloadTrack(@Param('rowKey') rowKey: string, @Query('fileName') fileName: string): Promise { - const containerName = 'tracks'; - const downloadedFile: string = await this.fileService.downloadFile(containerName, rowKey, fileName) - - return downloadedFile; - } - - @UseGuards(AuthGuard) - @Delete(':partitionKey/:rowKey/track') - async deleteTrack(@Param('rowKey') rowKey: string, @Query('fileName') fileName: string): Promise { - try { - const containerName = 'tracks'; - - return await this.fileService.deleteFile(containerName, rowKey, fileName) + return await this.logService.delete(id); } catch (error) { const customError = error as CustomError; diff --git a/api/src/log/log.dto.ts b/api/src/log/log.dto.ts index 9d82968..b5e986c 100644 --- a/api/src/log/log.dto.ts +++ b/api/src/log/log.dto.ts @@ -1,27 +1,30 @@ +import { PilotEntity } from "src/pilot/pilot.entity"; +import { TrackEntity } from "src/track/track.entity"; + export class LogDto { pilotId: string; - pilotName: string; - date: string; + date: Date; aircraftMakeModel: string; aircraftIdentity: string; routeFrom: string; routeTo: string; - durationOfFlight: number; - singleEngineLand: string; - simulatorAtd: number; - landingsDay: number; - landingsNight: number; - instrumentActual: number; - instrumentSimulated: number; - instrumentApproaches: number; - instrumentHolds: number; - instrumentNavTrack: number; - groundTrainingReceived: number; - flightTrainingReceived: number; - crossCountry: number; - night: number; - solo: number; - pilotInCommand: number; - tracks: string[]; - notes: string; + durationOfFlight?: number; + singleEngineLand?: number; + simulatorAtd?: number; + landingsDay?: number; + landingsNight?: number; + instrumentActual?: number; + instrumentSimulated?: number; + instrumentApproaches?: number; + instrumentHolds?: number; + instrumentNavTrack?: number; + groundTrainingReceived?: number; + flightTrainingReceived?: number; + crossCountry?: number; + night?: number; + solo?: number; + pilotInCommand?: number; + notes?: string; + pilot?: PilotEntity; + tracks?: TrackEntity[]; } diff --git a/api/src/log/log.entity.ts b/api/src/log/log.entity.ts index 166b9aa..a02b033 100644 --- a/api/src/log/log.entity.ts +++ b/api/src/log/log.entity.ts @@ -1,35 +1,36 @@ -export class Log { - partitionKey: string; - rowKey: string; - pilotId: string; - pilotName: string; - date: string; - aircraftMakeModel: string; - aircraftIdentity: string; - routeFrom: string; - routeTo: string; - durationOfFlight: number | null; - singleEngineLand: number | null; - simulatorAtd?: number | null; - landingsDay?: number | null; - landingsNight?: number | null; - groundTrainingReceived?: number; - flightTrainingReceived?: number; - crossCountry?: number | null; - night?: number | null; - solo?: number | null; - pilotInCommand?: number | null; - instrumentActual?: number | null; - instrumentSimulated?: number | null; - instrumentApproaches?: number | null; - instrumentHolds?: number | null; - instrumentNavTrack?: number | null; - tracks?: string[]; - notes?: string; -} +// export class Log { +// partitionKey: string; +// rowKey: string; +// pilotId: string; +// pilotName: string; +// date: string; +// aircraftMakeModel: string; +// aircraftIdentity: string; +// routeFrom: string; +// routeTo: string; +// durationOfFlight: number | null; +// singleEngineLand: number | null; +// simulatorAtd?: number | null; +// landingsDay?: number | null; +// landingsNight?: number | null; +// groundTrainingReceived?: number; +// flightTrainingReceived?: number; +// crossCountry?: number | null; +// night?: number | null; +// solo?: number | null; +// pilotInCommand?: number | null; +// instrumentActual?: number | null; +// instrumentSimulated?: number | null; +// instrumentApproaches?: number | null; +// instrumentHolds?: number | null; +// instrumentNavTrack?: number | null; +// tracks?: string[]; +// notes?: string; +// } import { PilotEntity } from 'src/pilot/pilot.entity'; -import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; +import { TrackEntity } from 'src/track/track.entity'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, OneToMany } from 'typeorm'; @Entity({ name: 'logs' }) export class LogEntity { @@ -54,53 +55,56 @@ export class LogEntity { @Column() durationOfFlight: number; - @Column() - singleEngineLand: number; + @Column({ nullable: true }) + singleEngineLand: number | null; - @Column() - simulatorAtd: number; + @Column({ nullable: true }) + simulatorAtd: number | null; - @Column() - landingsDay: number; + @Column({ nullable: true }) + landingsDay: number | null; - @Column() - landingsNight: number; + @Column({ nullable: true }) + landingsNight: number | null; - @Column() - groundTrainingReceived: number; + @Column({ nullable: true }) + groundTrainingReceived: number | null; - @Column() - flightTrainingReceived: number; + @Column({ nullable: true }) + flightTrainingReceived: number | null; - @Column() - crossCountry: number; + @Column({ nullable: true }) + crossCountry: number | null; - @Column() - night: number; + @Column({ nullable: true }) + night: number | null; - @Column() - solo: number; + @Column({ nullable: true }) + solo: number | null; - @Column() - pilotInCommand: number; + @Column({ nullable: true }) + pilotInCommand: number | null; - @Column() - instrumentActual: number; + @Column({ nullable: true }) + instrumentActual: number | null; - @Column() - instrumentSimulated: number; + @Column({ nullable: true }) + instrumentSimulated: number | null; - @Column() - instrumentApproaches: number; + @Column({ nullable: true }) + instrumentApproaches: number | null; - @Column() - instrumentHolds: number; + @Column({ nullable: true }) + instrumentHolds: number | null; - @Column() - instrumentNavTrack: number; + @Column({ nullable: true }) + instrumentNavTrack: number | null; - @Column() - notes: string; + @Column({ nullable: true }) + notes: string | null; + + @OneToMany(() => TrackEntity, (track: TrackEntity) => track.log, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + tracks: TrackEntity[] @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.logs) @JoinColumn({ name: 'pilotId' }) diff --git a/api/src/log/log.module.ts b/api/src/log/log.module.ts index 111ada4..c08d8c7 100644 --- a/api/src/log/log.module.ts +++ b/api/src/log/log.module.ts @@ -1,28 +1,33 @@ 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 { AzureTableStorageModule } from '@noahspan/azure-database'; +import { LogEntity } from './log.entity'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { FileService } from '../file/file.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PilotModule } from 'src/pilot/pilot.module'; @Module({ imports: [ - AzureTableStorageModule.forRootAsync({ - imports: [ConfigModule], - useFactory: async (configService: ConfigService) => { - return { - connectionString: configService.get('azureStorageConnectionString') - }; - }, - inject: [ConfigService] - }), - AzureTableStorageModule.forFeature(Log, { - createTableIfNotExists: false, - table: 'logs' - }), + // AzureTableStorageModule.forRootAsync({ + // imports: [ConfigModule], + // useFactory: async (configService: ConfigService) => { + // return { + // connectionString: configService.get('azureStorageConnectionString') + // }; + // }, + // inject: [ConfigService] + // }), + // AzureTableStorageModule.forFeature(Log, { + // createTableIfNotExists: false, + // table: 'logs' + // }), + PilotModule, + TypeOrmModule.forFeature([LogEntity]) ], controllers: [LogController], + exports: [LogService], providers: [ ConfigService, FileService, diff --git a/api/src/log/log.service.ts b/api/src/log/log.service.ts index c7de866..77b2135 100644 --- a/api/src/log/log.service.ts +++ b/api/src/log/log.service.ts @@ -1,34 +1,58 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository, Repository } from '@noahspan/azure-database'; -import { Log } from './log.entity'; -import { v4 as uuidv4 } from 'uuid'; +import { InjectRepository } from '@nestjs/typeorm'; +import { LogEntity } from './log.entity'; +import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; +import { LogDto } from './log.dto'; +import { PilotService } from 'src/pilot/pilot.service'; +import { PilotEntity } from 'src/pilot/pilot.entity'; +import { CustomError } from 'src/error/customError'; +import { TrackService } from 'src/track/track.service'; @Injectable() export class LogService { constructor( - @InjectRepository(Log) private readonly logRepository: Repository + @InjectRepository(LogEntity) private readonly logRepository: Repository, + private readonly pilotService: PilotService ) {} - async find(partitionKey: string, rowKey: string): Promise { - return await this.logRepository.find(partitionKey, rowKey); + async find(id: string): Promise { + const logEntity: LogEntity = await this.logRepository.findOne({ + where: { id: id }, + relations: ['pilot', 'tracks'] + }); + console.log(logEntity); + return logEntity; } - async findAll(): Promise { - return await this.logRepository.findAll(); + async findAll(): Promise { + return await this.logRepository.find(); } - async create(log: Log): Promise { - log.partitionKey = 'log'; - log.rowKey = uuidv4(); + async create(logDto: LogDto): Promise { + try{ + const pilotEntity: PilotEntity = await this.pilotService.find(logDto.pilotId); - return await this.logRepository.create(log); + if (pilotEntity) { + const { pilotId, ...newLogDto } = logDto; + const log = this.logRepository.create({ + ...newLogDto, + pilot: pilotEntity + }) + + return this.logRepository.insert(log); + } else { + throw new CustomError('Pilot not found', 'Not found', 404); + } + } catch (error) { + throw error + } } - async update(partitionKey: string, rowKey: string, log: Log): Promise { - return await this.logRepository.update(partitionKey, rowKey, log); + async update(id: string, log: LogDto): Promise { + return await this.logRepository.update(id, log); } - async delete(partitionKey: string, rowKey: string): Promise { - await this.logRepository.delete(partitionKey, rowKey); + async delete(id: string): Promise { + return await this.logRepository.delete({ id }); } } diff --git a/api/src/medical/medical.controller.ts b/api/src/medical/medical.controller.ts new file mode 100644 index 0000000..25910f9 --- /dev/null +++ b/api/src/medical/medical.controller.ts @@ -0,0 +1,71 @@ +import { Body, Controller, Delete, Get, HttpException, Param, Post, Put, UseGuards } from "@nestjs/common"; +import { MedicalService } from "./medical.service"; +import { CustomError } from "src/error/customError"; +import { MedicalDto } from "./medical.dto"; +import { AuthGuard } from "@noahspan/noahspan-modules"; + + +@Controller('medical') +export class MedicalController { + constructor(private readonly medicalService: MedicalService) {} + + @UseGuards(AuthGuard) + @Get(':id') + async find(@Param('id') id: string) { + try { + return await this.medicalService.find(id) + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @UseGuards(AuthGuard) + @Get() + async findAll() { + try { + return await this.medicalService.findAll(); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @UseGuards(AuthGuard) + @Post() + async create(@Body() medicalDto: MedicalDto) { + try { + return await this.medicalService.create(medicalDto); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @UseGuards(AuthGuard) + @Put(':id') + async update(@Param('id') id: string, @Body() medicalDto: MedicalDto) { + try { + return await this.medicalService.update(id, medicalDto); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @UseGuards(AuthGuard) + @Delete(':id') + async delete(@Param('id') id: string) { + try { + return await this.medicalService.delete(id); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } +} \ No newline at end of file diff --git a/api/src/medical/medical.dto.ts b/api/src/medical/medical.dto.ts new file mode 100644 index 0000000..b63c9a2 --- /dev/null +++ b/api/src/medical/medical.dto.ts @@ -0,0 +1,6 @@ +export class MedicalDto { + id: string; + class: string; + expirationDate: string; + pilotId: string; +} \ No newline at end of file diff --git a/api/src/medical/medical.module.ts b/api/src/medical/medical.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/api/src/medical/medical.service.ts b/api/src/medical/medical.service.ts new file mode 100644 index 0000000..d2a5d36 --- /dev/null +++ b/api/src/medical/medical.service.ts @@ -0,0 +1,33 @@ + +import { MedicalEntity } from "./medical.entity"; +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; +import { MedicalDto } from "./medical.dto"; + +@Injectable() +export class MedicalService { + constructor( + @InjectRepository(MedicalEntity) private readonly medicalRepository: Repository + ) {} + + async find(id: string): Promise { + return await this.medicalRepository.findOneBy({ id }); + } + + async findAll(): Promise { + return await this.medicalRepository.find(); + } + + async create(medical: MedicalDto): Promise { + return await this.medicalRepository.insert(medical) + } + + async update(id: string, medical: MedicalDto): Promise { + return await this.medicalRepository.update(id, medical); + } + + async delete(id: string): Promise { + return await this.medicalRepository.delete({ id }); + } +} \ No newline at end of file diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index a143830..e5d8e56 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -11,24 +11,21 @@ import { UseInterceptors, } from '@nestjs/common'; import { PilotDto } from './pilot.dto'; -import { Pilot } from './pilot.entity'; +import { PilotEntity } from './pilot.entity'; import { PilotService } from './pilot.service'; import { CustomError } from '../error/customError'; import { AuthGuard } from '@noahspan/noahspan-modules' import { PilotInterceptor } from './interceptors/pilot.interceptor'; @Controller('pilots') -@UseInterceptors(new PilotInterceptor()) +// @UseInterceptors(new PilotInterceptor()) export class PilotController { constructor(private readonly pilotService: PilotService) {} - @Get(':partitionKey/:rowKey') - async find( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string - ) { + @Get(':id') + async find(@Param('id') id: string) { try { - return await this.pilotService.find(partitionKey, rowKey); + return await this.pilotService.find(id); } catch (error) { const customError = error as CustomError; @@ -47,30 +44,11 @@ export class PilotController { } } - @UseGuards(AuthGuard) + // @UseGuards(AuthGuard) @Post() async create(@Body() pilotDto: PilotDto) { try { - let pilot = new Pilot(); - - pilot = { - partitionKey: pilotDto.partitionKey, - rowKey: pilotDto.rowKey, - id: pilotDto.id, - name: pilotDto.name, - address: pilotDto.address, - city: pilotDto.city, - state: pilotDto.state, - postalCode: pilotDto.postalCode, - email: pilotDto.email, - phone: pilotDto.phone, - medicalClass: pilotDto.medicalClass, - medicalExpiration: pilotDto.medicalExpiration, - certificates: JSON.stringify(pilotDto.certificates), - endorsements: JSON.stringify(pilotDto.endorsements) - } - - return await this.pilotService.create(pilot); + return await this.pilotService.create(pilotDto); } catch (error) { const customError = error as CustomError; @@ -78,34 +56,14 @@ export class PilotController { } } - @UseGuards(AuthGuard) - @Put(':partitionKey/:rowKey') + // @UseGuards(AuthGuard) + @Put(':id') async update( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string, + @Param('id') id: string, @Body() pilotDto: PilotDto ) { try { - let pilot = new Pilot(); - - pilot = { - partitionKey: pilotDto.partitionKey, - rowKey: pilotDto.rowKey, - id: pilotDto.id, - name: pilotDto.name, - address: pilotDto.address, - city: pilotDto.city, - state: pilotDto.state, - postalCode: pilotDto.postalCode, - email: pilotDto.email, - phone: pilotDto.phone, - medicalClass: pilotDto.medicalClass, - medicalExpiration: pilotDto.medicalExpiration, - certificates: JSON.stringify(pilotDto.certificates), - endorsements: JSON.stringify(pilotDto.endorsements) - } - - return await this.pilotService.update(partitionKey, rowKey, pilot); + return await this.pilotService.update(id, pilotDto); } catch (error) { const customError = error as CustomError; @@ -113,14 +71,13 @@ export class PilotController { } } - @UseGuards(AuthGuard) - @Delete(':partitionKey/:rowKey') + // @UseGuards(AuthGuard) + @Delete(':id') async delete( - @Param('partitionKey') partitionKey: string, - @Param('rowKey') rowKey: string + @Param('id') id: string, ) { try { - return await this.pilotService.delete(partitionKey, rowKey); + return await this.pilotService.delete(id); } catch (error) { const customError = error as CustomError; diff --git a/api/src/pilot/pilot.dto.ts b/api/src/pilot/pilot.dto.ts index 7eddeeb..1b6c0ac 100644 --- a/api/src/pilot/pilot.dto.ts +++ b/api/src/pilot/pilot.dto.ts @@ -1,10 +1,6 @@ -import { Certificate } from "./certificate/certificate.entity"; -import { Endorsement } from "./endorsement/endorsement.entity"; +import { LogEntity } from "src/log/log.entity"; export class PilotDto { - partitionKey: string; - rowKey: string; - id: string; name: string; address: string; city: string; @@ -12,8 +8,4 @@ export class PilotDto { postalCode: string; email?: string; phone?: string; - medicalClass?: string; - medicalExpiration?: string; - certificates: Certificate; - endorsements: Endorsement } diff --git a/api/src/pilot/pilot.module.ts b/api/src/pilot/pilot.module.ts index 78608b3..b6d0aaf 100644 --- a/api/src/pilot/pilot.module.ts +++ b/api/src/pilot/pilot.module.ts @@ -1,41 +1,44 @@ import { Module } from '@nestjs/common'; import { PilotController } from './pilot.controller'; import { PilotService } from './pilot.service'; -import { AzureTableStorageModule } from '@noahspan/azure-database'; -import { Pilot } from './pilot.entity'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { Log } from 'src/log/log.entity'; +// import { AzureTableStorageModule } from '@noahspan/azure-database'; +import { PilotEntity } from './pilot.entity'; +// import { ConfigModule, ConfigService } from '@nestjs/config'; +// import { Log } from 'src/log/log.entity'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ - AzureTableStorageModule.forRootAsync({ - imports: [ConfigModule], - useFactory: async (configService: ConfigService) => { - return { - connectionString: configService.get('azureStorageConnectionString') - }; - }, - inject: [ConfigService] - }), - AzureTableStorageModule.forFeature(Log, { - createTableIfNotExists: false, - table: 'logs' - }), - AzureTableStorageModule.forRootAsync({ - imports: [ConfigModule], - useFactory: async (configService: ConfigService) => { - return { - connectionString: configService.get('azureStorageConnectionString') - }; - }, - inject: [ConfigService] - }), - AzureTableStorageModule.forFeature(Pilot, { - createTableIfNotExists: false, - table: 'pilots' - }) + // AzureTableStorageModule.forRootAsync({ + // imports: [ConfigModule], + // useFactory: async (configService: ConfigService) => { + // return { + // connectionString: configService.get('azureStorageConnectionString') + // }; + // }, + // inject: [ConfigService] + // }), + // AzureTableStorageModule.forFeature(Log, { + // createTableIfNotExists: false, + // table: 'logs' + // }), + // AzureTableStorageModule.forRootAsync({ + // imports: [ConfigModule], + // useFactory: async (configService: ConfigService) => { + // return { + // connectionString: configService.get('azureStorageConnectionString') + // }; + // }, + // inject: [ConfigService] + // }), + // AzureTableStorageModule.forFeature(PilotEntity, { + // createTableIfNotExists: false, + // table: 'pilots' + // }), + TypeOrmModule.forFeature([PilotEntity]) ], controllers: [PilotController], + exports: [PilotService], providers: [PilotService] }) export class PilotModule {} diff --git a/api/src/pilot/pilot.service.ts b/api/src/pilot/pilot.service.ts index 3cc321e..15a44b0 100644 --- a/api/src/pilot/pilot.service.ts +++ b/api/src/pilot/pilot.service.ts @@ -1,54 +1,46 @@ -import { InjectRepository, Repository } from '@noahspan/azure-database'; -import { Inject, Injectable } from '@nestjs/common'; -import { Pilot } from './pilot.entity'; -import { Log } from 'src/log/log.entity'; -import { LogService } from 'src/log/log.service'; +import { Injectable } from '@nestjs/common'; +import { PilotEntity } from './pilot.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DeleteResult, InsertResult, Repository, UpdateResult } from 'typeorm'; +import { PilotDto } from './pilot.dto'; +import { CustomError } from 'src/error/customError'; @Injectable() export class PilotService { constructor( - @InjectRepository(Pilot) private readonly pilotRepository: Repository, - @InjectRepository(Log) private readonly logRepository: Repository + @InjectRepository(PilotEntity) private readonly pilotRepository: Repository ) {} - async find(partitionKey: string, rowKey: string): Promise { - return await this.pilotRepository.find(partitionKey, rowKey); + async find(id: string): Promise { + try { + const pilotEntity = await this.pilotRepository.findOneBy({ id }); + + if (pilotEntity) { + return pilotEntity + } else { + throw new CustomError('Pilot not found', 'Not found', 404) + } + } catch (error) { + throw error + } } - async findAll(): Promise { - return await this.pilotRepository.findAll(); + async findAll(): Promise { + return await this.pilotRepository.find(); } - async create(pilot: Pilot): Promise { - // try { - // return await this.pilotRepository.create(pilot); - // } catch (error) { - // throw new Error(error); - // } - return await this.pilotRepository.create(pilot); + async create(pilot: PilotDto): Promise { + return await this.pilotRepository.insert(pilot); } async update( - partitionKey: string, - rowKey: string, - pilot: Pilot - ): Promise { - return await this.pilotRepository.update(partitionKey, rowKey, pilot); + id: string, + pilot: PilotDto + ): Promise { + return await this.pilotRepository.update(id, pilot); } - async delete(partitionKey: string, rowKey: string): Promise { - const pilotLogs: Log[] = await this.logRepository.findAll({ - queryOptions: { - filter: `pilotId eq '${rowKey}'` - } - }) - - for (const pilotLog of pilotLogs) { - await this.logRepository.delete(pilotLog.partitionKey, pilotLog.rowKey); - } - - await this.pilotRepository.delete(partitionKey, rowKey); - - return + async delete(id: string): Promise { + return await this.pilotRepository.delete({ id }); } } diff --git a/api/src/track/track.controller.ts b/api/src/track/track.controller.ts new file mode 100644 index 0000000..946044a --- /dev/null +++ b/api/src/track/track.controller.ts @@ -0,0 +1,133 @@ +import { + Body, + Controller, + Delete, + Get, + HttpException, + Param, + Post, + Put, + Query, + UploadedFile, + UseGuards, + UseInterceptors +} from '@nestjs/common'; +import { AuthGuard } from '@noahspan/noahspan-modules'; +import { CustomError } from '../error/customError'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { FileService } from '../file/file.service'; +import { TrackService } from './track.service'; +import { TrackEntity } from './track.entity'; +import { TrackDto } from './track.dto'; +import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; + +@Controller('tracks') +export class TrackController { + constructor( + private readonly fileService: FileService, + private readonly trackService: TrackService + ) {} + + // @Get('id') + // async fin(@Param('id') id: string): Promise { + // try { + // return await this.trackService.find(id); + // } catch (error) { + // const customError = error as CustomError; + + // throw new HttpException(customError.message, customError.statusCode); + // } + // } + + // @Get() + // async findAdd(): Promise { + // try { + // return await this.trackService.findAll(); + // } catch (error) { + // const customError = error as CustomError; + + // throw new HttpException(customError.message, customError.statusCode) + // } + // } + + @Post(':logId/:order') + async create(@Param('logId') logId: string, @Param('order') order: number, @UploadedFile() file: Express.Multer.File): Promise { + try { + console.log(file) + return await this.trackService.create(logId, order, file); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + // @Put(':id') + // async update(@Param('id') id: string, @Body() trackDto: TrackDto): Promise { + // try { + // return await this.trackService.update(id, trackDto); + // } catch (error) { + // const customError = error as CustomError; + + // throw new HttpException(customError.message, customError.statusCode) + // } + // } + + @Delete(':id') + async delete(@Param('id') id: string): Promise { + try { + return await this.trackService.delete(id); + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @UseGuards(AuthGuard) + @Post(':partitionKey/:rowKey/track') + @UseInterceptors(FileInterceptor('file')) + async createTrack(@Param('rowKey') rowKey: string, @UploadedFile() file: Express.Multer.File) { + try { + const containerName = 'tracks'; + const url = await this.fileService.uploadFile(file, containerName, rowKey); + + return { url } + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } + + @Get(':partitionKey/:rowKey/track') + async downloadTrack(@Param('rowKey') rowKey: string, @Query('fileName') fileName: string): Promise { + const containerName = 'tracks'; + const downloadedFile: string = await this.fileService.downloadFile(containerName, rowKey, fileName) + + return downloadedFile; + } + + @UseGuards(AuthGuard) + @Delete(':partitionKey/:rowKey/track') + async deleteTrack(@Param('rowKey') rowKey: string, @Query('fileName') fileName: string): Promise { + try { + const containerName = 'tracks'; + + return await this.fileService.deleteFile(containerName, rowKey, fileName) + } catch (error) { + const customError = error as CustomError; + + throw new HttpException(customError.message, customError.statusCode); + } + } +} + + + + + + + + + diff --git a/api/src/track/track.dto.ts b/api/src/track/track.dto.ts new file mode 100644 index 0000000..217c80e --- /dev/null +++ b/api/src/track/track.dto.ts @@ -0,0 +1,5 @@ +export class TrackDto { + logId: string; + order: number; + url?: string; +} \ No newline at end of file diff --git a/api/src/track/track.entity.ts b/api/src/track/track.entity.ts new file mode 100644 index 0000000..d25eab8 --- /dev/null +++ b/api/src/track/track.entity.ts @@ -0,0 +1,18 @@ +import { LogEntity } from 'src/log/log.entity'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; + +@Entity({ name: 'tracks' }) +export class TrackEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + url: string; + + @Column() + order: number; + + @ManyToOne(() => LogEntity, (log: LogEntity) => log.tracks) + @JoinColumn({ name: 'logId' }) + log: LogEntity; +} \ No newline at end of file diff --git a/api/src/track/track.module.ts b/api/src/track/track.module.ts new file mode 100644 index 0000000..50460ca --- /dev/null +++ b/api/src/track/track.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { TrackEntity } from "./track.entity"; +import { TrackController } from "./track.controller"; +import { FileService } from "../file/file.service"; +import { TrackService } from './track.service'; +import { LogModule } from 'src/log/log.module'; + +@Module({ + imports: [ + LogModule, + TypeOrmModule.forFeature([TrackEntity]) + ], + controllers: [TrackController], + providers: [ + FileService, + TrackService + ] +}) +export class TrackModule {} \ No newline at end of file diff --git a/api/src/track/track.service.ts b/api/src/track/track.service.ts new file mode 100644 index 0000000..d04365f --- /dev/null +++ b/api/src/track/track.service.ts @@ -0,0 +1,88 @@ +import { DeleteResult, InsertResult, Repository, UpdateResult } from "typeorm"; +import { TrackDto } from "./track.dto"; +import { TrackEntity } from "./track.entity"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Injectable } from "@nestjs/common"; +import { LogService } from "src/log/log.service"; +import { LogEntity } from "src/log/log.entity"; +import { CustomError } from "src/error/customError"; +import { FileService } from "src/file/file.service"; + +@Injectable() +export class TrackService { + constructor( + @InjectRepository(TrackEntity) private readonly trackRepository: Repository, + private readonly fileService: FileService, + private readonly logService: LogService + ) {} + + // async find(id: string): Promise { + // return await this.trackRepository.findOneBy({ id }); + // } + + // async findAll(logId: string): Promise { + // try { + // await this.trackRepository.findOneBy({ }) + // } catch (error) { + + // } + + // } + + async create(logId: string, order: number, file: Express.Multer.File): Promise { + try { + const logEntity: LogEntity = await this.logService.find(logId); + + if (logEntity) { + const containerName = 'tracks'; + const url = await this.fileService.uploadFile(file, containerName, logId); + const track = this.trackRepository.create({ + log: logEntity, + order: order, + url: url + }); + + return await this.trackRepository.insert(track); + } else { + throw new CustomError('Log not found', 'Not found', 404) + } + } catch (error) { + console.log(error) + throw error; + } + } + + // async update(id: string, track: TrackDto): Promise { + // return await this.trackRepository.update(id, track); + // } + + async delete(id: string): Promise { + try { + + } catch (error) { + + } + return await this.trackRepository.delete({ id }); + } + + // async createTrack() + + // async downloadTrack(): Promise { + // const containerName = 'tracks'; + // const downloadedFile: string = await this.fileService.downloadFile(containerName, rowKey, fileName); + + // return downloadedFile; + // } + + // async deleteTrack(): Promsie { + // try { + // const containerName = 'tracks'; + + // return await this.fileService.deleteFile(containerName, rowKey, fileName) + // } catch (error) { + // const customError = error as CustomError; + + // throw customError; + // } + // } +} \ No newline at end of file diff --git a/app/package.json b/app/package.json index 78f2368..0de4fe4 100644 --- a/app/package.json +++ b/app/package.json @@ -12,15 +12,15 @@ }, "dependencies": { "@azure/msal-browser": "^4.0.1", - "@azure/msal-react": "^3.0.1", + "@azure/msal-react": "3.0.1", "@noahspan/noahspan-components": "^1.9.1", "axios": "^1.7.2", "dotenv": "^16.4.7", "leaflet": "^1.9.4", - "react": "19.0.0-rc.1", - "react-dom": "19.0.0-rc.1", + "react": "^18", + "react-dom": "^18", "react-hook-form": "^7.51.4", - "react-leaflet": "^5.0.0", + "react-leaflet": "^4", "react-leaflet-kml": "^2.1.2", "react-router-dom": "^6.23.0", "swiper": "^11.2.6" diff --git a/app/src/components/logForm/ILogFormProps.ts b/app/src/components/logForm/ILogFormProps.ts index 0cb9d9b..6e0ecb8 100644 --- a/app/src/components/logForm/ILogFormProps.ts +++ b/app/src/components/logForm/ILogFormProps.ts @@ -1,7 +1,7 @@ import { FormMode } from '../../enums/formMode'; export interface ILogFormProps { - entryId?: string; + logId?: string; isDrawerOpen: boolean; mode: FormMode; onOpenClose: (mode: FormMode) => void; diff --git a/app/src/components/logForm/ILogFormState.ts b/app/src/components/logForm/ILogFormState.ts index e8a994b..ba0df9f 100644 --- a/app/src/components/logForm/ILogFormState.ts +++ b/app/src/components/logForm/ILogFormState.ts @@ -5,5 +5,5 @@ export interface ILogFormState { isDisabled: boolean; isLoading: boolean; pilotOptions: { label: string; value: string }[]; - selectedEntryPilotName: string; + selectedPilotName: string; } diff --git a/app/src/components/logForm/LogForm.tsx b/app/src/components/logForm/LogForm.tsx index e379045..31fa2c4 100644 --- a/app/src/components/logForm/LogForm.tsx +++ b/app/src/components/logForm/LogForm.tsx @@ -28,7 +28,7 @@ import { FormMode } from '../../enums/formMode'; import { usePilots } from '../../hooks/pilots/UsePilots'; const LogForm: React.FC = ({ - entryId, + logId, isDrawerOpen, mode, onOpenClose @@ -39,7 +39,6 @@ const LogForm: React.FC = ({ const isAuthenticated = useIsAuthenticated(); const defaultValues = { pilotId: '', - pilotName: '', date: null, aircraftMakeModel: '', aircraftIdentity: '', @@ -79,14 +78,14 @@ const LogForm: React.FC = ({ const accessToken: string = await getAccessToken(); - if (!entryId) { + if (!logId) { await httpClient.post(`api/logs`, data, { headers: { Authorization: accessToken } }); } else { - await httpClient.put(`api/logs/log/${entryId}`, data, { + await httpClient.put(`api/logs/${logId}`, data, { headers: { Authorization: accessToken } @@ -120,21 +119,13 @@ const LogForm: React.FC = ({ ? { headers: { Authorization: await getAccessToken() } } : {}; const response: AxiosResponse = await httpClient.get( - `api/logs/log/${entryId}`, + `api/logs/${logId}`, config ); - const entry = response.data; + const log = response.data; - // if (mode !== FormMode.ADD) { - // const pilot = pilots?.find((pilot) => pilot.id === entry.pilotId); - // console.log(pilot.name) - // dispatch({ - // type: 'SET_SELECTED_ENTRY_PILOT_NAME', - // payload: pilot.name - // }); - // } - - methods.reset(entry); + log.pilotId = log.pilot.id + methods.reset(log); } catch (error) { const axiosError = error as AxiosError; @@ -144,10 +135,10 @@ const LogForm: React.FC = ({ } }; - if (entryId && isDrawerOpen) { + if (logId && isDrawerOpen) { getEntry(); } - }, [entryId]); + }, [logId]); useEffect(() => { if (pilots && FormMode.ADD) { @@ -214,10 +205,6 @@ const LogForm: React.FC = ({ (pilot) => (pilot.id = event.target.value) ); - if (pilot) { - methods.setValue('pilotName', pilot.name); - } - methods.setValue('pilotId', event.target.value); }} options={ diff --git a/app/src/components/logForm/reducer.tsx b/app/src/components/logForm/reducer.tsx index d41b4dc..a4f49a1 100644 --- a/app/src/components/logForm/reducer.tsx +++ b/app/src/components/logForm/reducer.tsx @@ -6,14 +6,14 @@ type Action = | { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean } | { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] } - | { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string }; + | { type: 'SET_SELECTED_PILOT_NAME'; payload: string }; export const initialState: ILogFormState = { alert: undefined, isDisabled: false, isLoading: true, pilotOptions: [], - selectedEntryPilotName: '' + selectedPilotName: '' }; export const reducer = ( @@ -45,10 +45,10 @@ export const reducer = ( pilotOptions: action.payload }; } - case 'SET_SELECTED_ENTRY_PILOT_NAME': { + case 'SET_SELECTED_PILOT_NAME': { return { ...state, - selectedEntryPilotName: action.payload + selectedPilotName: action.payload }; } default: { diff --git a/app/src/components/logTracks/LogTracks.tsx b/app/src/components/logTracks/LogTracks.tsx index 3daa834..74fd0d1 100644 --- a/app/src/components/logTracks/LogTracks.tsx +++ b/app/src/components/logTracks/LogTracks.tsx @@ -29,7 +29,7 @@ const LogTracks = ({ isDrawerOpen, mode, onOpenClose, selectedRowKey }: LogTrack const getLog = async (): Promise => { const logResponse: AxiosResponse = await httpClient.get( - `api/logs/log/${selectedRowKey}`, + `api/logs/${selectedRowKey}`, await getConfig() ); const logData: ILogbookEntry = logResponse.data; diff --git a/app/src/components/logbook/ILogbookEntry.ts b/app/src/components/logbook/ILogbookEntry.ts index 53cca9c..d19edac 100644 --- a/app/src/components/logbook/ILogbookEntry.ts +++ b/app/src/components/logbook/ILogbookEntry.ts @@ -1,8 +1,4 @@ -import { ColumnDef } from "@noahspan/noahspan-components"; - export interface ILogbookEntry { - partitionKey: string; - rowKey: string; id: string; pilotId: string; pilotName: string; diff --git a/app/src/components/logbook/ILogbookState.ts b/app/src/components/logbook/ILogbookState.ts index 18ae283..f265c33 100644 --- a/app/src/components/logbook/ILogbookState.ts +++ b/app/src/components/logbook/ILogbookState.ts @@ -13,6 +13,6 @@ export interface ILogbookState { isFormOpen: boolean; isLoading: boolean; isTracksOpen: boolean; - selectedEntryId: string | undefined; + selectedLogId: string | undefined; tracksMode: FormMode; } diff --git a/app/src/components/logbook/Logbook.tsx b/app/src/components/logbook/Logbook.tsx index 9764945..8a071fb 100644 --- a/app/src/components/logbook/Logbook.tsx +++ b/app/src/components/logbook/Logbook.tsx @@ -42,9 +42,9 @@ const Logbook: React.FC = () => { }, cell: (info: any) => ( ) @@ -94,16 +94,16 @@ const Logbook: React.FC = () => { } }; - const onOpenCloseEntryForm = (mode: FormMode, entryId?: string) => { + const onOpenCloseLogForm = (mode: FormMode, logId?: string) => { switch (mode) { case FormMode.ADD: case FormMode.EDIT: case FormMode.VIEW: dispatch({ - type: 'SET_OPEN_CLOSE_ENTRY_FORM', + type: 'SET_OPEN_CLOSE_LOG_FORM', payload: { formMode: mode, - selectedEntryId: entryId, + selectedLogId: logId, isFormOpen: true } }); @@ -111,10 +111,10 @@ const Logbook: React.FC = () => { break; case FormMode.CANCEL: dispatch({ - type: 'SET_OPEN_CLOSE_ENTRY_FORM', + type: 'SET_OPEN_CLOSE_LOG_FORM', payload: { formMode: mode, - selectedEntryId: undefined, + selectedLogId: undefined, isFormOpen: false } }); @@ -151,10 +151,10 @@ const Logbook: React.FC = () => { } } - const onDeleteEntry = (entryId: string) => { + const onDeleteLog = (logId: string) => { dispatch({ type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: true, selectedEntryId: entryId } + payload: { isConfirmationDialogOpen: true, selectedLogId: logId } }); }; @@ -167,11 +167,11 @@ const Logbook: React.FC = () => { ? { headers: { Authorization: `${token}` } } : {}; - await httpClient.delete(`api/logs/log/${state.selectedEntryId}`, config); + await httpClient.delete(`api/logs/${state.selectedLogId}`, config); dispatch({ type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedEntryId: undefined } + payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } }); await getLogbookEntries(); } catch (error) { @@ -189,7 +189,7 @@ const Logbook: React.FC = () => { const onConfirmationDialogCancel = () => { dispatch({ type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedEntryId: undefined } + payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } }); }; @@ -233,7 +233,7 @@ const Logbook: React.FC = () => { {isAuthenticated &&