diff --git a/api/package.json b/api/package.json index f9a6e7d..4e51c07 100644 --- a/api/package.json +++ b/api/package.json @@ -35,7 +35,7 @@ "@nestjs/serve-static": "^5.0.3", "@nestjs/typeorm": "^11.0.0", "@noahspan/azure-database": "^3.1.2", - "@noahspan/noahspan-modules": "^1.2.8", + "@noahspan/noahspan-modules": "^1.2.9", "@schematics/angular": "^17.3.7", "@types/multer": "^1.4.12", "better-sqlite3": "^12.2.0", diff --git a/api/src/certificate/certificate.entity.ts b/api/src/certificate/certificate.entity.ts index 6e670d3..0a1acbc 100644 --- a/api/src/certificate/certificate.entity.ts +++ b/api/src/certificate/certificate.entity.ts @@ -15,7 +15,7 @@ export class CertificateEntity { @Column() issueDate: Date - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.certificates) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.certificates, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/endorsement/endorsement.entity.ts b/api/src/endorsement/endorsement.entity.ts index 0c766cd..240e40b 100644 --- a/api/src/endorsement/endorsement.entity.ts +++ b/api/src/endorsement/endorsement.entity.ts @@ -12,7 +12,7 @@ export class EndorsementEntity { @Column() issueDate: Date; - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.endorsements) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.endorsements, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity } \ No newline at end of file diff --git a/api/src/file/file.service.ts b/api/src/file/file.service.ts index 9ae05fa..3c82e58 100644 --- a/api/src/file/file.service.ts +++ b/api/src/file/file.service.ts @@ -58,11 +58,32 @@ import { ConfigService } from '@nestjs/config'; return downloaded } - async deleteFile(containerName: string, rowKey:string, fileName: string): Promise { + async deleteFile(containerName: string, logId: string, fileName: string): Promise { this.containerName = containerName; - const blockBlobClient = await this.getBlobClient(`${rowKey}/${fileName}`); + const blockBlobClient = await this.getBlobClient(`${logId}/${fileName}`); await blockBlobClient.deleteIfExists(); } + + async deleteFolder(containerName: string, logId: string): Promise { + const blobService = await this.getBlobServiceInstance(); + + this.containerName = containerName; + + const containerClient = blobService.getContainerClient(containerName); + const blobsToDelete = [] + + for await (const blob of containerClient.listBlobsFlat({ prefix: logId })) { + blobsToDelete.push(blob.name) + } + + for (const blobName of blobsToDelete) { + const blockBlobClient = containerClient.getBlockBlobClient(blobName); + + await blockBlobClient.delete(); + } + + return; + } } \ No newline at end of file diff --git a/api/src/log/log.controller.ts b/api/src/log/log.controller.ts index f139cf6..2e6dd62 100644 --- a/api/src/log/log.controller.ts +++ b/api/src/log/log.controller.ts @@ -14,14 +14,16 @@ import { LogDto } from './log.dto'; import { LogEntity } from './log.entity'; import { LogService } from './log.service'; import { CustomError } from '../error/customError'; -import { AuthGuard } from '@noahspan/noahspan-modules'; +import { AuthGuard, Public } from '@noahspan/noahspan-modules'; import { LogInterceptor } from './log.interceptor'; import { FileService } from '../file/file.service'; import { DeleteResult, InsertResult, UpdateResult } from 'typeorm'; +import { Reflector } from '@nestjs/core'; + +const reflector = new Reflector(); @Controller('logs') -@UseInterceptors(new LogInterceptor()) -// @UseGuards(AuthGuard) +@UseInterceptors(new LogInterceptor(reflector)) export class LogController { constructor( private readonly fileService: FileService, @@ -30,6 +32,7 @@ export class LogController { @Get(':id') + @Public() async find( @Param('id') id: string, ): Promise { @@ -44,6 +47,7 @@ export class LogController { } @Get() + @Public() async findAll(): Promise { try { return await this.logService.findAll(); @@ -56,6 +60,7 @@ export class LogController { @Post() + @UseGuards(AuthGuard) async create(@Body() logDto: LogDto): Promise { try { return await this.logService.create(logDto); @@ -67,6 +72,7 @@ export class LogController { } @Put(':id') + @UseGuards(AuthGuard) async update( @Param('id') id: string, @Body() logDto: LogDto @@ -83,12 +89,14 @@ export class LogController { } @Delete(':id') + @UseGuards(AuthGuard) async delete( @Param('id') id: string, ): Promise { try { return await this.logService.delete(id); } catch (error) { + console.log(error) const customError = error as CustomError; throw new HttpException(customError.message, customError.statusCode); diff --git a/api/src/log/log.entity.ts b/api/src/log/log.entity.ts index 884645f..4c9fe24 100644 --- a/api/src/log/log.entity.ts +++ b/api/src/log/log.entity.ts @@ -76,10 +76,10 @@ export class LogEntity { @Column({ nullable: true }) notes: string | null; - @OneToMany(() => TrackEntity, (track: TrackEntity) => track.log, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => TrackEntity, (track: TrackEntity) => track.log) tracks: TrackEntity[] - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.logs) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.logs, {onDelete: 'CASCADE', onUpdate: 'CASCADE'}) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/log/log.interceptor.ts b/api/src/log/log.interceptor.ts index f10f6f9..15676dc 100644 --- a/api/src/log/log.interceptor.ts +++ b/api/src/log/log.interceptor.ts @@ -3,18 +3,23 @@ import { Observable, map } from 'rxjs'; import { LogEntity } from './log.entity'; import { jwtDecode } from 'jwt-decode'; import { CustomJwtPayload } from 'src/interfaces/customJwtPayload.interface'; +import { IS_PUBLIC_KEY } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; export class LogInterceptor implements NestInterceptor { + constructor(private reflector: Reflector) {} + intercept(context: ExecutionContext, handler: CallHandler): Observable { - const req = context.switchToHttp().getRequest(); - const authHeader = req.headers.authorization; - const token = authHeader && authHeader.split(' ')[1]; - const jwtPayload: CustomJwtPayload = jwtDecode(token); + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); return handler.handle().pipe( map((data: LogEntity[]) => { - if (data.length > 0 && jwtPayload.roles.includes('Flying.Read')) { - const logs = data.map((log: LogEntity) => { + const req = context.switchToHttp().getRequest(); + const limitData = (data) => { + return data.map((log: LogEntity) => { return { id: log.id, pilot: { @@ -28,10 +33,25 @@ export class LogInterceptor implements NestInterceptor { tracks: log.tracks, }; }); + } + + if (req.headers.authorization) { + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + if (jwtPayload.roles.includes('Flying.Read')) { + const logs = limitData(data); + + return logs; + } else { + return data; + } + } else if (!req.headers.authorization && isPublic) { + const publicData = limitData(data) + const logs = publicData.slice(0, 5) return logs; - } else { - return data; } }) ); diff --git a/api/src/log/log.module.ts b/api/src/log/log.module.ts index c08d8c7..b2a93af 100644 --- a/api/src/log/log.module.ts +++ b/api/src/log/log.module.ts @@ -1,28 +1,14 @@ import { Module } from '@nestjs/common'; import { LogController } from './log.controller'; import { LogService } from './log.service'; -// import { AzureTableStorageModule } from '@noahspan/azure-database'; import { LogEntity } from './log.entity'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { 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' - // }), PilotModule, TypeOrmModule.forFeature([LogEntity]) ], diff --git a/api/src/log/log.service.ts b/api/src/log/log.service.ts index 2076da2..ba46213 100644 --- a/api/src/log/log.service.ts +++ b/api/src/log/log.service.ts @@ -6,19 +6,20 @@ 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'; +import { FileService } from 'src/file/file.service'; @Injectable() export class LogService { constructor( @InjectRepository(LogEntity) private readonly logRepository: Repository, - private readonly pilotService: PilotService + private readonly fileService: FileService, + private readonly pilotService: PilotService, ) {} async find(id: string): Promise { const logEntity: LogEntity = await this.logRepository.findOne({ where: { id: id }, - // relations: ['pilot', 'tracks'] + relations: ['pilot', 'tracks'] }); return logEntity; @@ -55,6 +56,8 @@ export class LogService { } async delete(id: string): Promise { + await this.fileService.deleteFolder('tracks', id); + return await this.logRepository.delete({ id }); } } diff --git a/api/src/medical/medical.entity.ts b/api/src/medical/medical.entity.ts index df590c3..02550fc 100644 --- a/api/src/medical/medical.entity.ts +++ b/api/src/medical/medical.entity.ts @@ -12,7 +12,7 @@ export class MedicalEntity { @Column() expirationDate: Date; - @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.medical) + @ManyToOne(() => PilotEntity, (pilot: PilotEntity) => pilot.medical, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'pilotId' }) pilot: PilotEntity; } \ No newline at end of file diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index a3182d7..3ecc715 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -11,19 +11,21 @@ import { UseInterceptors, } from '@nestjs/common'; import { PilotDto } from './pilot.dto'; -import { PilotEntity } from './pilot.entity'; import { PilotService } from './pilot.service'; import { CustomError } from '../error/customError'; import { PilotInterceptor } from './pilot.interceptor'; -import { AuthGuard } from '@noahspan/noahspan-modules'; +import { AuthGuard, Public } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; + +const reflector = new Reflector(); @Controller('pilots') -@UseInterceptors(new PilotInterceptor()) -@UseGuards(AuthGuard) +@UseInterceptors(new PilotInterceptor(reflector)) export class PilotController { constructor(private readonly pilotService: PilotService) {} @Get(':id') + @Public() async find(@Param('id') id: string) { try { return await this.pilotService.find(id); @@ -35,28 +37,33 @@ export class PilotController { } @Get() + @Public() async findAll() { try { return await this.pilotService.findAll(); } catch (error) { const customError = error as CustomError; - + console.log(error) throw new HttpException(customError.message, customError.statusCode); } } @Post() + @UseGuards(AuthGuard) async create(@Body() pilotDto: PilotDto) { try { + return await this.pilotService.create(pilotDto); } catch (error) { - const customError = error as CustomError; + console.log(error) + // const customError = error as CustomError; - throw new HttpException(customError.message, customError.statusCode); + // throw new HttpException(customError.message, customError.statusCode); } } @Put(':id') + @UseGuards(AuthGuard) async update( @Param('id') id: string, @Body() pilotDto: PilotDto @@ -71,6 +78,7 @@ export class PilotController { } @Delete(':id') + @UseGuards(AuthGuard) async delete( @Param('id') id: string, ) { diff --git a/api/src/pilot/pilot.entity.ts b/api/src/pilot/pilot.entity.ts index cf18f2d..d78a017 100644 --- a/api/src/pilot/pilot.entity.ts +++ b/api/src/pilot/pilot.entity.ts @@ -33,15 +33,15 @@ export class PilotEntity { @Column() userId: string | null; - @OneToMany(() => LogEntity, (log: LogEntity) => log.pilot, {onDelete: 'CASCADE', onUpdate: 'CASCADE'}) + @OneToMany(() => LogEntity, (log: LogEntity) => log.pilot) logs: LogEntity[]; - @OneToMany(() => CertificateEntity, (certificate: CertificateEntity) => certificate.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => CertificateEntity, (certificate: CertificateEntity) => certificate.pilot) certificates: CertificateEntity[]; - @OneToMany(() => EndorsementEntity, (endorsement: EndorsementEntity) => endorsement.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => EndorsementEntity, (endorsement: EndorsementEntity) => endorsement.pilot) endorsements: EndorsementEntity[]; - @OneToMany(() => MedicalEntity, (medical: MedicalEntity) => medical.pilot, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + @OneToMany(() => MedicalEntity, (medical: MedicalEntity) => medical.pilot) medical: MedicalEntity[]; } diff --git a/api/src/pilot/pilot.interceptor.ts b/api/src/pilot/pilot.interceptor.ts index 522c832..5ef7036 100644 --- a/api/src/pilot/pilot.interceptor.ts +++ b/api/src/pilot/pilot.interceptor.ts @@ -1,31 +1,49 @@ -import { CallHandler, ExecutionContext, NestInterceptor, UnauthorizedException } from '@nestjs/common'; +import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; import { jwtDecode } from 'jwt-decode'; import { Observable, map } from 'rxjs'; import { CustomJwtPayload } from 'src/interfaces/customJwtPayload.interface'; import { PilotEntity } from './pilot.entity'; +import { IS_PUBLIC_KEY } from '@noahspan/noahspan-modules'; +import { Reflector } from '@nestjs/core'; export class PilotInterceptor implements NestInterceptor { + constructor(private reflector: Reflector) {} + intercept(context: ExecutionContext, handler: CallHandler): Observable { - const req = context.switchToHttp().getRequest(); - const authHeader = req.headers.authorization; - const token = authHeader && authHeader.split(' ')[1]; - const jwtPayload: CustomJwtPayload = jwtDecode(token); + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); return handler.handle().pipe( map((data: PilotEntity[]) => { - if (data.length && jwtPayload.roles.includes('Flying.Read')) { - const pilots = data.map((pilot) => { + const req = context.switchToHttp().getRequest(); + const limitData = (data) => { + return data.map((pilot: PilotEntity) => { return { id: pilot.id, name: pilot.name }; - }); + }) + } - return pilots; - } else if (data.length && jwtPayload.roles.includes('Flying.Write')) { - return data; - } else { - return UnauthorizedException; + if (req.headers.authorization) { + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.split(' ')[1]; + const jwtPayload: CustomJwtPayload = jwtDecode(token); + + if (jwtPayload.roles.includes('Flying.Read')) { + const pilots = limitData(data) + + return pilots; + } else { + return data; + } + } else if (!req.headers.authorization && isPublic) { + const publicData = limitData(data); + const logs = publicData.slice(0,5) + + return logs; } }) ); diff --git a/api/src/pilot/pilot.module.ts b/api/src/pilot/pilot.module.ts index b6d0aaf..7fea858 100644 --- a/api/src/pilot/pilot.module.ts +++ b/api/src/pilot/pilot.module.ts @@ -1,40 +1,11 @@ import { Module } from '@nestjs/common'; import { PilotController } from './pilot.controller'; import { PilotService } from './pilot.service'; -// 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(PilotEntity, { - // createTableIfNotExists: false, - // table: 'pilots' - // }), TypeOrmModule.forFeature([PilotEntity]) ], controllers: [PilotController], diff --git a/api/src/track/track.controller.ts b/api/src/track/track.controller.ts index caff203..594e8a6 100644 --- a/api/src/track/track.controller.ts +++ b/api/src/track/track.controller.ts @@ -1,12 +1,10 @@ import { - Body, Controller, Delete, Get, HttpException, Param, Post, - Put, Query, UploadedFile, UseGuards, @@ -18,8 +16,7 @@ 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'; +import { DeleteResult, InsertResult } from 'typeorm'; @Controller('tracks') export class TrackController { @@ -28,22 +25,10 @@ export class TrackController { private readonly trackService: TrackService ) {} - // @Get('id') - // async find(@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); - // } - // } - @UseGuards(AuthGuard) @Get(':logId') async findAll(@Param('logId') logId: string): Promise { try { - console.log('logId: ' + logId) return await this.trackService.findAll(logId); } catch (error) { const customError = error as CustomError; @@ -66,11 +51,10 @@ export class TrackController { } @UseGuards(AuthGuard) - @Delete(':id') - async delete(@Param('id') id: string, @Query('fileName') fileName: string, @Query('logId') logId: string): Promise { + @Delete(':id/:filename/:logId') + async delete(@Param('id') id: string, @Query('fileName') filename: string, @Query('logId') logId: string): Promise { try { - - return await this.trackService.delete(id, logId, fileName); + return await this.trackService.delete(id, logId, filename); } catch (error) { const customError = error as CustomError; diff --git a/api/src/track/track.entity.ts b/api/src/track/track.entity.ts index d25eab8..af3e65a 100644 --- a/api/src/track/track.entity.ts +++ b/api/src/track/track.entity.ts @@ -12,7 +12,7 @@ export class TrackEntity { @Column() order: number; - @ManyToOne(() => LogEntity, (log: LogEntity) => log.tracks) + @ManyToOne(() => LogEntity, (log: LogEntity) => log.tracks, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @JoinColumn({ name: 'logId' }) log: LogEntity; } \ No newline at end of file diff --git a/api/src/track/track.service.ts b/api/src/track/track.service.ts index 8460bff..9414905 100644 --- a/api/src/track/track.service.ts +++ b/api/src/track/track.service.ts @@ -31,7 +31,6 @@ export class TrackService { const logEntity: LogEntity = await this.logService.find(logId); if (logEntity) { - console.log(logEntity) const tracks = await this.trackRepository.find({ where: { log: { @@ -40,7 +39,6 @@ export class TrackService { }, }) - console.log(tracks) return tracks; } } catch (error) { @@ -55,7 +53,6 @@ export class TrackService { if (logEntity) { const url = await this.fileService.uploadFile(file, this.containerName, logId); - console.log(url) const track = this.trackRepository.create({ log: logEntity, order: order, @@ -67,7 +64,6 @@ export class TrackService { throw new CustomError('Log not found', 'Not found', 404) } } catch (error) { - console.log(error) throw error; } } @@ -80,16 +76,6 @@ export class TrackService { } } - async delete(id: string, logId: string, fileName: string): Promise { - try { - await this.fileService.deleteFile(this.containerName, logId, fileName); - - return await this.trackRepository.delete({ id }); - } catch (error) { - throw error - } - } - async downloadTrackFile(logId: string, fileName: string): Promise { try { const downloadedFile: string = await this.fileService.downloadFile(this.containerName, logId, fileName); @@ -99,4 +85,14 @@ export class TrackService { throw error } } + + async delete(id: string, logId: string, fileName: string): Promise { + try { + await this.fileService.deleteFile(this.containerName, logId, fileName); + + return await this.trackRepository.delete({ id }); + } catch (error) { + throw error + } + } } \ No newline at end of file diff --git a/client/src/auth/oidcConfig.ts b/client/src/auth/oidcConfig.ts index 651135f..8e08f73 100644 --- a/client/src/auth/oidcConfig.ts +++ b/client/src/auth/oidcConfig.ts @@ -5,7 +5,7 @@ export const { OidcProvider, useOidc, getOidc } = createReactOidc(async () => ({ clientId: import.meta.env.VITE_CLIENT_APP_ID, homeUrl: import.meta.env.BASE_URL, scopes: ['email', 'openid', 'profile', `api://${import.meta.env.VITE_API_APP_ID}/user_impersonation`], - autoLogin: true, + autoLogin: false, postLoginRedirectUrl: '/', noIframe: true })); \ No newline at end of file diff --git a/client/src/components/confirmationDialog/ConfirmationDialog.tsx b/client/src/components/confirmationDialog/ConfirmationDialog.tsx index c327613..2a467de 100644 --- a/client/src/components/confirmationDialog/ConfirmationDialog.tsx +++ b/client/src/components/confirmationDialog/ConfirmationDialog.tsx @@ -1,13 +1,16 @@ -import { - Button, - Dialog, - DialogActions, - DialogContent, - Icon, - IconName, - Loading -} from '@noahspan/noahspan-components'; -import { IDialogConfirmationProps } from './IConfirmationDialogProps'; +// import { +// Button, +// Dialog, +// DialogActions, +// DialogContent, +// Icon, +// IconName, +// Loading +// } from '@noahspan/noahspan-components'; +import { Button, Modal, ModalBody, ModalContent, ModalHeader, ModalFooter, Spinner } from '@heroui/react' +import { DialogConfirmationProps } from './ConfirmationDialogProps.interface'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCircleCheck, faXmark } from '@fortawesome/free-solid-svg-icons'; const ConfirmationDialog = ({ contentText, @@ -16,28 +19,33 @@ const ConfirmationDialog = ({ onCancel, onConfirm, title -}: IDialogConfirmationProps) => { +}: DialogConfirmationProps) => { return ( - -

