Refactoring
This commit is contained in:
52
.github/workflows/app.yaml
vendored
52
.github/workflows/app.yaml
vendored
@@ -5,19 +5,14 @@ on:
|
|||||||
calling_workflow:
|
calling_workflow:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
deployment_environment:
|
version_number:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: ${{ inputs.calling_workflow }}
|
environment: ${{ inputs.calling_workflow }}
|
||||||
env:
|
|
||||||
VITE_API_URL: ${{ secrets.VITE_API_URL }}
|
|
||||||
VITE_CLIENT_ID: ${{ secrets.VITE_CLIENT_ID }}
|
|
||||||
VITE_TENANT_ID: ${{ secrets.VITE_TENANT_ID }}
|
|
||||||
VITE_REDIRECT_URL: ${{ secrets.VITE_REDIRECT_URL }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -36,13 +31,38 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm run build -w app
|
npm run build -w app
|
||||||
|
|
||||||
- name: Deploy
|
- name: Setup Docker Buildx
|
||||||
id: builddeploy
|
uses: docker/setup-buildx-action@v3
|
||||||
uses: Azure/static-web-apps-deploy@v1
|
|
||||||
|
- name: Log into Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
action: 'upload'
|
|
||||||
skip_app_build: true
|
- name: Build and push to registry
|
||||||
app_location: ${{ vars.APP_LOCATION }}
|
uses: docker/build-push-action@v6
|
||||||
deployment_environment: ${{ inputs.deployment_environment }}
|
with:
|
||||||
|
push: true
|
||||||
|
tags: noahspan/flying-app:${{ inputs.version_number }}
|
||||||
|
file: ./app/Dockerfile
|
||||||
|
context: ./app
|
||||||
|
build-args: |
|
||||||
|
VITE_API_URL=${{ secrets.VITE_API_URL }}
|
||||||
|
VITE_CLIENT_ID=${{ secrets.VITE_CLIENT_ID }}
|
||||||
|
VITE_TENANT_ID=${{ secrets.VITE_TENANT_ID }}
|
||||||
|
VITE_REDIRECT_URL=${{ secrets.VITE_REDIRECT_URL }}
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: ${{ inputs.calling_workflow }}
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Log in to Azure
|
||||||
|
uses: azure/login@v2
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||||
|
|
||||||
|
- name: Update Container App
|
||||||
|
run: |
|
||||||
|
az containerapp update --name flying-app-${{ inputs.calling_workflow }} --revision-suffix ${{ inputs.version_number }} --resource-group noahspan --image docker.io/noahspan/flying-app:${{ inputs.version_number }}
|
||||||
|
|||||||
2
.github/workflows/dev.yaml
vendored
2
.github/workflows/dev.yaml
vendored
@@ -48,5 +48,5 @@ jobs:
|
|||||||
uses: ./.github/workflows/app.yaml
|
uses: ./.github/workflows/app.yaml
|
||||||
with:
|
with:
|
||||||
calling_workflow: dev
|
calling_workflow: dev
|
||||||
deployment_environment: development
|
version_number: ${{ github.run_id }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
43
.github/workflows/pull_request.yaml
vendored
43
.github/workflows/pull_request.yaml
vendored
@@ -7,31 +7,20 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# build-deploy-api:
|
build-deploy-api:
|
||||||
# if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
||||||
# name: build-deploy-api
|
name: build-deploy-api
|
||||||
# uses: ./.github/workflows/api.yaml
|
uses: ./.github/workflows/api.yaml
|
||||||
# with:
|
|
||||||
# calling_workflow: dev
|
|
||||||
# secrets: inherit
|
|
||||||
|
|
||||||
# build-deploy-app:
|
|
||||||
# if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
|
||||||
# name: build-deploy-app
|
|
||||||
# uses: ./.github/workflows/app.yaml
|
|
||||||
# with:
|
|
||||||
# calling_workflow: dev
|
|
||||||
# deployment_environment: staging
|
|
||||||
# secrets: inherit
|
|
||||||
|
|
||||||
close-pull-request-job:
|
|
||||||
if: github.event_name == 'pull_request' && github.event.action == 'closed'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Close Pull Request Job
|
|
||||||
steps:
|
|
||||||
- name: Close Pull Request
|
|
||||||
id: closepullrequest
|
|
||||||
uses: Azure/static-web-apps-deploy@v1
|
|
||||||
with:
|
with:
|
||||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
calling_workflow: pull_request
|
||||||
action: 'close'
|
version_number: ${{ github.run_id }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build-deploy-app:
|
||||||
|
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
||||||
|
name: build-deploy-app
|
||||||
|
uses: ./.github/workflows/app.yaml
|
||||||
|
with:
|
||||||
|
calling_workflow: pull_request
|
||||||
|
version_number: ${{ github.run_id }}
|
||||||
|
secrets: inherit
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ node_modules
|
|||||||
**/.env
|
**/.env
|
||||||
**/.env.*
|
**/.env.*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.dapr
|
||||||
|
**/**/secrets.json
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
*.js.map
|
|
||||||
*.ts
|
|
||||||
.git*
|
|
||||||
.vscode
|
|
||||||
local.settings.json
|
|
||||||
test
|
|
||||||
tsconfig.json
|
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
# To enable ssh & remote debugging on app service change the base image to the one below
|
FROM --platform=linux/amd64 node:18-alpine
|
||||||
# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice
|
|
||||||
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
|
||||||
|
|
||||||
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
|
COPY ./node_modules ./node_modules
|
||||||
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
|
COPY ./dist ./dist
|
||||||
|
|
||||||
COPY . /home/site/wwwroot
|
EXPOSE 3000
|
||||||
|
|
||||||
RUN cd /home/site/wwwroot && \
|
CMD ["node", "dist/main.js"]
|
||||||
npm install
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"bindings": [
|
|
||||||
{
|
|
||||||
"authLevel": "anonymous",
|
|
||||||
"type": "httpTrigger",
|
|
||||||
"direction": "in",
|
|
||||||
"name": "req",
|
|
||||||
"route": "{*segments}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "http",
|
|
||||||
"direction": "out",
|
|
||||||
"name": "res"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"scriptFile": "../dist/main/index.js"
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Context, HttpRequest } from '@azure/functions';
|
|
||||||
import { AzureHttpAdapter } from '@nestjs/azure-func-http';
|
|
||||||
import { createApp } from '../src/main.azure';
|
|
||||||
|
|
||||||
export default function(context: Context, req: HttpRequest): void {
|
|
||||||
AzureHttpAdapter.handle(createApp, context, req);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Azure"
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "api",
|
"name": "@noahspan/flying-api",
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -16,23 +16,25 @@
|
|||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"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"
|
||||||
"start:azure": "npm run build && func host start"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/app-configuration": "^1.8.0",
|
||||||
"@azure/data-tables": "^13.2.2",
|
"@azure/data-tables": "^13.2.2",
|
||||||
"@azure/functions": "^1.0.3",
|
"@azure/msal-node": "^2.16.2",
|
||||||
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"@nestjs/axios": "^3.0.3",
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/azure-database": "^3.0.0",
|
"@nestjs/azure-database": "^3.0.0",
|
||||||
"@nestjs/azure-func-http": "^0.10.0",
|
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.2",
|
"@nestjs/config": "^3.2.2",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@noahspan/noahspan-modules": "^0.4.7",
|
"@noahspan/azure-database": "^3.1.0",
|
||||||
|
"@noahspan/noahspan-modules": "^0.5.2",
|
||||||
"@schematics/angular": "^17.3.7",
|
"@schematics/angular": "^17.3.7",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
|
"passport-azure-ad": "^4.3.5",
|
||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
|
|||||||
@@ -6,47 +6,62 @@ import {
|
|||||||
Res,
|
Res,
|
||||||
StreamableFile
|
StreamableFile
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||||
AppConfigService,
|
import { MsGraphService, Public } from '@noahspan/noahspan-modules';
|
||||||
MsGraphService,
|
|
||||||
MsGraphClient
|
|
||||||
} from '@noahspan/noahspan-modules';
|
|
||||||
import { FeatureFlagValue } from '@azure/app-configuration';
|
import { FeatureFlagValue } from '@azure/app-configuration';
|
||||||
import { Public } from '@noahspan/noahspan-modules';
|
|
||||||
import { Person } from '@microsoft/microsoft-graph-types';
|
import { Person } from '@microsoft/microsoft-graph-types';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { arrayBuffer } from 'stream/consumers';
|
import { arrayBuffer } from 'stream/consumers';
|
||||||
import type { Response } from 'express';
|
import type { Response } from 'express';
|
||||||
|
import { DaprService } from '@noahspan/noahspan-modules';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly appService: AppService,
|
private readonly appService: AppService,
|
||||||
private readonly appConfigService: AppConfigService,
|
private readonly daprService: DaprService,
|
||||||
private readonly msGraphService: MsGraphService
|
private readonly msGraphService: MsGraphService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
// @Public()
|
||||||
|
// @Get('featureFlags')
|
||||||
|
// async getFeatureFlags(
|
||||||
|
// @Query() query: any
|
||||||
|
// ): Promise<{ key: string; enabled: boolean }[]> {
|
||||||
|
// try {
|
||||||
|
// const featureFlagKeys: string[] =
|
||||||
|
// query.keys && query.keys.toString().includes(';')
|
||||||
|
// ? query.keys.split(';')
|
||||||
|
// : [query.keys];
|
||||||
|
// const featureFlagLabel: string = query.label;
|
||||||
|
// const featureFlags: { key: string; enabled: boolean }[] =
|
||||||
|
// await this.appConfigService.getFeatureFlags(
|
||||||
|
// featureFlagKeys,
|
||||||
|
// featureFlagLabel
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return featureFlags;
|
||||||
|
// } catch (error) {
|
||||||
|
// return error;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@Get('featureFlags')
|
@Get('featureFlags')
|
||||||
async getFeatureFlags(
|
async getFeatureFlags(@Query() query: any): Promise<void> {
|
||||||
@Query() query: any
|
|
||||||
): Promise<{ key: string; enabled: boolean }[]> {
|
|
||||||
try {
|
try {
|
||||||
const featureFlagKeys: string[] =
|
// const config = await this.daprService.daprClient.configuration.get('app-config', ['flying-logbook'], {
|
||||||
query.keys && query.keys.toString().includes(';')
|
// 'label': 'dev'
|
||||||
? query.keys.split(';')
|
// })
|
||||||
: [query.keys];
|
console.log('====== BLAH ======');
|
||||||
const featureFlagLabel: string = query.label;
|
const config =
|
||||||
const featureFlags: { key: string; enabled: boolean }[] =
|
await this.daprService.daprClient.configuration.get('app-config');
|
||||||
await this.appConfigService.getFeatureFlags(
|
|
||||||
featureFlagKeys,
|
|
||||||
featureFlagLabel
|
|
||||||
);
|
|
||||||
|
|
||||||
return featureFlags;
|
console.log('====== Config ======: ' + config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(`======= ERROR ======: ${error}`);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,85 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import {
|
import { AuthGuard, AuthModule } from '@noahspan/noahspan-modules';
|
||||||
AppConfigModule,
|
import { MsGraphModule } from '@noahspan/noahspan-modules';
|
||||||
AuthModule,
|
|
||||||
AuthGuard,
|
|
||||||
MsGraphModule
|
|
||||||
} from '@noahspan/noahspan-modules';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { LogbookModule } from './logbook/logbook.module';
|
import { LogModule } from './log/log.module';
|
||||||
import { PilotModule } from './pilot/pilot.module';
|
import { PilotModule } from './pilot/pilot.module';
|
||||||
import { APP_FILTER } from '@nestjs/core';
|
import { APP_FILTER } from '@nestjs/core';
|
||||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||||
|
import { DaprModule, DaprService } from '@noahspan/noahspan-modules';
|
||||||
|
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||||
|
|
||||||
|
const secretStoreName = 'key-vault';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
AuthModule.registerAsync({
|
||||||
AppConfigModule.register({
|
imports: [DaprModule],
|
||||||
url: process.env.APP_CONFIG_URL,
|
useFactory: async (daprService: DaprService) => {
|
||||||
tenantId: process.env.TENANT_ID,
|
const clientIdSecret = await daprService.daprClient.secret.get(
|
||||||
clientId: process.env.CLIENT_ID,
|
secretStoreName,
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
'client-id'
|
||||||
|
);
|
||||||
|
const clientSecret = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'client-secret'
|
||||||
|
);
|
||||||
|
const tenantIdSecret = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'tenant-id'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId: clientIdSecret['client-id'].toString(),
|
||||||
|
clientSecret: clientSecret['client-secret'].toString(),
|
||||||
|
tenantId: tenantIdSecret['tenant-id'].toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [DaprService]
|
||||||
}),
|
}),
|
||||||
AuthModule.register({
|
AzureTableStorageModule.forRootAsync({
|
||||||
tenantId: process.env.TENANT_ID,
|
imports: [DaprModule],
|
||||||
clientId: process.env.CLIENT_ID,
|
useFactory: async (daprService: DaprService) => {
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
const connectionString = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'azure-storage-connection-string'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionString:
|
||||||
|
connectionString['azure-storage-connection-string'].toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [DaprService]
|
||||||
}),
|
}),
|
||||||
MsGraphModule.register({
|
DaprModule,
|
||||||
tenantId: process.env.TENANT_ID,
|
LogModule,
|
||||||
clientId: process.env.CLIENT_ID,
|
MsGraphModule.registerAsync({
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
imports: [DaprModule],
|
||||||
|
useFactory: async (daprService: DaprService) => {
|
||||||
|
const clientIdSecret = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'client-id'
|
||||||
|
);
|
||||||
|
const clientSecret = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'client-secret'
|
||||||
|
);
|
||||||
|
const tenantIdSecret = await daprService.daprClient.secret.get(
|
||||||
|
secretStoreName,
|
||||||
|
'tenant-id'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
clientId: clientIdSecret['client-id'].toString(),
|
||||||
|
clientSecret: clientSecret['client-secret'].toString(),
|
||||||
|
tenantId: tenantIdSecret['tenant-id'].toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [DaprService]
|
||||||
}),
|
}),
|
||||||
LogbookModule,
|
|
||||||
PilotModule
|
PilotModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
// import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
||||||
|
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||||
|
import { MsGraphService } from '@noahspan/noahspan-modules';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
export class CustomError extends Error {
|
|
||||||
statusCode: number;
|
|
||||||
|
|
||||||
constructor(message, name, statusCode) {
|
|
||||||
super(message);
|
|
||||||
this.name = name;
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
92
api/src/log/log.controller.ts
Normal file
92
api/src/log/log.controller.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpException,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Put
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { LogDto } from './log.dto';
|
||||||
|
import { Log } from './log.entity';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
import { CustomError } from '@noahspan/noahspan-modules';
|
||||||
|
|
||||||
|
@Controller('logs')
|
||||||
|
export class LogController {
|
||||||
|
constructor(private readonly logService: LogService) {}
|
||||||
|
|
||||||
|
@Get(':partitionKey/:rowKey')
|
||||||
|
async find(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string
|
||||||
|
): Promise<Log> {
|
||||||
|
try {
|
||||||
|
return await this.logService.find(partitionKey, rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAll(): Promise<Log[]> {
|
||||||
|
try {
|
||||||
|
return await this.logService.findAll();
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(@Body() logDto: LogDto): Promise<Log> {
|
||||||
|
try {
|
||||||
|
const log = new Log();
|
||||||
|
|
||||||
|
Object.assign(log, logDto);
|
||||||
|
console.log(log);
|
||||||
|
return await this.logService.create(log);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':partitionKey/:rowKey')
|
||||||
|
async update(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string,
|
||||||
|
@Body() logDto: LogDto
|
||||||
|
): Promise<Log> {
|
||||||
|
try {
|
||||||
|
const log = new Log();
|
||||||
|
|
||||||
|
Object.assign(log, logDto);
|
||||||
|
|
||||||
|
return await this.logService.update(partitionKey, rowKey, log);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':partitionKey/:rowKey')
|
||||||
|
async delete(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
return await this.logService.delete(partitionKey, rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export class LogbookDto {
|
export class LogDto {
|
||||||
pilotId: string;
|
pilotId: string;
|
||||||
pilotName: string;
|
pilotName: string;
|
||||||
date: string;
|
date: string;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export class LogbookEntity {
|
export class Log {
|
||||||
partitionKey: string;
|
partitionKey: string;
|
||||||
rowKey: string;
|
rowKey: string;
|
||||||
pilotId: string;
|
pilotId: string;
|
||||||
34
api/src/log/log.module.ts
Normal file
34
api/src/log/log.module.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { DaprModule, DaprService } from '@noahspan/noahspan-modules';
|
||||||
|
import { LogController } from './log.controller';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||||
|
import { Log } from './log.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
AzureTableStorageModule.forRootAsync({
|
||||||
|
imports: [DaprModule],
|
||||||
|
useFactory: async (daprService: DaprService) => {
|
||||||
|
const connectionString = await daprService.daprClient.secret.get(
|
||||||
|
'key-vault',
|
||||||
|
'azure-storage-connection-string'
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionString:
|
||||||
|
connectionString['azure-storage-connection-string'].toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [DaprService]
|
||||||
|
}),
|
||||||
|
AzureTableStorageModule.forFeature(Log, {
|
||||||
|
createTableIfNotExists: false,
|
||||||
|
table: 'logs'
|
||||||
|
}),
|
||||||
|
DaprModule
|
||||||
|
],
|
||||||
|
controllers: [LogController],
|
||||||
|
providers: [LogService]
|
||||||
|
})
|
||||||
|
export class LogModule {}
|
||||||
36
api/src/log/log.service.ts
Normal file
36
api/src/log/log.service.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
|
import { Log } from './log.entity';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogService {
|
||||||
|
private readonly tableName = 'Logbook';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Log) private readonly logRepository: Repository<Log>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async find(partitionKey: string, rowKey: string): Promise<Log> {
|
||||||
|
return await this.logRepository.find(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<Log[]> {
|
||||||
|
return await this.logRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(log: Log): Promise<Log> {
|
||||||
|
log.partitionKey = 'log';
|
||||||
|
log.rowKey = uuidv4();
|
||||||
|
|
||||||
|
return await this.logRepository.create(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(partitionKey: string, rowKey: string, log: Log): Promise<Log> {
|
||||||
|
return await this.logRepository.update(partitionKey, rowKey, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(partitionKey: string, rowKey: string): Promise<void> {
|
||||||
|
await this.logRepository.delete(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Delete,
|
|
||||||
Get,
|
|
||||||
HttpException,
|
|
||||||
Param,
|
|
||||||
Post,
|
|
||||||
Put
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { LogbookService } from './logbook.service';
|
|
||||||
import { LogbookDto } from './logbook.dto';
|
|
||||||
import { CustomError } from '../customError/CustomError';
|
|
||||||
import { TableInsertEntityHeaders } from '@azure/data-tables';
|
|
||||||
import { LogbookEntity } from './logbook.entity';
|
|
||||||
|
|
||||||
@Controller('logbook')
|
|
||||||
export class LogbookController {
|
|
||||||
constructor(private readonly logbookService: LogbookService) {}
|
|
||||||
|
|
||||||
@Get(':entryId')
|
|
||||||
async find(@Param() params: any): Promise<LogbookEntity> {
|
|
||||||
try {
|
|
||||||
const entry: LogbookEntity = await this.logbookService.find(
|
|
||||||
params.entryId
|
|
||||||
);
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
async findAll(): Promise<LogbookEntity[]> {
|
|
||||||
try {
|
|
||||||
const logbookEntries: LogbookEntity[] =
|
|
||||||
await this.logbookService.findAll();
|
|
||||||
|
|
||||||
return logbookEntries;
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
async create(@Body() logbookData: LogbookDto): Promise<void> {
|
|
||||||
try {
|
|
||||||
const response: TableInsertEntityHeaders =
|
|
||||||
await this.logbookService.create(logbookData);
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put(':entryId')
|
|
||||||
async update(
|
|
||||||
@Param() params: any,
|
|
||||||
@Body() logbookData: LogbookDto
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
return await this.logbookService.update(logbookData);
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete(':rowKey')
|
|
||||||
async delete(@Param() params: any): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.logbookService.delete(params.rowKey);
|
|
||||||
} catch (error) {
|
|
||||||
const customError = error as CustomError;
|
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { LogbookController } from './logbook.controller';
|
|
||||||
import { LogbookService } from './logbook.service';
|
|
||||||
import { TableModule } from '@noahspan/noahspan-modules';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TableModule.register({
|
|
||||||
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
|
|
||||||
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
|
|
||||||
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
|
|
||||||
allowInsecureConnection: Boolean(
|
|
||||||
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
|
|
||||||
)
|
|
||||||
})
|
|
||||||
],
|
|
||||||
controllers: [LogbookController],
|
|
||||||
providers: [LogbookService]
|
|
||||||
})
|
|
||||||
export class LogbookModule {}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { TableClient, TableService } from '@noahspan/noahspan-modules';
|
|
||||||
import { LogbookDto } from './logbook.dto';
|
|
||||||
import { LogbookEntity } from './logbook.entity';
|
|
||||||
import { RestError, TableInsertEntityHeaders } from '@azure/data-tables';
|
|
||||||
import { CustomError } from '../customError/CustomError';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class LogbookService {
|
|
||||||
private readonly tableName = 'Logbook';
|
|
||||||
|
|
||||||
constructor(private readonly tableService: TableService) {}
|
|
||||||
|
|
||||||
async getLogbookEntries(filter: string): Promise<LogbookEntity[]> {
|
|
||||||
try {
|
|
||||||
const client: TableClient = await this.tableService.getTableClient(
|
|
||||||
this.tableName
|
|
||||||
);
|
|
||||||
const entities = await client.listEntities({
|
|
||||||
queryOptions: { filter: filter }
|
|
||||||
});
|
|
||||||
const logbookEntries: LogbookEntity[] = [];
|
|
||||||
|
|
||||||
for await (const entity of entities) {
|
|
||||||
const logbookEntry = {
|
|
||||||
partitionKey: entity.partitionKey.toString(),
|
|
||||||
rowKey: entity.rowKey.toString(),
|
|
||||||
pilotId: entity.pilotId.toString(),
|
|
||||||
pilotName: entity.pilotName.toString(),
|
|
||||||
date: entity.date.toString(),
|
|
||||||
aircraftMakeModel: entity.aircraftMakeModel.toString(),
|
|
||||||
aircraftIdentity: entity.aircraftIdentity.toString(),
|
|
||||||
routeFrom: entity.routeFrom.toString(),
|
|
||||||
routeTo: entity.routeTo.toString(),
|
|
||||||
durationOfFlight: Number(entity.durationOfFlight),
|
|
||||||
singleEngineLand: entity.singleEngineLand
|
|
||||||
? Number(entity.singleEngineLand)
|
|
||||||
: null,
|
|
||||||
simulatorAtd: entity.simulatorAtd
|
|
||||||
? Number(entity.simulatorAtd)
|
|
||||||
: null,
|
|
||||||
landingsDay: entity.landingsDay ? Number(entity.landingsDay) : null,
|
|
||||||
landingsNight: entity.landingsNight
|
|
||||||
? Number(entity.landingsNight)
|
|
||||||
: null,
|
|
||||||
groundTrainingReceived: entity.groundTrainingReceived
|
|
||||||
? Number(entity.groundTrainingReceived)
|
|
||||||
: null,
|
|
||||||
flightTrainingReceived: entity.flightTrainingReceived
|
|
||||||
? Number(entity.flightTrainingReceived)
|
|
||||||
: null,
|
|
||||||
crossCountry: entity.crossCountry
|
|
||||||
? Number(entity.crossCountry)
|
|
||||||
: null,
|
|
||||||
night: entity.night ? Number(entity.night) : null,
|
|
||||||
solo: entity.solo ? Number(entity.solo) : null,
|
|
||||||
pilotInCommand: entity.pilotInCommand
|
|
||||||
? Number(entity.pilotInCommand)
|
|
||||||
: null,
|
|
||||||
instrumentActual: entity.instrumentActual
|
|
||||||
? Number(entity.instrumentActual)
|
|
||||||
: null,
|
|
||||||
instrumentSimulated: entity.instrumentSimulated
|
|
||||||
? Number(entity.instrumentSimulated)
|
|
||||||
: null,
|
|
||||||
instrumentApproaches: entity.instrumentApproaches
|
|
||||||
? Number(entity.instrumentApproaches)
|
|
||||||
: null,
|
|
||||||
instrumentHolds: entity.instrumentHolds
|
|
||||||
? Number(entity.instrumentHolds)
|
|
||||||
: null,
|
|
||||||
instrumentNavTrack: entity.instrumentNavTrack
|
|
||||||
? Number(entity.instrumentNavTrack)
|
|
||||||
: null,
|
|
||||||
notes: entity.notes ? entity.notes.toString() : ''
|
|
||||||
};
|
|
||||||
|
|
||||||
logbookEntries.push(logbookEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return logbookEntries;
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async find(entryId: string): Promise<LogbookEntity> {
|
|
||||||
try {
|
|
||||||
const filter: string = `PartitionKey eq 'entry' and RowKey eq '${entryId}'`;
|
|
||||||
const entries: LogbookEntity[] = await this.getLogbookEntries(filter);
|
|
||||||
|
|
||||||
return entries[0];
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(): Promise<LogbookEntity[]> {
|
|
||||||
try {
|
|
||||||
const filter: string = `PartitionKey eq 'entry'`;
|
|
||||||
const entries: LogbookEntity[] = await this.getLogbookEntries(filter);
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(logbookData: LogbookDto): Promise<TableInsertEntityHeaders> {
|
|
||||||
const client: TableClient = await this.tableService.getTableClient(
|
|
||||||
this.tableName
|
|
||||||
);
|
|
||||||
const logbook: LogbookEntity = new LogbookEntity();
|
|
||||||
|
|
||||||
Object.assign(logbook, logbookData);
|
|
||||||
logbook.partitionKey = 'entry';
|
|
||||||
logbook.rowKey = uuidv4();
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await client.createEntity(logbook);
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(logbookData: LogbookDto): Promise<void> {
|
|
||||||
try {
|
|
||||||
const client: TableClient = await this.tableService.getTableClient(
|
|
||||||
this.tableName
|
|
||||||
);
|
|
||||||
const logbook: LogbookEntity = new LogbookEntity();
|
|
||||||
|
|
||||||
Object.assign(logbook, logbookData);
|
|
||||||
|
|
||||||
await client.upsertEntity(logbook, 'Replace');
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(rowKey: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
const client: TableClient = await this.tableService.getTableClient(
|
|
||||||
this.tableName
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.deleteEntity('entry', rowKey);
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { AppModule } from './app.module';
|
|
||||||
|
|
||||||
export async function createApp(): Promise<INestApplication> {
|
|
||||||
const app = await NestFactory.create(AppModule);
|
|
||||||
app.setGlobalPrefix('api');
|
|
||||||
|
|
||||||
await app.init();
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
if (process.env.NODE_ENV !== 'production') require('dotenv').config();
|
||||||
|
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { HttpService } from '@nestjs/axios';
|
||||||
@@ -20,6 +22,7 @@ async function bootstrap() {
|
|||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
app.enableCors();
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||||
import { Certificate } from './certificate.entity';
|
import { Certificate } from './certificate.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { InjectRepository, Repository } from '@nestjs/azure-database';
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endorsement } from './endorsement.entity';
|
import { Endorsement } from './endorsement.entity';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
export class PilotInfoEntity {
|
|
||||||
partitionKey: string;
|
|
||||||
rowKey: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
address?: string;
|
|
||||||
city?: string;
|
|
||||||
state?: string;
|
|
||||||
postalCode?: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PilotInfoDto } from './pilot-info.dto';
|
|
||||||
import { PilotInfoEntity } from './pilot-info.entity';
|
|
||||||
import { TableClient, TableService } from '@noahspan/noahspan-modules';
|
|
||||||
import { odata, RestError, TableInsertEntityHeaders } from '@azure/data-tables';
|
|
||||||
import { CustomError } from '../../customError/CustomError';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PilotInfoService {
|
|
||||||
private readonly partitionKey: string = 'info';
|
|
||||||
|
|
||||||
constructor(private readonly tableService: TableService) {}
|
|
||||||
|
|
||||||
async find(pilotId: string): Promise<PilotInfoEntity> {
|
|
||||||
try {
|
|
||||||
const client: TableClient =
|
|
||||||
await this.tableService.getTableClient('Pilots');
|
|
||||||
console.log(client);
|
|
||||||
const entities = await client.listEntities({
|
|
||||||
queryOptions: {
|
|
||||||
filter: odata`PartitionKey eq 'pilot' and RowKey eq '${pilotId}'`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let pilot: PilotInfoEntity;
|
|
||||||
console.log(entities);
|
|
||||||
for await (const entity of entities) {
|
|
||||||
pilot = {
|
|
||||||
partitionKey: entity.partitionKey,
|
|
||||||
rowKey: entity.rowKey,
|
|
||||||
id: entity.id.toString(),
|
|
||||||
name: entity.name.toString(),
|
|
||||||
address: entity.address.toString(),
|
|
||||||
city: entity.city.toString(),
|
|
||||||
state: entity.state.toString(),
|
|
||||||
postalCode: entity.postalCode.toString(),
|
|
||||||
email: entity.email.toString(),
|
|
||||||
phone: entity.phone.toString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return pilot;
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(): Promise<PilotInfoEntity[]> {
|
|
||||||
try {
|
|
||||||
const client: TableClient =
|
|
||||||
await this.tableService.getTableClient('Pilots');
|
|
||||||
const entities = await client.listEntities({
|
|
||||||
queryOptions: { filter: odata`PartitionKey eq 'pilot'` }
|
|
||||||
});
|
|
||||||
const pilots: PilotInfoEntity[] = [];
|
|
||||||
|
|
||||||
for await (const entity of entities) {
|
|
||||||
const pilot: PilotInfoEntity = {
|
|
||||||
partitionKey: entity.partitionKey,
|
|
||||||
rowKey: entity.rowKey,
|
|
||||||
id: entity.id.toString(),
|
|
||||||
name: entity.name.toString()
|
|
||||||
};
|
|
||||||
|
|
||||||
pilots.push(pilot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pilots;
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(pilotInfoData: PilotInfoDto): Promise<TableInsertEntityHeaders> {
|
|
||||||
try {
|
|
||||||
const client: TableClient =
|
|
||||||
await this.tableService.getTableClient('Pilots');
|
|
||||||
const pilotInfo: PilotInfoEntity = new PilotInfoEntity();
|
|
||||||
|
|
||||||
Object.assign(pilotInfo, pilotInfoData);
|
|
||||||
pilotInfo.partitionKey = 'pilot';
|
|
||||||
pilotInfo.rowKey = pilotInfo.id;
|
|
||||||
console.log(pilotInfo);
|
|
||||||
return await client.createEntity(pilotInfo);
|
|
||||||
} catch (error) {
|
|
||||||
const restError: RestError = error as RestError;
|
|
||||||
console.log(restError);
|
|
||||||
throw new CustomError(
|
|
||||||
restError.details['odataError']['message']['value'],
|
|
||||||
restError.details['odataError']['code'],
|
|
||||||
restError.statusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// async update(rowKey: string, pilotInfo: PilotInfo): Promise<PilotInfo> {
|
|
||||||
// return await this.pilotInfoRepository.update(
|
|
||||||
// this.partitionKey,
|
|
||||||
// rowKey,
|
|
||||||
// pilotInfo
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async delete(rowKey: string): Promise<void> {
|
|
||||||
// await this.pilotInfoRepository.delete(this.partitionKey, rowKey);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||||
import { Medical } from './medical.entity';
|
import { Medical } from './medical.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,71 +1,98 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
HttpException,
|
HttpException,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
|
Put,
|
||||||
UseInterceptors
|
UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PilotInfoService } from './info/pilot-info.service';
|
import { PilotDto } from './pilot.dto';
|
||||||
import { PilotInfoDto } from './info/pilot-info.dto';
|
import { Pilot } from './pilot.entity';
|
||||||
import { TableInsertEntityHeaders } from '@azure/data-tables';
|
import { PilotService } from './pilot.service';
|
||||||
import { CustomError } from '../customError/CustomError';
|
import { CustomError, Public } from '@noahspan/noahspan-modules';
|
||||||
import { PilotInfoEntity } from './info/pilot-info.entity';
|
import { PilotInterceptor } from './interceptors/pilot.interceptor';
|
||||||
import { PilotInterceptor } from 'src/pilot/interceptors/pilot.interceptor';
|
|
||||||
import { Public } from '@noahspan/noahspan-modules';
|
|
||||||
|
|
||||||
@Controller('pilots')
|
@Controller('pilots')
|
||||||
export class PilotController {
|
export class PilotController {
|
||||||
constructor(private readonly pilotInfoService: PilotInfoService) {}
|
constructor(private readonly pilotService: PilotService) {}
|
||||||
|
|
||||||
@Get(':pilotId')
|
@Get(':partitionKey/:rowKey')
|
||||||
@Public()
|
@Public()
|
||||||
@UseInterceptors(PilotInterceptor)
|
@UseInterceptors(PilotInterceptor)
|
||||||
async find(@Param() params: any): Promise<PilotInfoEntity> {
|
async find(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const pilot: PilotInfoEntity = await this.pilotInfoService.find(
|
return await this.pilotService.find(partitionKey, rowKey);
|
||||||
params.pilotId
|
|
||||||
);
|
|
||||||
|
|
||||||
return pilot;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Public()
|
@Public()
|
||||||
@UseInterceptors(PilotInterceptor)
|
@UseInterceptors(PilotInterceptor)
|
||||||
async findAll(): Promise<PilotInfoEntity[]> {
|
async findAll() {
|
||||||
try {
|
try {
|
||||||
const pilots: PilotInfoEntity[] = await this.pilotInfoService.findAll();
|
return await this.pilotService.findAll();
|
||||||
|
|
||||||
return pilots;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
|
async create(@Body() pilotDto: PilotDto) {
|
||||||
try {
|
try {
|
||||||
const response: TableInsertEntityHeaders =
|
const pilot = new Pilot();
|
||||||
await this.pilotInfoService.create(pilotInfoData);
|
|
||||||
|
Object.assign(pilot, pilotDto);
|
||||||
|
|
||||||
|
return await this.pilotService.create(pilot);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Put(':partitionKey/:rowKey')
|
||||||
|
async update(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string,
|
||||||
|
@Body() pilotDto: PilotDto
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const pilot = new Pilot();
|
||||||
|
|
||||||
|
Object.assign(pilot, pilotDto);
|
||||||
|
|
||||||
|
return await this.pilotService.update(partitionKey, rowKey, pilot);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':partitionKey/:rowKey')
|
||||||
|
async delete(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await this.pilotService.delete(partitionKey, rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export class PilotInfoDto {
|
export class PilotDto {
|
||||||
|
partitionKey: string;
|
||||||
|
rowKey: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
14
api/src/pilot/pilot.entity.ts
Normal file
14
api/src/pilot/pilot.entity.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { EntityString } from '@noahspan/azure-database';
|
||||||
|
|
||||||
|
export class Pilot {
|
||||||
|
@EntityString() partitionKey: string;
|
||||||
|
@EntityString() rowKey: string;
|
||||||
|
@EntityString() id: string;
|
||||||
|
@EntityString() name: string;
|
||||||
|
@EntityString() address?: string;
|
||||||
|
@EntityString() city?: string;
|
||||||
|
@EntityString() state?: string;
|
||||||
|
@EntityString() postalCode?: string;
|
||||||
|
@EntityString() email?: string;
|
||||||
|
@EntityString() phone?: string;
|
||||||
|
}
|
||||||
@@ -1,20 +1,34 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { DaprModule, DaprService } from '@noahspan/noahspan-modules';
|
||||||
import { PilotController } from './pilot.controller';
|
import { PilotController } from './pilot.controller';
|
||||||
import { TableModule } from '@noahspan/noahspan-modules';
|
import { PilotService } from './pilot.service';
|
||||||
import { PilotInfoService } from './info/pilot-info.service';
|
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||||
|
import { Pilot } from './pilot.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TableModule.register({
|
AzureTableStorageModule.forRootAsync({
|
||||||
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
|
imports: [DaprModule],
|
||||||
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
|
useFactory: async (daprService: DaprService) => {
|
||||||
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
|
const connectionString = await daprService.daprClient.secret.get(
|
||||||
allowInsecureConnection: Boolean(
|
'key-vault',
|
||||||
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
|
'azure-storage-connection-string'
|
||||||
)
|
);
|
||||||
})
|
|
||||||
|
return {
|
||||||
|
connectionString:
|
||||||
|
connectionString['azure-storage-connection-string'].toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [DaprService]
|
||||||
|
}),
|
||||||
|
AzureTableStorageModule.forFeature(Pilot, {
|
||||||
|
createTableIfNotExists: false,
|
||||||
|
table: 'pilots'
|
||||||
|
}),
|
||||||
|
DaprModule
|
||||||
],
|
],
|
||||||
controllers: [PilotController],
|
controllers: [PilotController],
|
||||||
providers: [PilotInfoService]
|
providers: [PilotService]
|
||||||
})
|
})
|
||||||
export class PilotModule {}
|
export class PilotModule {}
|
||||||
|
|||||||
40
api/src/pilot/pilot.service.ts
Normal file
40
api/src/pilot/pilot.service.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Pilot } from './pilot.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PilotService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Pilot)
|
||||||
|
private readonly pilotRepository: Repository<Pilot>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
|
||||||
|
return await this.pilotRepository.find(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<Pilot[]> {
|
||||||
|
return await this.pilotRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(pilot: Pilot): Promise<Pilot> {
|
||||||
|
// try {
|
||||||
|
// return await this.pilotRepository.create(pilot);
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new Error(error);
|
||||||
|
// }
|
||||||
|
return await this.pilotRepository.create(pilot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
partitionKey: string,
|
||||||
|
rowKey: string,
|
||||||
|
pilot: Pilot
|
||||||
|
): Promise<Pilot> {
|
||||||
|
return await this.pilotRepository.update(partitionKey, rowKey, pilot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(partitionKey: string, rowKey: string): Promise<void> {
|
||||||
|
await this.pilotRepository.delete(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Dockerfile
Normal file
18
app/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
ARG VITE_API_URL
|
||||||
|
ARG VITE_CLIENT_ID
|
||||||
|
ARG VITE_TENANT_ID
|
||||||
|
ARG VITE_REDIRECT_URL
|
||||||
|
|
||||||
|
FROM --platform=linux/amd64 node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./dist ./dist
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
RUN npm i -g serve
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD [ "serve", "-s", "dist", "-p", "8080" ]
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "@noahspan/flying-client",
|
"name": "@noahspan/flying-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.10",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"serve": "serve -s dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^3.17.0",
|
"@azure/msal-browser": "^3.17.0",
|
||||||
"@azure/msal-react": "^2.0.19",
|
"@azure/msal-react": "^2.0.19",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
||||||
"@noahspan/noahspan-components": "^0.8.9",
|
"@noahspan/noahspan-components": "^0.8.9",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"framer-motion": "^11.1.7",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.51.4",
|
"react-hook-form": "^7.51.4",
|
||||||
@@ -29,9 +25,6 @@
|
|||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.19",
|
|
||||||
"postcss": "^8.4.38",
|
|
||||||
"tailwindcss": "^3.4.3",
|
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -19,6 +19,7 @@ const App: React.FC<unknown> = () => {
|
|||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(
|
||||||
`api/featureFlags?keys=${featureFlagKeys}&label=${import.meta.env.MODE}`
|
`api/featureFlags?keys=${featureFlagKeys}&label=${import.meta.env.MODE}`
|
||||||
);
|
);
|
||||||
|
console.log(response);
|
||||||
const featureFlags: { key: string; enabled: boolean }[] = response.data;
|
const featureFlags: { key: string; enabled: boolean }[] = response.data;
|
||||||
|
|
||||||
if (featureFlags.length > 0) {
|
if (featureFlags.length > 0) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
|
||||||
export interface ILogbookEntryFormProps {
|
export interface ILogFormProps {
|
||||||
entryId?: string;
|
entryId?: string;
|
||||||
isDrawerOpen: boolean;
|
isDrawerOpen: boolean;
|
||||||
mode: FormMode;
|
mode: FormMode;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface ILogbookEntryFormState {
|
export interface ILogFormState {
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
XmarkIcon
|
XmarkIcon
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||||
import { ILogbookEntryFormProps } from './ILogbookEntryFormProps';
|
import { ILogFormProps } from './ILogFormProps';
|
||||||
import { initialState, reducer } from './reducer';
|
import { initialState, reducer } from './reducer';
|
||||||
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
@@ -26,7 +26,7 @@ import { useIsAuthenticated } from '@azure/msal-react';
|
|||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
import { usePilots } from '../../hooks/pilots/UsePilots';
|
import { usePilots } from '../../hooks/pilots/UsePilots';
|
||||||
|
|
||||||
const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
const LogForm: React.FC<ILogFormProps> = ({
|
||||||
entryId,
|
entryId,
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
mode,
|
mode,
|
||||||
@@ -37,8 +37,6 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
const { getAccessToken } = useAccessToken();
|
const { getAccessToken } = useAccessToken();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
partitionKey: '',
|
|
||||||
rowKey: '',
|
|
||||||
pilotId: '',
|
pilotId: '',
|
||||||
pilotName: '',
|
pilotName: '',
|
||||||
date: null,
|
date: null,
|
||||||
@@ -80,13 +78,13 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
const accessToken: string = await getAccessToken();
|
const accessToken: string = await getAccessToken();
|
||||||
|
|
||||||
if (!entryId) {
|
if (!entryId) {
|
||||||
await httpClient.post(`api/logbook`, data, {
|
await httpClient.post(`api/logs`, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await httpClient.put(`api/logbook/${entryId}`, data, {
|
await httpClient.put(`api/logs/${entryId}`, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
@@ -120,7 +118,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
? { headers: { Authorization: await getAccessToken() } }
|
? { headers: { Authorization: await getAccessToken() } }
|
||||||
: {};
|
: {};
|
||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(
|
||||||
`api/logbook/${entryId}`,
|
`api/logs/${entryId}`,
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
const entry = response.data;
|
const entry = response.data;
|
||||||
@@ -939,4 +937,4 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LogbookEntryForm;
|
export default LogForm;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ILogbookEntryFormState } from './ILogbookEntryFormState';
|
import { ILogFormState } from './ILogFormState';
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| { type: 'SET_ERROR'; payload: string | undefined }
|
| { type: 'SET_ERROR'; payload: string | undefined }
|
||||||
@@ -7,7 +7,7 @@ type Action =
|
|||||||
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||||
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
||||||
|
|
||||||
export const initialState: ILogbookEntryFormState = {
|
export const initialState: ILogFormState = {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@@ -16,9 +16,9 @@ export const initialState: ILogbookEntryFormState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const reducer = (
|
export const reducer = (
|
||||||
state: ILogbookEntryFormState,
|
state: ILogFormState,
|
||||||
action: Action
|
action: Action
|
||||||
): ILogbookEntryFormState => {
|
): ILogFormState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_ERROR': {
|
case 'SET_ERROR': {
|
||||||
return {
|
return {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useReducer, useState } from 'react';
|
import { useEffect, useReducer } from 'react';
|
||||||
import LogbookEntryForm from '../logbookEntryForm/LogbookEntryForm';
|
import LogForm from '../logForm/LogForm';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
@@ -35,10 +35,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
const config = isAuthenticated
|
const config = isAuthenticated
|
||||||
? { headers: { Authorization: `${token}` } }
|
? { headers: { Authorization: `${token}` } }
|
||||||
: {};
|
: {};
|
||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(`api/logs`, config);
|
||||||
`api/logbook`,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -98,7 +95,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
? { headers: { Authorization: `${token}` } }
|
? { headers: { Authorization: `${token}` } }
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
await httpClient.delete(`api/logbook/${state.selectedEntryId}`, config);
|
await httpClient.delete(`api/logs/${state.selectedEntryId}`, config);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_DELETE',
|
type: 'SET_DELETE',
|
||||||
@@ -419,7 +416,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
{state.isFormOpen && (
|
{state.isFormOpen && (
|
||||||
<LogbookEntryForm
|
<LogForm
|
||||||
entryId={state.selectedEntryId}
|
entryId={state.selectedEntryId}
|
||||||
isDrawerOpen={state.isFormOpen}
|
isDrawerOpen={state.isFormOpen}
|
||||||
mode={state.formMode}
|
mode={state.formMode}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
XmarkIcon
|
XmarkIcon
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { IPilotFormProps } from './IPilotFormProps';
|
import { IPilotFormProps } from './IPilotFormProps';
|
||||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||||
import { useIsAuthenticated } from '@azure/msal-react';
|
import { useIsAuthenticated } from '@azure/msal-react';
|
||||||
@@ -38,8 +38,8 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
const { getAccessToken } = useAccessToken();
|
const { getAccessToken } = useAccessToken();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
partitionKey: '',
|
partitionKey: 'pilot',
|
||||||
rowKey: '',
|
rowKey: 'noah@noahspannbauer.com',
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
address: '',
|
address: '',
|
||||||
@@ -53,6 +53,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
defaultValues: defaultValues
|
defaultValues: defaultValues
|
||||||
});
|
});
|
||||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||||
|
const [isError, setIsError] = useState<boolean>(false);
|
||||||
|
|
||||||
const onPeoplePickerSearch = async (
|
const onPeoplePickerSearch = async (
|
||||||
_event: React.SyntheticEvent,
|
_event: React.SyntheticEvent,
|
||||||
@@ -105,24 +106,19 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const accessToken: string = await getAccessToken();
|
const accessToken: string = await getAccessToken();
|
||||||
const response: AxiosResponse = await httpClient.post(
|
|
||||||
`api/pilots`,
|
await httpClient.post(`api/pilots`, data, {
|
||||||
data,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (response) console.log(response);
|
onOpenClose(FormMode.CANCEL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error)) {
|
const axiosError = error as AxiosError;
|
||||||
const errResp = error.response;
|
const responseData = axiosError.response?.data as any;
|
||||||
|
|
||||||
console.log(errResp?.data.message);
|
console.log(responseData.message);
|
||||||
} else {
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,25 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
|
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
|
||||||
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
|
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
|
||||||
const [pilots, setPilots] = useState<Pilot[]>([]);
|
const [pilots, setPilots] = useState<Pilot[]>([]);
|
||||||
const onOpenClosePilotForm = (mode: FormMode, pilotId?: string) => {
|
|
||||||
|
const getPilots = async (): Promise<Pilot[]> => {
|
||||||
|
try {
|
||||||
|
const config = isAuthenticated
|
||||||
|
? { headers: { Authorization: await getAccessToken() } }
|
||||||
|
: {};
|
||||||
|
const response: AxiosResponse = await httpClient.get(
|
||||||
|
`api/pilots`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
const pilots: Pilot[] = response.data;
|
||||||
|
|
||||||
|
return pilots;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('broken');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenClosePilotForm = async (mode: FormMode, pilotId?: string) => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case FormMode.ADD:
|
case FormMode.ADD:
|
||||||
case FormMode.EDIT:
|
case FormMode.EDIT:
|
||||||
@@ -49,6 +67,9 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
setIsDrawerOpen(true);
|
setIsDrawerOpen(true);
|
||||||
break;
|
break;
|
||||||
case FormMode.CANCEL:
|
case FormMode.CANCEL:
|
||||||
|
const pilots = await getPilots();
|
||||||
|
|
||||||
|
setPilots(pilots);
|
||||||
setPilotFormMode(mode);
|
setPilotFormMode(mode);
|
||||||
setSelectedPilotId(undefined);
|
setSelectedPilotId(undefined);
|
||||||
setIsDrawerOpen(false);
|
setIsDrawerOpen(false);
|
||||||
@@ -73,6 +94,7 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
const onCloseActionMenu = () => {
|
const onCloseActionMenu = () => {
|
||||||
setAnchorElAction(null);
|
setAnchorElAction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<IconButton onClick={onOpenActionMenu}>
|
<IconButton onClick={onOpenActionMenu}>
|
||||||
@@ -124,23 +146,17 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getPilots = async () => {
|
const loadPilots = async () => {
|
||||||
try {
|
try {
|
||||||
const config = isAuthenticated
|
const pilots = await getPilots();
|
||||||
? { headers: { Authorization: await getAccessToken() } }
|
|
||||||
: {};
|
setPilots(pilots);
|
||||||
const response: AxiosResponse = await httpClient.get(
|
|
||||||
`api/pilots`,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
console.log(response.data);
|
|
||||||
setPilots(response.data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getPilots();
|
loadPilots();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
12
app/src/index.html
Normal file
12
app/src/index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
||||||
theme: {
|
|
||||||
extend: {}
|
|
||||||
},
|
|
||||||
darkMode: 'class',
|
|
||||||
plugins: []
|
|
||||||
};
|
|
||||||
@@ -3,5 +3,14 @@ import react from '@vitejs/plugin-react';
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()]
|
plugins: [react()],
|
||||||
|
preview: {
|
||||||
|
port: 8080,
|
||||||
|
strictPort: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 8080,
|
||||||
|
strictPort: true,
|
||||||
|
host: '0.0.0.0'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
14
dapr.yaml
Normal file
14
dapr.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: 1
|
||||||
|
|
||||||
|
common:
|
||||||
|
resourcesPaths: ['./.dapr/components']
|
||||||
|
|
||||||
|
apps:
|
||||||
|
- appID: flying-api
|
||||||
|
appDirPath: ./api
|
||||||
|
appPort: 3000
|
||||||
|
command: ['npm', 'run', 'start']
|
||||||
|
- appID: flying-app
|
||||||
|
appDirPath: ./app
|
||||||
|
appPort: 5173
|
||||||
|
command: ['npm', 'run', 'dev']
|
||||||
20
infrastructure/app_config.tf
Normal file
20
infrastructure/app_config.tf
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
data "azurerm_app_configuration" "app_configuration" {
|
||||||
|
name = "noahspan-appconfig"
|
||||||
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_app_configuration_feature" "pilots_app_configuration_feature" {
|
||||||
|
configuration_store_id = data.azurerm_app_configuration.app_configuration.id
|
||||||
|
description = "Feature flag for pilots page"
|
||||||
|
name = "flying-pilots"
|
||||||
|
label = var.WORKSPACE
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_app_configuration_feature" "logbook_app_configuration_feature" {
|
||||||
|
configuration_store_id = data.azurerm_app_configuration.app_configuration.id
|
||||||
|
description = "Feature flag for logbook page"
|
||||||
|
name = "flying-logbook"
|
||||||
|
label = var.WORKSPACE
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
4
infrastructure/config.tf
Normal file
4
infrastructure/config.tf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module "environment" {
|
||||||
|
source = "./config"
|
||||||
|
environment = var.WORKSPACE
|
||||||
|
}
|
||||||
50
infrastructure/config/main.tf
Normal file
50
infrastructure/config/main.tf
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
locals {
|
||||||
|
container_app_app_name = {
|
||||||
|
dev = "flying-app-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
container_app_app_container_image = {
|
||||||
|
dev = "noahspan/flying-app:v0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_app_container_name = {
|
||||||
|
dev = "flying-app-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_api_name = {
|
||||||
|
dev = "flying-api-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_api_container_image = {
|
||||||
|
dev = "noahspan/flying-api:v0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_api_container_name = {
|
||||||
|
dev = "flying-api-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_api_dapr_app_id = {
|
||||||
|
dev = "flyingapidev"
|
||||||
|
}
|
||||||
|
|
||||||
|
container_app_environment_name = {
|
||||||
|
dev = "flying-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
key_vault_name = {
|
||||||
|
dev = "noahspanflyingkeyvault"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_analytics_workspace_name = {
|
||||||
|
dev = "flying-log-analytics-workspace-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_account_name = {
|
||||||
|
dev = "noahspanflyingdev"
|
||||||
|
}
|
||||||
|
|
||||||
|
user_assigned_identity_name = {
|
||||||
|
dev = "noahspanflyingdevuser"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
infrastructure/config/outputs.tf
Normal file
47
infrastructure/config/outputs.tf
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
output "container_app_app_name" {
|
||||||
|
value = local.container_app_app_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_app_container_image" {
|
||||||
|
value = local.container_app_app_container_image[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_app_container_name" {
|
||||||
|
value = local.container_app_app_container_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_api_name" {
|
||||||
|
value = local.container_app_api_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_api_container_image" {
|
||||||
|
value = local.container_app_api_container_image[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_api_container_name" {
|
||||||
|
value = local.container_app_api_container_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_api_dapr_app_id" {
|
||||||
|
value = local.container_app_api_dapr_app_id[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "container_app_environment_name" {
|
||||||
|
value = local.container_app_environment_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "key_vault_name" {
|
||||||
|
value = local.key_vault_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "log_analytics_workspace_name" {
|
||||||
|
value = local.log_analytics_workspace_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "storage_account_name" {
|
||||||
|
value = local.storage_account_name[var.environment]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "user_assigned_identity_name" {
|
||||||
|
value = local.user_assigned_identity_name[var.environment]
|
||||||
|
}
|
||||||
1
infrastructure/config/variables.tf
Normal file
1
infrastructure/config/variables.tf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
variable "environment" {}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
resource "azurerm_container_app" "container_app_dev" {
|
|
||||||
name = "flying-api-dev"
|
|
||||||
container_app_environment_id = azurerm_container_app_environment.container_app_environment_dev.id
|
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
|
||||||
revision_mode = "Single"
|
|
||||||
|
|
||||||
registry {
|
|
||||||
server = "index.docker.io"
|
|
||||||
username = var.DOCKER_IO_USERNAME
|
|
||||||
password_secret_name = "docker-io-password"
|
|
||||||
}
|
|
||||||
|
|
||||||
template {
|
|
||||||
min_replicas = 0
|
|
||||||
max_replicas = 5
|
|
||||||
|
|
||||||
container {
|
|
||||||
name = "flying-api-dev"
|
|
||||||
image = "noahspan/flying-api:v0.0.1"
|
|
||||||
cpu = 0.25
|
|
||||||
memory = "0.5Gi"
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "APP_CONFIG_URL"
|
|
||||||
value = "https://noahspann-appconfig.azconfig.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "AZURE_STORAGE_ACCOUNT_KEY"
|
|
||||||
secret_name = "azure-storage-account-key"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "AZURE_STORAGE_ACCOUNT_NAME"
|
|
||||||
value = "noahspanflyingdev"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "AZURE_STROAGE_ACCOUNT_URL"
|
|
||||||
value = "https://noahspanflyingdev.table.core.windows.net/"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "CLIENT_ID"
|
|
||||||
secret_name = "client-id"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "CLIENT_SECRET"
|
|
||||||
secret_name = "client-secret"
|
|
||||||
}
|
|
||||||
|
|
||||||
env {
|
|
||||||
name = "TENANT_ID"
|
|
||||||
value = "0f23652e-4b15-420f-991e-3d6fc769a31d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ingress {
|
|
||||||
allow_insecure_connections = false
|
|
||||||
external_enabled = true
|
|
||||||
target_port = 80
|
|
||||||
transport = "auto"
|
|
||||||
|
|
||||||
traffic_weight {
|
|
||||||
latest_revision = true
|
|
||||||
percentage = 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
secret {
|
|
||||||
name = "azure-storage-account-key"
|
|
||||||
value = var.AZURE_STORAGE_ACCOUNT_KEY
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,2 @@
|
|||||||
resource "azurerm_log_analytics_workspace" "log_analytics_workspace" {
|
|
||||||
name = "flying-log-analytics-workspace"
|
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
|
||||||
sku = "PerGB2018"
|
|
||||||
retention_in_days = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "azurerm_container_app_environment" "container_app_environment_dev" {
|
|
||||||
name = "flying"
|
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
|
||||||
log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id
|
|
||||||
}
|
|
||||||
|
|||||||
66
infrastructure/dapr.tf
Normal file
66
infrastructure/dapr.tf
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
resource "azurerm_container_app_environment_dapr_component" "table_storage_pilots_dapr_component" {
|
||||||
|
name = "table-storage-pilots"
|
||||||
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
|
component_type = "state.azure.tablestorage"
|
||||||
|
version = "v1"
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "accountName"
|
||||||
|
value = azurerm_storage_account.storage_account.name
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "azureClientId"
|
||||||
|
value = azurerm_user_assigned_identity.user_assigned_identity.id
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "tableName"
|
||||||
|
value = "Pilots"
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes = [ azurerm_container_app.container_app_api.dapr[0].app_id ]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_container_app_environment_dapr_component" "table_storage_logbook_dapr_component" {
|
||||||
|
name = "table-storage-logbook"
|
||||||
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
|
component_type = "state.azure.tablestorage"
|
||||||
|
version = "v1"
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "accountName"
|
||||||
|
value = azurerm_storage_account.storage_account.name
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "azureClientId"
|
||||||
|
value = azurerm_user_assigned_identity.user_assigned_identity.client_id
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "tableName"
|
||||||
|
value = "Logbook"
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes = [ azurerm_container_app.container_app_api.dapr[0].app_id ]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_container_app_environment_dapr_component" "key_vault_dapr_component" {
|
||||||
|
name = "key-vault"
|
||||||
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
|
component_type = "secretstores.azure.keyvault"
|
||||||
|
version = "v1"
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "vaultName"
|
||||||
|
value = azurerm_key_vault.key_vault.name
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
name = "azureClientId"
|
||||||
|
value = azurerm_user_assigned_identity.user_assigned_identity.client_id
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes = [ azurerm_container_app.container_app_api.dapr[0].app_id ]
|
||||||
|
}
|
||||||
@@ -2,3 +2,14 @@ data "azurerm_dns_zone" "dns_zone" {
|
|||||||
name = var.DOMAIN_NAME
|
name = var.DOMAIN_NAME
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# resource "azurerm_dns_txt_record" "dns_txt_record" {
|
||||||
|
# name = "asuid.${var.APP_SUBDOMAIN_NAME}"
|
||||||
|
# resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
# zone_name = data.azurerm_dns_zone.dns_zone.name
|
||||||
|
# ttl = 14400
|
||||||
|
|
||||||
|
# record {
|
||||||
|
# value = azurerm_container_app.container_app_app.custom_domain_verification_id
|
||||||
|
# }
|
||||||
|
# }
|
||||||
5
infrastructure/identity.tf
Normal file
5
infrastructure/identity.tf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
resource "azurerm_user_assigned_identity" "user_assigned_identity" {
|
||||||
|
name = module.environment.user_assigned_identity_name
|
||||||
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
|
}
|
||||||
70
infrastructure/key_vault.tf
Normal file
70
infrastructure/key_vault.tf
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
resource "azurerm_key_vault" "key_vault" {
|
||||||
|
name = module.environment.key_vault_name
|
||||||
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||||
|
soft_delete_retention_days = 7
|
||||||
|
sku_name = "standard"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_key_vault_access_policy" "key_vault_access_policy_cli" {
|
||||||
|
key_vault_id = azurerm_key_vault.key_vault.id
|
||||||
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||||
|
object_id = data.azurerm_client_config.current.object_id
|
||||||
|
|
||||||
|
key_permissions = [
|
||||||
|
"Create",
|
||||||
|
"Get",
|
||||||
|
]
|
||||||
|
|
||||||
|
secret_permissions = [
|
||||||
|
"Set",
|
||||||
|
"Get",
|
||||||
|
"Delete",
|
||||||
|
"Purge",
|
||||||
|
"Recover"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_key_vault_access_policy" "key_vault_access_policy_user_assigned_identity" {
|
||||||
|
key_vault_id = azurerm_key_vault.key_vault.id
|
||||||
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||||
|
object_id = azurerm_user_assigned_identity.user_assigned_identity.principal_id
|
||||||
|
|
||||||
|
key_permissions = [
|
||||||
|
"Create",
|
||||||
|
"Get",
|
||||||
|
]
|
||||||
|
|
||||||
|
secret_permissions = [
|
||||||
|
"Set",
|
||||||
|
"Get",
|
||||||
|
"Delete",
|
||||||
|
"Purge",
|
||||||
|
"Recover"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_key_vault_secret" "client_id_key_vault_secret" {
|
||||||
|
name = "client-id"
|
||||||
|
value = var.CLIENT_ID
|
||||||
|
key_vault_id = azurerm_key_vault.key_vault.id
|
||||||
|
|
||||||
|
depends_on = [ azurerm_key_vault_access_policy.key_vault_access_policy_cli ]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_key_vault_secret" "client_secret_key_vault_secret" {
|
||||||
|
name = "client-secret"
|
||||||
|
value = var.CLIENT_SECRET
|
||||||
|
key_vault_id = azurerm_key_vault.key_vault.id
|
||||||
|
|
||||||
|
depends_on = [ azurerm_key_vault_access_policy.key_vault_access_policy_cli ]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_key_vault_secret" "tenant_id_key_vault_secret" {
|
||||||
|
name = "tenant-id"
|
||||||
|
value = var.TENANT_ID
|
||||||
|
key_vault_id = azurerm_key_vault.key_vault.id
|
||||||
|
|
||||||
|
depends_on = [ azurerm_key_vault_access_policy.key_vault_access_policy_cli ]
|
||||||
|
}
|
||||||
@@ -1,23 +1,124 @@
|
|||||||
resource "azurerm_static_web_app" "static_web_app" {
|
data "azurerm_client_config" "current" {
|
||||||
name = var.STATIC_WEB_APP_NAME
|
}
|
||||||
|
|
||||||
|
resource "azurerm_log_analytics_workspace" "log_analytics_workspace" {
|
||||||
|
name = module.environment.log_analytics_workspace_name
|
||||||
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
location = var.REGION
|
sku = "PerGB2018"
|
||||||
sku_size = "Free"
|
retention_in_days = 30
|
||||||
sku_tier = "Free"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_dns_cname_record" "dns_cname_record_app" {
|
resource "azurerm_container_app_environment" "container_app_environment" {
|
||||||
name = var.APP_SUBDOMAIN_NAME
|
name = module.environment.container_app_environment_name
|
||||||
zone_name = data.azurerm_dns_zone.dns_zone.name
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
ttl = 14400
|
log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id
|
||||||
record = azurerm_static_web_app.static_web_app.default_host_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain" {
|
resource "azurerm_container_app" "container_app_api" {
|
||||||
static_web_app_id = azurerm_static_web_app.static_web_app.id
|
name = module.environment.container_app_api_name
|
||||||
domain_name = "${var.APP_SUBDOMAIN_NAME}.${var.DOMAIN_NAME}"
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
validation_type = "cname-delegation"
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
revision_mode = "Single"
|
||||||
|
|
||||||
depends_on = [ azurerm_dns_cname_record.dns_cname_record_app ]
|
registry {
|
||||||
|
server = "index.docker.io"
|
||||||
|
username = var.DOCKER_IO_USERNAME
|
||||||
|
password_secret_name = "docker-io-password"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dapr {
|
||||||
|
app_id = module.environment.container_app_api_dapr_app_id
|
||||||
|
app_port = 80
|
||||||
|
app_protocol = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
min_replicas = 0
|
||||||
|
max_replicas = 2
|
||||||
|
|
||||||
|
container {
|
||||||
|
name = module.environment.container_app_api_container_name
|
||||||
|
image = module.environment.container_app_api_container_image
|
||||||
|
cpu = 0.25
|
||||||
|
memory = "0.5Gi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ingress {
|
||||||
|
allow_insecure_connections = false
|
||||||
|
external_enabled = true
|
||||||
|
target_port = 80
|
||||||
|
transport = "auto"
|
||||||
|
|
||||||
|
traffic_weight {
|
||||||
|
latest_revision = true
|
||||||
|
percentage = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secret {
|
||||||
|
name = "docker-io-password"
|
||||||
|
value = var.DOCKER_IO_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
ignore_changes = [ template[0].container[0].image ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_container_app" "container_app_app" {
|
||||||
|
name = module.environment.container_app_app_name
|
||||||
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
revision_mode = "Single"
|
||||||
|
|
||||||
|
registry {
|
||||||
|
server = "index.docker.io"
|
||||||
|
username = var.DOCKER_IO_USERNAME
|
||||||
|
password_secret_name = "docker-io-password"
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
min_replicas = 0
|
||||||
|
max_replicas = 2
|
||||||
|
|
||||||
|
container {
|
||||||
|
name = module.environment.container_app_app_container_name
|
||||||
|
image = module.environment.container_app_app_container_image
|
||||||
|
cpu = 0.25
|
||||||
|
memory = "0.5Gi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ingress {
|
||||||
|
allow_insecure_connections = false
|
||||||
|
external_enabled = true
|
||||||
|
target_port = 80
|
||||||
|
transport = "auto"
|
||||||
|
|
||||||
|
traffic_weight {
|
||||||
|
latest_revision = true
|
||||||
|
percentage = 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secret {
|
||||||
|
name = "docker-io-password"
|
||||||
|
value = var.DOCKER_IO_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
ignore_changes = [ template[0].container[0].image ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# resource "azurerm_container_app_custom_domain" "custom_domain" {
|
||||||
|
# name = trimsuffix(trimprefix(azurerm_dns_txt_record.dns_txt_record.fqdn, "asuid."), ".")
|
||||||
|
# container_app_id = azurerm_container_app.container_app_app.id
|
||||||
|
|
||||||
|
# lifecycle {
|
||||||
|
# ignore_changes = [ certificate_binding_type, container_app_environment_certificate_id ]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
output "api_key" {
|
# output "api_key" {
|
||||||
value = azurerm_static_web_app.static_web_app.api_key
|
# value = azurerm_static_web_app.static_web_app.api_key
|
||||||
sensitive = true
|
# sensitive = true
|
||||||
}
|
# }
|
||||||
|
|
||||||
output "default_host_name" {
|
# output "default_host_name" {
|
||||||
value = azurerm_static_web_app.static_web_app.default_host_name
|
# value = azurerm_static_web_app.static_web_app.default_host_name
|
||||||
}
|
# }
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
provider "azurerm" {
|
provider "azurerm" {
|
||||||
features {}
|
features {
|
||||||
|
key_vault {
|
||||||
|
purge_soft_deleted_certificates_on_destroy = true
|
||||||
|
recover_soft_deleted_secrets = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
resource "azurerm_storage_account" "storage_account" {
|
resource "azurerm_storage_account" "storage_account" {
|
||||||
name = "${var.STORAGE_ACCOUNT_NAME}"
|
name = module.environment.storage_account_name
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
account_tier = "Standard"
|
account_tier = "Standard"
|
||||||
account_replication_type = "LRS"
|
account_replication_type = "LRS"
|
||||||
|
|
||||||
|
identity {
|
||||||
|
type = "UserAssigned"
|
||||||
|
identity_ids = [azurerm_user_assigned_identity.user_assigned_identity.id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_storage_table" "logbook_storage_table" {
|
resource "azurerm_storage_table" "logs_table" {
|
||||||
name = "Logbook"
|
name = "logs"
|
||||||
storage_account_name = azurerm_storage_account.storage_account.name
|
storage_account_name = azurerm_storage_account.storage_account.name
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_storage_table" "pilots_storage_table" {
|
resource "azurerm_storage_table" "pilot_table" {
|
||||||
name = "Pilots"
|
name = "pilots"
|
||||||
storage_account_name = azurerm_storage_account.storage_account.name
|
storage_account_name = azurerm_storage_account.storage_account.name
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,6 @@ variable "APP_SUBDOMAIN_NAME" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "AZURE_STORAGE_ACCOUNT_KEY" {
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "CLIENT_ID" {
|
variable "CLIENT_ID" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
@@ -45,14 +40,6 @@ variable "RESOURCE_GROUP_NAME" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "STATIC_WEB_APP_NAME" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "STORAGE_ACCOUNT_NAME" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "TENANT_ID" {
|
variable "TENANT_ID" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|||||||
3926
package-lock.json
generated
3926
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user