{title}

- - {!isLoading &&
{contentText}
} - {isLoading && } -
- - - - -
+ + {title} + + {!isLoading &&
{contentText}
} + {isLoading && } +
+ + + + +
+ ); }; diff --git a/client/src/components/confirmationDialog/IConfirmationDialogProps.ts b/client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts similarity index 75% rename from client/src/components/confirmationDialog/IConfirmationDialogProps.ts rename to client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts index 90cd809..1d7fd0e 100644 --- a/client/src/components/confirmationDialog/IConfirmationDialogProps.ts +++ b/client/src/components/confirmationDialog/ConfirmationDialogProps.interface.ts @@ -1,4 +1,4 @@ -export interface IDialogConfirmationProps { +export interface DialogConfirmationProps { contentText: string; isLoading: boolean; isOpen: boolean; diff --git a/client/src/components/flights/Flights.tsx b/client/src/components/flights/Flights.tsx index 075e05a..691b0f1 100644 --- a/client/src/components/flights/Flights.tsx +++ b/client/src/components/flights/Flights.tsx @@ -20,8 +20,9 @@ const Flights = () => { if (flights && flights.length > 0) { dispatch({ type: 'SET_FLIGHTS', payload: flights}) + dispatch({ type: 'SET_ALERT', payload: undefined }) } else { - dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'There are no flights' }}) + dispatch({ type: 'SET_ALERT', payload: { severity: 'default', message: 'No flights found' }}) } }, [logs]) diff --git a/client/src/components/logForm/LogForm.tsx b/client/src/components/logForm/LogForm.tsx index f63de4a..b9612ca 100644 --- a/client/src/components/logForm/LogForm.tsx +++ b/client/src/components/logForm/LogForm.tsx @@ -64,10 +64,6 @@ const LogForm = () => { } }, [pilots]); - useEffect(() => { - console.log(state.isDisabled) - }, [state.isDisabled]) - return (
diff --git a/client/src/components/logbook/Logbook.tsx b/client/src/components/logbook/Logbook.tsx index b828e2c..321859c 100644 --- a/client/src/components/logbook/Logbook.tsx +++ b/client/src/components/logbook/Logbook.tsx @@ -132,17 +132,11 @@ const Logbook: React.FC = () => { > View - onOpenCloseDrawer(FormMode.EDIT, info.row.original.id)} - startContent={} - > - Tracks - onDeleteLog(info.row.original.id)} startContent={} > Delete @@ -393,7 +387,6 @@ const Logbook: React.FC = () => { }; const onOpenCloseDrawer= (mode: FormMode, logId?: string) => { - console.log(logId) switch (mode) { case FormMode.ADD: case FormMode.EDIT: @@ -423,10 +416,8 @@ const Logbook: React.FC = () => { }; const onDeleteLog = (logId: string) => { - dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: true, selectedLogId: logId } - }); + dispatch({ type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', payload: true }); + logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: logId }) }; const onConfirmationDialogConfirm = async () => { @@ -436,9 +427,10 @@ const Logbook: React.FC = () => { await httpClient.delete(`api/logs/${logbookContext.state.selectedLogId}`); dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } + type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', + payload: false }); + logbookContext.dispatch({ type: 'SET_SELECTED_LOG_ID', payload: '' }) await getLogbookEntries(); } catch (error) { const axiosError = error as AxiosError; @@ -454,8 +446,8 @@ const Logbook: React.FC = () => { const onConfirmationDialogCancel = () => { dispatch({ - type: 'SET_DELETE', - payload: { isConfirmationDialogOpen: false, selectedLogId: undefined } + type: 'SET_IS_CONFIRMATION_DIALOG_OPEN', + payload: false }); }; @@ -523,7 +515,7 @@ const Logbook: React.FC = () => { }
{!state.isLoading && state.alert && ( -
+
dispatch({ type: 'SET_ALERT', payload: undefined }) diff --git a/client/src/components/logbook/columns.tsx b/client/src/components/logbook/columns.tsx index 6c80ad5..37315ce 100644 --- a/client/src/components/logbook/columns.tsx +++ b/client/src/components/logbook/columns.tsx @@ -34,7 +34,6 @@ const date: ColumnDef = { header: 'Date', cell: (info: CellContext) => { const date = new Date(info.getValue() as string); - console.log(date) const formattedDate = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` return formattedDate; diff --git a/client/src/components/logbook/reducer.ts b/client/src/components/logbook/reducer.ts index a470bca..43bb230 100644 --- a/client/src/components/logbook/reducer.ts +++ b/client/src/components/logbook/reducer.ts @@ -6,13 +6,7 @@ import { LogbookState } from './LogbookState.interface'; type Action = | { type: 'SET_COLUMNS'; payload: ColumnDef[] } - | { - type: 'SET_DELETE'; - payload: { - isConfirmationDialogOpen: boolean; - selectedLogId: string | undefined; - }; - } + | { type: 'SET_IS_CONFIRMATION_DIALOG_OPEN'; payload: boolean } | { type: 'SET_ENTRIES'; payload: LogbookEntry[] } | { type: 'SET_ALERT'; payload: Alert | undefined } | { type: 'SET_IS_CONFIRMATION_DIALOG_LOADING'; payload: boolean } @@ -39,10 +33,10 @@ export const reducer = ( columns: action.payload } } - case 'SET_DELETE': { + case 'SET_IS_CONFIRMATION_DIALOG_OPEN': { return { ...state, - isConfirmDialogOpen: action.payload.isConfirmationDialogOpen, + isConfirmDialogOpen: action.payload, }; } case 'SET_ENTRIES': { diff --git a/client/src/components/logbookCard/LogbookCard.tsx b/client/src/components/logbookCard/LogbookCard.tsx index e32b353..8fdce5d 100644 --- a/client/src/components/logbookCard/LogbookCard.tsx +++ b/client/src/components/logbookCard/LogbookCard.tsx @@ -1,86 +1,72 @@ -import { Card, CardBody, CardContent, CardHeader } from "@noahspan/noahspan-components"; +import { Accordion, AccordionItem, Card, CardBody, CardHeader } from '@heroui/react' import { LogbookCardProps } from "./LogbookCardProps.interface"; -import ActionMenu from "../actionMenu/ActionMenu"; -import LogTrackMaps from "../logTrackMaps/LogTrackMaps"; - +import TrackMap from "../trackMap/TrackMap"; const LogbookCard = ({ logs, mode, onDelete, onOpenCloseForm }: LogbookCardProps) => { return (
{logs.map((log) => { + console.log(log) const date = new Date(log.date); const formattedDate: string = `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`; return (
- - - - -
- {mode === 'flights' && log.tracks && log.tracks.length > 0 && -
- -
- } -
- Aircraft Make and Model -
-
- {log.aircraftMakeModel} -
-
- Route From -
-
- {log.routeFrom} -
-
- Route To -
-
- {log.routeTo} -
-
- Duration Of Flight -
-
- {log.durationOfFlight} -
- {mode === 'logbook' && log.tracks && log.tracks.length > 0 && - <> -
- Tracks -
- {log.tracks.map((track: { id: string; order: number; url: string}) => { - const trackSplit = track.url.split('/') - const filename = trackSplit[trackSplit.length - 1]; - - return ( -
- {filename} -
- ) - - })} - - } - {log.notes && - <> -
- Notes -
-
- {log.notes} -
- - } + + +

{formattedDate}

+
+ + {mode === 'flights' && log.tracks && log.tracks.length > 0 && +
+
- - + } + + +
+
+ Aircraft Make and Model +
+
+ {log.aircraftMakeModel} +
+
+ Route From +
+
+ {log.routeFrom} +
+
+ Route To +
+
+ {log.routeTo} +
+
+ Duration Of Flight +
+
+ {log.durationOfFlight} +
+ {log.notes && + <> +
+ Notes +
+
+ {log.notes} +
+ + } +
+
+
+
diff --git a/client/src/components/logbookDrawer/LogbookDrawer.tsx b/client/src/components/logbookDrawer/LogbookDrawer.tsx index 4e545f2..ebb3f32 100644 --- a/client/src/components/logbookDrawer/LogbookDrawer.tsx +++ b/client/src/components/logbookDrawer/LogbookDrawer.tsx @@ -9,8 +9,10 @@ import { FormMode } from "../../enums/formMode"; import httpClient from "../../httpClient/httpClient"; import { AxiosError } from "axios"; import { useLogbookContext } from "../../hooks/logbookContext/UseLogbookContext"; +import { Key, useState } from "react"; const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { + const [activeTab, setActiveTab] = useState('time'); const defaultValues = { pilotId: '', date: null, @@ -34,8 +36,7 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { instrumentApproaches: null, instrumentHolds: null, instrumentNavTrack: null, - notes: '', - tracks: [] + notes: '' }; const methods = useForm(); const logbookContext = useLogbookContext() @@ -69,9 +70,19 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { } }; + const onSelectedKeyChanged = (key: React.Key) => { + setActiveTab(key) + } + return ( + + + } isOpen={logbookContext.state.isDrawerOpen} + onClose={onCancel} > @@ -91,7 +102,13 @@ const LogbookDrawer = ({ onOpenClose }: LogbookDrawerProps) => { />
)} - + { > - - - Tracks -
- } - > - - + {logbookContext.state.formMode !== FormMode.ADD && + + + Tracks +
+ } + > + + + } - -
-
- - {logbookContext.state.formMode.toString() !== FormMode.VIEW && ( + {activeTab !== 'tracks' && + +
+
- )} + {logbookContext.state.formMode.toString() !== FormMode.VIEW && ( + + )} +
-
- + + } diff --git a/client/src/components/pilotForm/PilotForm.tsx b/client/src/components/pilotForm/PilotForm.tsx index 5027c22..9130706 100644 --- a/client/src/components/pilotForm/PilotForm.tsx +++ b/client/src/components/pilotForm/PilotForm.tsx @@ -1,15 +1,5 @@ -import { useEffect, useState } from 'react'; +import { Key, useEffect, useState } from 'react'; import { useForm, Controller, FormProvider } from 'react-hook-form'; -import { - // Button, - // Drawer, - Icon, - IconButton, - IconName, - // Input, - PeoplePicker, - StateSelect -} from '@noahspan/noahspan-components'; import { IPilotFormProps } from './IPilotFormProps'; import { AxiosError, AxiosResponse } from 'axios'; import { FormMode } from '../../enums/formMode'; @@ -18,11 +8,11 @@ import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificate import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsements'; import PilotFormMedical from '../pilotFormMedical/PilotFormMedical'; import { useOidc } from '../../auth/oidcConfig'; -import { getOidc } from '../../auth/oidcConfig'; import httpClient from '../../httpClient/httpClient'; -import { Button, Drawer, DrawerHeader, DrawerContent, DrawerBody, DrawerFooter, Input } from '@heroui/react' +import { Button, Drawer, DrawerHeader, DrawerContent, DrawerBody, DrawerFooter, Input, Autocomplete, AutocompleteItem, SelectItem, Select } from '@heroui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons'; +import { states } from './states'; const PilotForm: React.FC = ({ pilotId, @@ -31,7 +21,7 @@ const PilotForm: React.FC = ({ onOpenClose }: IPilotFormProps) => { const [peoplePickerValue, setPeoplePickerValue] = useState(''); - const [peoplePickerResults, setPeoplePickerResults] = useState([]); + const [peoplePickerResults, setPeoplePickerResults] = useState([]); const [isPeoplePickerLoading, setIsPeoplePickerLoading] = useState(false); const [selectedPerson, setSelectedPerson] = useState({ @@ -47,6 +37,7 @@ const PilotForm: React.FC = ({ postalCode: '', email: '', phone: '', + userId: '' }; const methods = useForm({ defaultValues: defaultValues @@ -63,15 +54,10 @@ const PilotForm: React.FC = ({ try { if (value !== '') { const searchString: string = value; - const oidc = await getOidc(); const response: AxiosResponse = await httpClient.get( - `api/msgraph/search?search=${searchString}`, { - headers: { - Authorization: oidc.isUserLoggedIn ? `Bearer ${(await oidc.getTokens()).accessToken}` : '' - } - } + `api/msgraph/search?search=${searchString}` ); - console.log(response) + setPeoplePickerResults(response.data); } else { setPeoplePickerResults([]); @@ -83,9 +69,12 @@ const PilotForm: React.FC = ({ } }; - const onPersonSelected = (person: Person) => { - methods.setValue('name', person.displayName!.toString()); - setPeoplePickerValue(person.displayName!); + const onPersonSelected = (userPrincipalName: string) => { + const person: Person | undefined = peoplePickerResults.find((person) => person.userPrincipalName === userPrincipalName as string); + + methods.setValue('name', person?.displayName!); + methods.setValue('userId', person?.userPrincipalName!); + setPeoplePickerValue(person?.displayName!); setPeoplePickerResults([]) }; @@ -134,9 +123,7 @@ const PilotForm: React.FC = ({ ); const pilot = response.data; - setSelectedPerson({ - displayName: pilot.name - }); + setPeoplePickerValue(pilot.name); methods.reset(pilot); } catch (error) { console.log(error); @@ -175,15 +162,19 @@ const PilotForm: React.FC = ({
Name *
- + onPersonSelected(key as string)} + > + {peoplePickerResults.map((person: Person) => ( + + {person.displayName} + + ))} +
{isUserLoggedIn && <> @@ -244,19 +235,14 @@ const PilotForm: React.FC = ({ control={methods.control} rules={{ required: 'A state must be selected' }} render={({ field: { onChange, value } }) => ( - + selectedKeys={[value]} + > + {states.map((state) => ( + {state.label} + ))} + )} />
@@ -369,7 +355,7 @@ const PilotForm: React.FC = ({ ? isDisabled : false } - startContent={} + startContent={} onPress={onCancel} data-testid="pilot-cancel-button" > @@ -379,7 +365,7 @@ const PilotForm: React.FC = ({
{!state.isLoading && state.alert && ( -
+
dispatch({ type: 'SET_ALERT', payload: undefined }) @@ -259,7 +212,6 @@ const Pilots: React.FC = () => { )}
{state.pilots.length > 0 && screenSize !== ScreenSize.SM && - //
{(column) => ( @@ -285,7 +237,7 @@ const Pilots: React.FC = () => {
} {state.pilots.length > 0 && screenSize === ScreenSize.SM && - + }
@@ -299,7 +251,7 @@ const Pilots: React.FC = () => { )} {state.isConfirmDialogOpen && ( { - const [loading, setLoading] = useState(false); const [userPhoto, setUserPhoto] = useState(); - const [pages, setPages] = useState<{ name: string; path: string; }[]>([]); const appContext = useAppContext(); - const { isUserLoggedIn, login, logout } = useOidc() - const navigate = useNavigate(); + const { isUserLoggedIn, logout, login } = useOidc() const { pathname } = useLocation() - const getPages = () => { - const pages = [ - { - name: 'Flights', - path: '/' - }, - { - name: 'Logbook', - path: '/logbook' - }, - { - name: 'Pilots', - path: '/pilots' - } - ]; - - setPages(pages) - }; - const handleSignIn = () => { - // auth.signinRedirect(); - // auth.signinRedirect({ - // scope: `api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation` - // }) - login(); - // console.log(auth.user?.access_token) - }; - const handleSignOut = () => { - // auth.signoutRedirect(); - logout() - }; + const pages = [ + { + name: 'Flights', + path: '/' + }, + { + name: 'Logbook', + path: '/logbook' + }, + { + name: 'Pilots', + path: '/pilots' + } + ]; const getUserProfile = async (): Promise => { try { - const response: AxiosResponse = await httpClient.get(`api/user/profile`, { - // headers: { - // Authorization: `Bearer ${accessToken}` - // } - }); + const response: AxiosResponse = await httpClient.get(`api/msgraph/profile`); const userProfile: User = response.data; return userProfile; @@ -64,10 +40,7 @@ const SiteNav = () => { }; const getUserPhoto = async (): Promise => { try { - const response: AxiosResponse = await httpClient.get(`api/user/photo`, { - // headers: { - // Authorization: accessToken - // }, + const response: AxiosResponse = await httpClient.get(`api/msgraph/photo`, { responseType: 'arraybuffer' }); const arrayBufferView = new Uint8Array(response.data); @@ -79,82 +52,49 @@ const SiteNav = () => { throw new Error(); } }; - const handlePageClick = (url: string) => { - navigate(url); - }; - - // const Settings = () => { - // return ( - //
- // - // - // Sign Out - // - //
- // ); - // }; useEffect(() => { const setUserProfile = async () => { try { - setLoading(true); + const userProfile = await getUserProfile(); + const userPhoto = await getUserPhoto(); - // const userProfile = await getUserProfile(); - // const userPhoto = await getUserPhoto(); + setUserPhoto(userPhoto); - // setUserPhoto(userPhoto); - - // appContext.dispatch({ - // type: 'SET_USER_PROFILE', - // payload: userProfile - // }); - const oidc = await getOidc(); - - if (oidc.isUserLoggedIn) { - console.log((await oidc.getTokens()).accessToken) - } - + appContext.dispatch({ + type: 'SET_USER_PROFILE', + payload: userProfile + }); } catch (error) { console.log(error); - } finally { - setLoading(false); - } + } }; if ( isUserLoggedIn && Object.keys(appContext.state.userProfile).length === 0 ) { - console.log(isUserLoggedIn) - console.log() setUserProfile(); - getPages(); } }, [isUserLoggedIn]); - useEffect(() => { - getPages(); - }, []) - - useEffect(() => { - console.log(pathname) - }, [pathname]) - return ( - - - - + + + + + + - {pages.length > 0 && pages.map((page) => { + {pages.length > 0 && pages.map((page, index) => { return ( - + {page.name} @@ -163,9 +103,27 @@ const SiteNav = () => { })} - + {!isUserLoggedIn && + + } + {isUserLoggedIn && + + + + + + logout({redirectTo: 'specific url', url: '/'})} startContent={}> + Sign Out + + + + } ); diff --git a/client/src/components/stateSelect/StateSelect.tsx b/client/src/components/stateSelect/StateSelect.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/logTrackMaps/LogTrackMaps.css b/client/src/components/trackMap/TrackMap.css similarity index 100% rename from client/src/components/logTrackMaps/LogTrackMaps.css rename to client/src/components/trackMap/TrackMap.css diff --git a/client/src/components/logTrackMaps/LogTrackMaps.tsx b/client/src/components/trackMap/TrackMap.tsx similarity index 57% rename from client/src/components/logTrackMaps/LogTrackMaps.tsx rename to client/src/components/trackMap/TrackMap.tsx index 8eb4b3d..6d548ed 100644 --- a/client/src/components/logTrackMaps/LogTrackMaps.tsx +++ b/client/src/components/trackMap/TrackMap.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from 'react'; -import { LogTrackMapsProps } from './LogTrackMapsProps.interface'; +import { Suspense, useEffect, useState } from 'react'; +import { TrackMapProps } from './TrackMapProps.interface'; import { AxiosInstance, AxiosResponse } from 'axios'; import { useAuth } from 'react-oidc-context' import { MapContainer, TileLayer } from 'react-leaflet'; @@ -7,16 +7,15 @@ import ReactLeafletKml from 'react-leaflet-kml'; import 'swiper/css'; import 'swiper/css/pagination'; import 'swiper/css'; -import './LogTrackMaps.css'; +import './TrackMap.css'; import 'leaflet/dist/leaflet.css'; import httpClient from '../../httpClient/httpClient' -const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { +const TrackMap = ({ height, logId, tracks }: TrackMapProps) => { const [kmls, setKmls] = useState([]) - const auth = useAuth() + const auth = useAuth(); useEffect(() => { - console.log(tracks) const getTracks = async () => { const convertedTracks: any[] = [] @@ -24,12 +23,7 @@ const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { const trackUrlSplit = track.url.split('/') const filename = trackUrlSplit[trackUrlSplit.length - 1]; const response: AxiosResponse = await httpClient.get( - `api/tracks/${logId}/${filename}`, - { - headers: { - Authorization: auth.user?.access_token - } - } + `api/tracks/${logId}/${filename}` ); const kml = new DOMParser().parseFromString(response.data, 'text/xml') @@ -40,24 +34,26 @@ const LogTrackMaps = ({ logId, tracks }: LogTrackMapsProps) => { } getTracks(); - }, [tracks]) + }, []) return ( - - {kmls.length > 0 && kmls.map((kml) => ( - - ))} + + + {kmls.length > 0 && kmls.map((kml) => ( + + ))} + ); } -export default LogTrackMaps; \ No newline at end of file +export default TrackMap; \ No newline at end of file diff --git a/client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts b/client/src/components/trackMap/TrackMapProps.interface.ts similarity index 58% rename from client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts rename to client/src/components/trackMap/TrackMapProps.interface.ts index acab4a0..a82d082 100644 --- a/client/src/components/logTrackMaps/LogTrackMapsProps.interface.ts +++ b/client/src/components/trackMap/TrackMapProps.interface.ts @@ -1,4 +1,5 @@ -export interface LogTrackMapsProps { +export interface TrackMapProps { + height: string; logId: string; tracks: {id: string; order: number; url: string}[]; } \ No newline at end of file diff --git a/client/src/components/tracksForm/TracksForm.tsx b/client/src/components/tracksForm/TracksForm.tsx index 6ae89df..f723a75 100644 --- a/client/src/components/tracksForm/TracksForm.tsx +++ b/client/src/components/tracksForm/TracksForm.tsx @@ -1,31 +1,44 @@ -import { useEffect, useReducer } from "react"; -// import { Button, Drawer, Icon, IconButton, IconName, Input, Loading } from "@noahspan/noahspan-components"; -import { AxiosError, AxiosInstance, AxiosResponse } from "axios"; +import { useEffect, useReducer, useRef } from "react"; +import { AxiosError, AxiosResponse } from "axios"; import { TracksFormProps } from "./TracksFormProps.interface"; import { FormMode } from "../../enums/formMode"; import { LogbookEntry } from "../logbook/LogbookEntry.interface"; import ConfirmationDialog from "../confirmationDialog/ConfirmationDialog"; import { initialState, reducer } from "./reducer"; -import LogTrackMaps from "../logTrackMaps/LogTrackMaps"; +import TrackMap from "../trackMap/TrackMap"; import httpClient from "../../httpClient/httpClient"; -import { Button, Drawer, DrawerContent, DrawerBody, DrawerHeader, Input, Spinner, DrawerFooter } from '@heroui/react'; +import { Button, Input, Spinner } from '@heroui/react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUpload, faTrash, faXmark } from '@fortawesome/free-solid-svg-icons'; import { useLogbookContext } from "../../hooks/logbookContext/UseLogbookContext"; -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; - const TracksForm = () => { const [state, dispatch] = useReducer(reducer, initialState) const logbookContext = useLogbookContext(); + const getTracks = async () => { + try { + const response: AxiosResponse = await httpClient.get( + `api/tracks/${logbookContext.state.selectedLogId}` + ) + + const tracks = response.data; + + dispatch({ type: 'SET_TRACKS', payload: tracks }) + } catch (error) { + const axiosError = error as AxiosError; + + logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: { severity: 'danger', message: axiosError.message }}) + } + } + const handleFileUpload = async (event: React.ChangeEvent) => { - console.log('blah') try { dispatch({ type: 'SET_IS_LOADING', payload: true}) const file = event.target.files![0] const formData = new FormData(); + const order = state.tracks.length + 1 formData.append('file', file); @@ -33,9 +46,10 @@ const TracksForm = () => { config.headers["Content-Type"] = 'multipart/form-data' return config - }) + }); - const uploadResponse: AxiosResponse = await httpClient.post(`api/tracks/${logbookContext.state.selectedLogId}/1`, formData); + await httpClient.post(`api/tracks/${logbookContext.state.selectedLogId}/${order}`, formData); + await getTracks(); // const uploadUrl = uploadResponse.data.url; // const tracks: string[] = log.tracks ? JSON.parse(log.tracks!) : []; @@ -53,49 +67,77 @@ const TracksForm = () => { } } - // const onDeleteTrack = async (index: number) => { - // dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { index: index }}}) - // } + const onDeleteTrack = async (id: string, filename: string, index: number) => { + dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { id: id, filename: filename, index: index }}}) + } + + const onConfirmDialogConfirm = async () => { + try { + await httpClient.delete(`api/tracks/${state.selectedTrack!.id}/${state.selectedTrack!.filename}/${logbookContext.state.selectedLogId}`); + + await getTracks(); + + // tracks.splice(state.selectedTrack!.index, 1); + // log.tracks = JSON.stringify(tracks); + // await httpClient.put(`api/logs/log/${selectedRowKey}`, log, config); + + // const updatedLog = await getLog(); + + // dispatch({ type: 'SET_TRACKS', payload: JSON.parse(updatedLog.tracks!) }) + dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) + } catch (error) { + console.log(error); + } + } + + const onConfirmDialogCancel = async () => { + dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) + } useEffect(() => { - const getTracks = async () => { - try { - const response: AxiosResponse = await httpClient.get( - `api/tracks/${logbookContext.state.selectedLogId}` - ) - - const tracks = response.data; - - dispatch({ type: 'SET_TRACKS', payload: tracks }) - } catch (error) { - const axiosError = error as AxiosError; - - logbookContext.dispatch({ type: 'SET_FORM_ALERT', payload: { severity: 'danger', message: axiosError.message }}) - } - } - if (logbookContext.state.selectedLogId) { getTracks(); } }, [logbookContext.state.selectedLogId]) + useEffect(() => { + console.log(logbookContext.state.formMode) + if (logbookContext.state.formMode === FormMode.VIEW) { + dispatch({ type: 'SET_IS_DISABLED', payload: true }); + } + }, [logbookContext.state.formMode]); + + useEffect(() => { + console.log(state.isDisabled) + }, [state.isDisabled]) + return (
+ {state.tracks.length > 0 && +
+ +
+ } <> - {state.tracks.length > 0 && - <> -
- -
-
- -
- - } + {state.tracks.length > 0 && state.tracks.map((track, index) => { + const filename: string = track.url.substring(track.url.lastIndexOf('/') + 1); + + return ( + <> +
+ +
+
+ +
+ + ) + })}
+ {state.isConfirmDialogOpen && ( + + )}
); @@ -136,31 +188,7 @@ const TracksForm = () => { // dispatch({ type: 'SET_ON_DELETE_TRACK', payload: { isConfirmDialogOpen: true, selectedTrack: { fileName: fileName, index: index }}}) // } - // const onConfirmDialogConfirm = async () => { - // try { - // const config = await getConfig(); - // await httpClient.delete(`api/logs/log/${selectedRowKey}/track?fileName=${state.selectedTrack!.fileName}`, config); - - // const log = await getLog(); - // const tracks: string[] = JSON.parse(log.tracks!); - - // tracks.splice(state.selectedTrack!.index, 1); - // log.tracks = JSON.stringify(tracks); - // await httpClient.put(`api/logs/log/${selectedRowKey}`, log, config); - - // const updatedLog = await getLog(); - - // dispatch({ type: 'SET_TRACKS', payload: JSON.parse(updatedLog.tracks!) }) - // dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) - // } catch (error) { - // console.log(error); - // } - // } - - // const onConfirmDialogCancel = async () => { - // dispatch({ type: 'SET_IS_CONFIRM_DIALOG_OPEN', payload: false }) - // } // useEffect(() => { // const updateTracks = async () => { @@ -204,19 +232,8 @@ const TracksForm = () => { // })} // // } - {/* {logbookContext.state.formMode === FormMode.VIEW && - - } */} - {/* {state.isConfirmDialogOpen && ( - - )} */} + + //
// ) // } diff --git a/client/src/components/tracksForm/TracksFormState.interface.ts b/client/src/components/tracksForm/TracksFormState.interface.ts index 099237e..f1b86f2 100644 --- a/client/src/components/tracksForm/TracksFormState.interface.ts +++ b/client/src/components/tracksForm/TracksFormState.interface.ts @@ -1,9 +1,11 @@ export interface TracksFormState { isConfirmDialogOpen: boolean; isConfirmDialogLoading: boolean; + isDisabled: boolean; isLoading: boolean; selectedTrack: { - fileName: string, + id: string, + filename: string, index: number } | undefined; tracks: { id: string; order: number; url: string; }[]; diff --git a/client/src/components/tracksForm/reducer.ts b/client/src/components/tracksForm/reducer.ts index 7f754e5..83cfe03 100644 --- a/client/src/components/tracksForm/reducer.ts +++ b/client/src/components/tracksForm/reducer.ts @@ -3,13 +3,15 @@ import { TracksFormState } from "./TracksFormState.interface"; type Action = | { type: 'SET_IS_CONFIRM_DIALOG_OPEN'; payload: boolean } | { type: 'SET_IS_CONFORM_DIALOG_LOADING'; payload: boolean } + | { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean } - | { type: 'SET_ON_DELETE_TRACK'; payload: { isConfirmDialogOpen: boolean, selectedTrack: { fileName: string, index: number } }} + | { type: 'SET_ON_DELETE_TRACK'; payload: { isConfirmDialogOpen: boolean, selectedTrack: { id: string, filename: string, index: number } }} | { type: 'SET_TRACKS'; payload: { id: string; order: number; url: string }[] }; export const initialState: TracksFormState = { isConfirmDialogOpen: false, isConfirmDialogLoading: false, + isDisabled: false, isLoading: false, selectedTrack: undefined, tracks: [] @@ -29,6 +31,12 @@ export const reducer = (state: TracksFormState, action: Action): TracksFormState isConfirmDialogLoading: action.payload } } + case 'SET_IS_DISABLED': { + return { + ...state, + isDisabled: action.payload + } + } case 'SET_IS_LOADING': { return { ...state, diff --git a/client/src/hooks/featureFlag/UseFeatureFlag.tsx b/client/src/hooks/featureFlag/UseFeatureFlag.tsx deleted file mode 100644 index 002f929..0000000 --- a/client/src/hooks/featureFlag/UseFeatureFlag.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useAppContext } from '../appContext/UseAppContext'; - -export const useFeatureFlag = (featureFlagKey: string) => { - const appContext = useAppContext(); - const featureFlag = appContext.state.featureFlags.find( - (featureFlag) => featureFlag.key === featureFlagKey - ); - console.log(featureFlag) - return featureFlag; -}; diff --git a/client/src/hooks/pilots/UsePilots.tsx b/client/src/hooks/pilots/UsePilots.tsx index 2f7b5e8..83dc340 100644 --- a/client/src/hooks/pilots/UsePilots.tsx +++ b/client/src/hooks/pilots/UsePilots.tsx @@ -23,7 +23,7 @@ export const usePilots = () => { const response: AxiosResponse = await httpClient.get( `/api/pilots` ); - console.log(response) + setPilots(response.data); } catch (error) { return error; diff --git a/client/src/main.tsx b/client/src/main.tsx index 126fde5..4777a71 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -7,15 +7,13 @@ import { BrowserRouter } from 'react-router-dom'; import { OidcProvider } from './auth/oidcConfig.ts'; ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - - + + + + + + + + + ); diff --git a/client/src/styles.css b/client/src/styles.css index f25ab6f..ce32b3c 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -5,6 +5,8 @@ @custom-variant dark (&:is(.dark *)); @plugin "@tailwindcss/typography"; -/* body { - @apply bg-base-300; -} */ +@theme inline { + --color-primary: #000000; +} + + diff --git a/database/flying.db b/database/flying.db index 00488c8..dd824fd 100644 Binary files a/database/flying.db and b/database/flying.db differ diff --git a/infrastructure/config/app/main.tf b/infrastructure/config/app/main.tf index 4ad08da..0c04359 100644 --- a/infrastructure/config/app/main.tf +++ b/infrastructure/config/app/main.tf @@ -1,7 +1,12 @@ locals { app_name = { - test = "flying-app-test" - prod = "flying-app-prod" + test = "flying-test" + prod = "flying-prod" + } + + container_app_environment_name = { + test = "noahspan-test" + prod = "noahspan-prod" } container_image = { diff --git a/infrastructure/config/app/outputs.tf b/infrastructure/config/app/outputs.tf index 9578578..34329e1 100644 --- a/infrastructure/config/app/outputs.tf +++ b/infrastructure/config/app/outputs.tf @@ -2,6 +2,10 @@ output "app_name" { value = local.app_name[var.environment] } +output "container_app_environment_name" { + value = local.container_app_environment_name[var.environment] +} + output "container_image" { value = local.container_image[var.environment] } diff --git a/infrastructure/config/container_app_environment/main.tf b/infrastructure/config/container_app_environment/main.tf deleted file mode 100644 index e1667ee..0000000 --- a/infrastructure/config/container_app_environment/main.tf +++ /dev/null @@ -1,11 +0,0 @@ -locals { - name = { - test = "flying-test" - prod = "flying-prod" - } - - log_analytics_workspace_name = { - test = "flying-log-analytics-workspace-test" - prod = "flying-log-analytics-workspace-prod" - } -} \ No newline at end of file diff --git a/infrastructure/config/container_app_environment/outputs.tf b/infrastructure/config/container_app_environment/outputs.tf deleted file mode 100644 index 4b9bda4..0000000 --- a/infrastructure/config/container_app_environment/outputs.tf +++ /dev/null @@ -1,7 +0,0 @@ -output "name" { - value = local.name[var.environment] -} - -output "log_analytics_workspace_name" { - value = local.log_analytics_workspace_name[var.environment] -} \ No newline at end of file diff --git a/infrastructure/config/container_app_environment/variables.tf b/infrastructure/config/container_app_environment/variables.tf deleted file mode 100644 index 9b5503e..0000000 --- a/infrastructure/config/container_app_environment/variables.tf +++ /dev/null @@ -1 +0,0 @@ -variable "environment" {} \ No newline at end of file diff --git a/infrastructure/config/main.tf b/infrastructure/config/main.tf index c231f5c..9361aa1 100644 --- a/infrastructure/config/main.tf +++ b/infrastructure/config/main.tf @@ -1,18 +1,8 @@ -module "api" { - source = "./api" - environment = var.environment -} - module "app" { source = "./app" environment = var.environment } -module "container_app_environment" { - source = "./container_app_environment" - environment = var.environment -} - module "storage" { source = "./storage" environment = var.environment diff --git a/infrastructure/config/outputs.tf b/infrastructure/config/outputs.tf index f8d13c1..bb7d635 100644 --- a/infrastructure/config/outputs.tf +++ b/infrastructure/config/outputs.tf @@ -1,15 +1,7 @@ -output "api" { - value = module.api -} - output "app" { value = module.app } -output "container_app_environment" { - value = module.container_app_environment -} - output "storage" { value = module.storage } \ No newline at end of file diff --git a/infrastructure/container_app.tf b/infrastructure/container_app.tf new file mode 100644 index 0000000..7842a77 --- /dev/null +++ b/infrastructure/container_app.tf @@ -0,0 +1,165 @@ +resource "azurerm_container_app" "container_app" { + name = module.environment.app.app_name + container_app_environment_id = data.azurerm_container_app_environment.container_app_environment.id + resource_group_name = data.azurerm_resource_group.resource_group.name + revision_mode = "Single" + + template { + min_replicas = module.environment.app.template_min_replicas + max_replicas = module.environment.app.template_max_replicas + + init_container { + args = ["restore", "-if-db-not-exists", "-if-replica-exists", "/var/lib/data/flying.db"] + cpu = 0.25 + image = "litestream/litestream:0.5.2" + memory = "0.5Gi" + name = "restore" + + volume_mounts { + name = "data" + path = "/var/lib/data" + } + + volume_mounts { + name = "backup" + path = "/mnt/data" + sub_path = "data" + } + + volume_mounts { + name = "backup" + path = "/etc" + sub_path = "litestream" + } + } + + container { + args = ["replicate"] + cpu = 0.25 + image = "litestream/litestream:0.5.2" + memory = "0.5Gi" + name = "replicate" + + volume_mounts { + name = "data" + path = "/var/lib/data" + } + + volume_mounts { + name = "backup" + path = "/mnt/data" + sub_path = "data" + } + + volume_mounts { + name = "backup" + path = "/etc" + sub_path = "litestream" + } + } + + container { + cpu = 0.25 + image = module.environment.app.container_image + memory = "0.5Gi" + name = module.environment.app.container_name + + env { + name = "AZURE_STORAGE_CONNECTION_STRING" + secret_name = "azure-storage-connection-string" + } + + env { + name = "CLIENT_ID" + secret_name = "client-id" + } + + env { + name = "CLIENT_SECRET" + secret_name = "client-secret" + } + + env { + name = "TENANT_ID" + value = var.TENANT_ID + } + + env { + name = "DB_PATH" + value = "/var/lib/data/flying.db" + } + + env { + name = "DB_SYNC" + value = "false" + } + + startup_probe { + failure_count_threshold = 10 + initial_delay = 1 + interval_seconds = 2 + path = "/api/health" + port = 3000 + transport = "HTTP" + } + + volume_mounts { + name = "data" + path = "/var/lib/data" + } + } + + volume { + name = "backup" + storage_name = azurerm_container_app_environment_storage.container_app_environment_storage_backup.name + storage_type = "AzureFile" + } + + volume { + name = "data" + storage_type = "EmptyDir" + } + } + + ingress { + allow_insecure_connections = false + external_enabled = module.environment.app.ingress_external_enabled + target_port = module.environment.app.ingress_target_port + transport = module.environment.app.ingress_transport + + traffic_weight { + latest_revision = true + percentage = module.environment.app.traffic_weight_percentage + } + } + + registry { + server = "docker.io" + username = var.DOCKER_IO_USERNAME + password_secret_name = "docker-io-password" + } + + secret { + name = "azure-storage-connection-string" + value = azurerm_storage_account.storage_account.primary_connection_string + } + + secret { + name = "client-id" + value = var.CLIENT_ID + } + + secret { + name = "client-secret" + value = var.CLIENT_SECRET + } + + secret { + name = "docker-io-password" + value = var.DOCKER_IO_PASSWORD + } + + lifecycle { + ignore_changes = [ template[0].container[0].image, template[0].container[0].image, template[0].init_container[0].image, registry[0].server ] + } +} \ No newline at end of file diff --git a/infrastructure/container_app_environment_storage.tf b/infrastructure/container_app_environment_storage.tf new file mode 100644 index 0000000..e23187b --- /dev/null +++ b/infrastructure/container_app_environment_storage.tf @@ -0,0 +1,8 @@ +resource "azurerm_container_app_environment_storage" "container_app_environment_storage_backup" { + name = "${module.environment.app.app_name}-backup" + container_app_environment_id = data.azurerm_container_app_environment.container_app_environment.id + account_name = azurerm_storage_account.storage_account.name + share_name = azurerm_storage_share.backup_storage_share.name + access_key = azurerm_storage_account.storage_account.primary_access_key + access_mode = "ReadOnly" +} \ No newline at end of file diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 7a1e8aa..8a0ff77 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -1,216 +1,10 @@ -module "storage" { - source = "github.com/noahspannbauer/noahspan-terraform/modules/storage" - resource_group_name = var.RESOURCE_GROUP_NAME - storage_account_name = module.environment.storage.account_name - storage_containers = module.environment.storage.containers - storage_shares = module.environment.storage.shares - storage_tables = module.environment.storage.tables +data "azurerm_client_config" "current" {} + +data "azurerm_resource_group" "resource_group" { + name = var.RESOURCE_GROUP_NAME } -module "container_app_environment" { - source = "github.com/noahspannbauer/noahspan-terraform/modules/container_app_environment" - container_app_environment_name = module.environment.container_app_environment.name - log_analytics_workspace_name = module.environment.container_app_environment.log_analytics_workspace_name - log_analytics_workspace_retention_in_days = 30 - log_analytics_workspace_sku = "PerGB2018" +data "azurerm_container_app_environment" "container_app_environment" { + name = module.environment.app.container_app_environment_name resource_group_name = var.RESOURCE_GROUP_NAME } - -module "container_app_environment_storage" { - source = "github.com/noahspannbauer/noahspan-terraform/modules/container_app_environment_storage" - name = "${module.container_app_environment.name}-files" - container_app_environment_id = module.container_app_environment.id - account_name = module.storage.name - share_name = module.environment.storage.shares[0] - access_key = module.storage.primary_access_key - access_mode = "ReadWrite" -} - -module "api" { - source = "github.com/noahspannbauer/noahspan-terraform/modules/container_app" - containers = [ - { - cpu = 0.25 - envs = [ - { - name = "AZURE_STORAGE_CONNECTION_STRING" - secret_name = "azure-storage-connection-string" - }, - { - name = "CLIENT_ID" - secret_name = "client-id" - }, - { - name = "CLIENT_SECRET" - secret_name = "client-secret" - }, - { - name = "TENANT_ID" - secret_name = "tenant-id" - }, - { - name = "DB_PATH" - secret_name = "db-path" - }, - { - name = "DB_SYNC" - secret_name = "db-sync" - } - ] - image = module.environment.api.container_image - memory = "0.5Gi" - name = module.environment.api.container_name - startup_probe = [ - { - failure_count_threshold = 10 - initial_delay = 1 - interval_seconds = 2 - path = "/api/health" - port = 3000 - transport = "HTTP" - } - ] - volume_mounts = [ - { - name = "${module.container_app_environment.name}-emptydir" - path = "/var/lib/data" - } - ] - }, - { - args = ["replicate"] - cpu = 0.25 - image = "docker.io/litestream/litestream:0.3.13" - memory = "0.5Gi" - name = "replicate" - volume_mounts = [ - { - name = "${module.container_app_environment.name}-emptydir" - path = "/var/lib/data" - }, - { - name = "${module.container_app_environment.name}-fileshare" - path = "/mnt/data" - sub_path = "data" - }, - { - name = "${module.container_app_environment.name}-fileshare" - path = "/etc" - sub_path = "litestream" - } - ] - } - ] - container_app_name = module.environment.api.app_name - container_app_environment_id = module.container_app_environment.id - custom_domain_count = module.environment.api.custom_domain_count - init_containers = [ - { - args = ["restore", "-if-db-not-exists", "-if-replica-exists", "/var/lib/data/flying.db"] - cpu = 0.25 - image = "docker.io/litestream/litestream:0.3.13" - memory = "0.5Gi" - name = "restore" - volume_mounts = [ - { - name = "${module.container_app_environment.name}-emptydir" - path = "/var/lib/data" - }, - { - name = "${module.container_app_environment.name}-fileshare" - path = "/mnt/data" - sub_path = "data" - }, - { - name = "${module.container_app_environment.name}-fileshare" - path = "/etc" - sub_path = "litestream" - } - ] - } - ] - ingress_external_enabled = module.environment.api.ingress_external_enabled - ingress_target_port = module.environment.api.ingress_target_port - ingress_transport = module.environment.api.ingress_transport - registry_password = var.DOCKER_IO_PASSWORD - registry_username = var.DOCKER_IO_USERNAME - registry_password_secret_name = "docker-io-password" - registry_server_name = "docker.io" - resource_group_name = var.RESOURCE_GROUP_NAME - secrets = [ - { - name = "azure-storage-connection-string" - value = module.storage.primary_connection_string - }, - { - name = "docker-io-password" - value = var.DOCKER_IO_PASSWORD - }, - { - name = "client-id" - value = var.CLIENT_ID - }, - { - name = "client-secret" - value = var.CLIENT_SECRET - }, - { - name = "tenant-id" - value = var.TENANT_ID - }, - { - name = "db-path" - value = var.DB_PATH - }, - { - name = "db-sync" - value = var.DB_SYNC - } - ] - storage_account_primary_connection_string = module.storage.primary_connection_string - traffic_weight_percentage = module.environment.api.traffic_weight_percentage - template_min_replicas = module.environment.api.template_min_replicas - template_max_replicas = module.environment.api.template_max_replicas - volume = [ - { - name = "${module.container_app_environment.name}-fileshare" - storage_name = module.container_app_environment_storage.name - storage_type = "AzureFile" - }, - { - name = "${module.container_app_environment.name}-emptydir" - storage_type = "EmptyDir" - } - ] -} - -module "app" { - source = "github.com/noahspannbauer/noahspan-terraform/modules/container_app" - containers = [ - { - cpu = 0.25 - image = module.environment.app.container_image - memory = "0.5Gi" - name = module.environment.app.container_name - } - ] - container_app_name = module.environment.app.app_name - container_app_environment_id = module.container_app_environment.id - custom_domain_count = module.environment.app.custom_domain_count - ingress_external_enabled = module.environment.app.ingress_external_enabled - ingress_target_port = module.environment.app.ingress_target_port - registry_password = var.DOCKER_IO_PASSWORD - registry_username = var.DOCKER_IO_USERNAME - registry_password_secret_name = "docker-io-password" - registry_server_name = "docker.io" - resource_group_name = var.RESOURCE_GROUP_NAME - secrets = [ - { - name = "docker-io-password" - value = var.DOCKER_IO_PASSWORD - } - ] - traffic_weight_percentage = module.environment.app.traffic_weight_percentage - template_max_replicas = module.environment.app.template_max_replicas - template_min_replicas = module.environment.app.template_min_replicas -} \ No newline at end of file diff --git a/infrastructure/storage.tf b/infrastructure/storage.tf new file mode 100644 index 0000000..7b4163f --- /dev/null +++ b/infrastructure/storage.tf @@ -0,0 +1,19 @@ +resource "azurerm_storage_account" "storage_account" { + name = module.environment.storage.account_name + resource_group_name = data.azurerm_resource_group.resource_group.name + location = data.azurerm_resource_group.resource_group.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "storage_container" { + name = "tracks" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" +} + +resource "azurerm_storage_share" "backup_storage_share" { + name = "${module.environment.app.app_name}-backup-storage-share" + quota = 50 + storage_account_name = azurerm_storage_account.storage_account.name +} \ No newline at end of file diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index 3877574..7f4786a 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -1,10 +1,3 @@ -variable "API_SUBDOMAIN_NAME" { - type = string -} - -variable "APP_SUBDOMAIN_NAME" { - type = string -} variable "CLIENT_ID" { type = string @@ -15,18 +8,6 @@ variable "CLIENT_SECRET" { sensitive = true } -variable "DB_PATH" { - type = string -} - -variable "DB_SYNC" { - type = string -} - -variable "DNS_ZONE_RESOURCE_GROUP" { - type = string -} - variable "DOCKER_IO_PASSWORD" { type = string sensitive = true @@ -36,10 +17,6 @@ variable "DOCKER_IO_USERNAME" { type = string } -variable "DOMAIN_NAME" { - type = string -} - variable "RESOURCE_GROUP_NAME" { type = string } diff --git a/package-lock.json b/package-lock.json index 0998ce8..93eb6f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "@nestjs/serve-static": "^5.0.3", "@nestjs/typeorm": "^11.0.0", "@noahspan/azure-database": "^3.1.2", - "@noahspan/noahspan-modules": "^1.2.8", + "@noahspan/noahspan-modules": "^1.2.9", "@schematics/angular": "^17.3.7", "@types/multer": "^1.4.12", "better-sqlite3": "^12.2.0", @@ -5148,6 +5148,25 @@ "@types/node": "*" } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/passport": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", @@ -5752,9 +5771,9 @@ } }, "node_modules/@noahspan/noahspan-modules": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@noahspan/noahspan-modules/-/noahspan-modules-1.2.8.tgz", - "integrity": "sha512-Zx4wtMAiRj2D132ng4DhlKt/O4kXj1NzCjxwVAXL1tinSsX9xHxjecY/GmYs9j/iMdFwAb5SpnyN5YeLX/THFw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@noahspan/noahspan-modules/-/noahspan-modules-1.2.9.tgz", + "integrity": "sha512-YUkFpI7UvvCUA3cAkSsWJZS3telrlVraCgXY1TrSoimPcT48fO4HKNqKboSJAeVq03rK6mV+BGimF9ZXZjrJzg==", "dependencies": { "@azure/identity": "^4.2.0", "@azure/msal-node": "^2.9.2", @@ -5762,6 +5781,7 @@ "@nestjs/common": "^11.0.11", "@nestjs/core": "^11.0.11", "@nestjs/jwt": "^11.0.0", + "@nestjs/mapped-types": "*", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.11", "axios": "^1.7.2",