Feature/33 api fails when no pilots or logbook entries (#35)
* api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * api no longer fails when no pilots or logbook entries * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * refactoring * refactoring * refactoring * refactoring * adding test environment * adding test environment
This commit was merged in pull request #35.
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
infrastructure
|
||||
test
|
||||
62
.github/workflows/api.yaml
vendored
62
.github/workflows/api.yaml
vendored
@@ -1,62 +0,0 @@
|
||||
name: 'API Build and Deploy'
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
calling_workflow:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.calling_workflow }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: 'Setup Node'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ vars.NODE_VERSION }}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
npm run build -w api
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to registry
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: noahspan/flying-api:${{ github.run_id }}
|
||||
file: ./api/Dockerfile
|
||||
context: ./api
|
||||
|
||||
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 container app update --name flying-api --resource-group noahspan --image noahspan-flying:${{ github.run_id }}
|
||||
69
.github/workflows/api_build.yaml
vendored
Normal file
69
.github/workflows/api_build.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: 'API Build and Deploy'
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
environment_name:
|
||||
required: true
|
||||
type: string
|
||||
version_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.environment_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
api
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Install Nest CLI
|
||||
run: |
|
||||
npm install -g @nestjs/cli
|
||||
|
||||
- name: 'Setup Node'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ vars.NODE_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pnpm --filter api build
|
||||
|
||||
- name: Deploy
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
pnpm --filter api --prod deploy ./.prod/api
|
||||
|
||||
- name: Log into Docker Hub
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push to registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: noahspan/flying-api:${{ inputs.version_number }}
|
||||
context: .
|
||||
target: api
|
||||
48
.github/workflows/app.yaml
vendored
48
.github/workflows/app.yaml
vendored
@@ -1,48 +0,0 @@
|
||||
name: 'App Build and Deploy'
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
calling_workflow:
|
||||
required: true
|
||||
type: string
|
||||
deployment_environment:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: 'Setup Node'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ vars.NODE_VERSION }}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm run build -w app
|
||||
|
||||
- name: Deploy
|
||||
id: builddeploy
|
||||
uses: Azure/static-web-apps-deploy@v1
|
||||
with:
|
||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
action: 'upload'
|
||||
skip_app_build: true
|
||||
app_location: ${{ vars.APP_LOCATION }}
|
||||
deployment_environment: ${{ inputs.deployment_environment }}
|
||||
69
.github/workflows/app_build.yaml
vendored
Normal file
69
.github/workflows/app_build.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: 'App Build and Deploy'
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
calling_workflow:
|
||||
required: true
|
||||
type: string
|
||||
version_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: |
|
||||
app
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: 'Setup Node'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ vars.NODE_VERSION }}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
pnpm install
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pnpm --filter app build
|
||||
|
||||
- name: Deploy
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
pnpm --filter app --prod deploy ./.prod/app
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into Docker Hub
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: noahspan/flying-app:${{ inputs.version_number }}
|
||||
context: .
|
||||
target: app
|
||||
27
.github/workflows/deploy.yaml
vendored
Normal file
27
.github/workflows/deploy.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Deploy
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_name:
|
||||
required: true
|
||||
type: string
|
||||
environment_name:
|
||||
required: true
|
||||
type: string
|
||||
version_number:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.environment_name }}
|
||||
steps:
|
||||
- name: Log in to Azure
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Update Container App
|
||||
run: |
|
||||
az containerapp update --name ${{ inputs.app_name }}}}-${{ inputs.environment_name }} --revision-suffix ${{ inputs.version_number }} --resource-group noahspan --image docker.io/noahspan/${{ inputs.app_name }}:${{ inputs.version_number }}
|
||||
21
.github/workflows/dev.yaml
vendored
21
.github/workflows/dev.yaml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Dev
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feature/**
|
||||
|
||||
jobs:
|
||||
build-deploy-api:
|
||||
name: build-deploy-api
|
||||
uses: ./.github/workflows/api.yaml
|
||||
with:
|
||||
calling_workflow: dev
|
||||
secrets: inherit
|
||||
|
||||
build-deploy-app:
|
||||
name: build-deploy-app
|
||||
uses: ./.github/workflows/app.yaml
|
||||
with:
|
||||
calling_workflow: dev
|
||||
deployment_environment: development
|
||||
secrets: inherit
|
||||
42
.github/workflows/main.yaml
vendored
Normal file
42
.github/workflows/main.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Main
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-api:
|
||||
name: build-api
|
||||
uses: ./.github/workflows/api_build.yaml
|
||||
with:
|
||||
environment_name: test
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
deploy-api:
|
||||
name: deploy-api
|
||||
needs: build-api
|
||||
uses: ./.github/workflows/deploy.yaml
|
||||
with:
|
||||
app_name: flying-app
|
||||
environment_name: test
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
build-app:
|
||||
name: build-app
|
||||
uses: ./.github/workflows/app_build.yaml
|
||||
with:
|
||||
environment_name: test
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
deploy-app:
|
||||
name: deploy-app
|
||||
needs: build-app
|
||||
uses: ./.github/workflows/deploy.yaml
|
||||
with:
|
||||
app_name: flying-app
|
||||
environment_name: test
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
36
.github/workflows/pull_request.yaml
vendored
36
.github/workflows/pull_request.yaml
vendored
@@ -2,36 +2,20 @@ name: Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-deploy-api:
|
||||
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
||||
name: build-deploy-api
|
||||
uses: ./.github/workflows/api.yaml
|
||||
build-api:
|
||||
name: build-api
|
||||
uses: ./.github/workflows/api_build.yaml
|
||||
with:
|
||||
calling_workflow: dev
|
||||
environment_name: pull_request
|
||||
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
|
||||
build-app:
|
||||
name: build-app
|
||||
uses: ./.github/workflows/app_build.yaml
|
||||
with:
|
||||
calling_workflow: dev
|
||||
deployment_environment: staging
|
||||
environment_name: pull_request
|
||||
version_number: ${{ github.run_id }}
|
||||
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:
|
||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
|
||||
action: 'close'
|
||||
|
||||
42
.github/workflows/tag.yaml
vendored
Normal file
42
.github/workflows/tag.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Tag
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build-api:
|
||||
name: build-api
|
||||
uses: ./.github/workflows/api_build.yaml
|
||||
with:
|
||||
environment_name: prod
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
deploy-api:
|
||||
name: deploy-api
|
||||
needs: build-api
|
||||
uses: ./.github/workflows/deploy.yaml
|
||||
with:
|
||||
app_name: flying-api
|
||||
environment_name: prod
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
build-app:
|
||||
name: build-app
|
||||
uses: ./.github/workflows/app_build.yaml
|
||||
with:
|
||||
environment_name: prod
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
|
||||
deploy-app:
|
||||
name: deploy-app
|
||||
needs: build-app
|
||||
uses: ./.github/workflows/deploy.yaml
|
||||
with:
|
||||
app_name: flying-app
|
||||
environment_name: prod
|
||||
version_number: ${{ github.run_id }}
|
||||
secrets: inherit
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,4 +2,7 @@
|
||||
node_modules
|
||||
**/.env
|
||||
**/.env.*
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.dapr
|
||||
**/**/secrets.json
|
||||
.prod
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
# . "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
# npx lint-staged
|
||||
|
||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# FROM --platform=linux/amd64 node:18-alpine AS base
|
||||
# ENV PNPM_HOME="/pnpm"
|
||||
# ENV PATH="$PNPM_HOME:$PATH"
|
||||
# RUN corepack enable
|
||||
|
||||
# FROM base AS api
|
||||
# COPY /api/dist /app/dist
|
||||
# COPY /api/node_modules /app/node_modules
|
||||
# WORKDIR /app
|
||||
# EXPOSE 3000
|
||||
# # CMD ["node", "dist/main.js"]
|
||||
# ENTRYPOINT ["tail", "-f", "/dev/null"]
|
||||
|
||||
# FROM base AS app
|
||||
# COPY /app/dist /app/dist
|
||||
# WORKDIR /app
|
||||
# RUN npm i -g serve
|
||||
# EXPOSE 8080
|
||||
# CMD [ "serve", "-s", "/dist", "-p", "8080" ]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
FROM node:18-slim AS base
|
||||
|
||||
FROM base AS api
|
||||
WORKDIR api
|
||||
COPY ./.prod/api .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/main.js"]
|
||||
# ENTRYPOINT ["tail", "-f", "/dev/null"]
|
||||
|
||||
FROM base AS app
|
||||
WORKDIR /app
|
||||
COPY ./.prod/app .
|
||||
RUN npm i -g serve
|
||||
EXPOSE 8080
|
||||
CMD [ "serve", "-s", "dist", "-p", "8080" ]
|
||||
@@ -1,3 +0,0 @@
|
||||
local.settings.json
|
||||
node_modules
|
||||
test
|
||||
@@ -1,7 +0,0 @@
|
||||
*.js.map
|
||||
*.ts
|
||||
.git*
|
||||
.vscode
|
||||
local.settings.json
|
||||
test
|
||||
tsconfig.json
|
||||
@@ -1,11 +0,0 @@
|
||||
# To enable ssh & remote debugging on app service change the base image to the one below
|
||||
# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice
|
||||
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
||||
|
||||
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
|
||||
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
|
||||
|
||||
COPY . /home/site/wwwroot
|
||||
|
||||
RUN cd /home/site/wwwroot && \
|
||||
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"
|
||||
}
|
||||
9146
api/package-lock.json
generated
9146
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "api",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.22",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -16,23 +16,25 @@
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"start:azure": "npm run build && func host start"
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/app-configuration": "^1.8.0",
|
||||
"@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/azure-database": "^3.0.0",
|
||||
"@nestjs/azure-func-http": "^0.10.0",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.2",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@noahspan/noahspan-modules": "^0.4.7",
|
||||
"@noahspan/azure-database": "^3.1.2",
|
||||
"@schematics/angular": "^17.3.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express-jwt": "^8.5.1",
|
||||
"passport-azure-ad": "^4.3.5",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"uuid": "^10.0.0"
|
||||
@@ -51,11 +53,13 @@
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
|
||||
@@ -3,54 +3,23 @@ import {
|
||||
Get,
|
||||
Headers,
|
||||
Query,
|
||||
Res,
|
||||
StreamableFile
|
||||
StreamableFile,
|
||||
UseGuards
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
AppConfigService,
|
||||
MsGraphService,
|
||||
MsGraphClient
|
||||
} from '@noahspan/noahspan-modules';
|
||||
import { FeatureFlagValue } from '@azure/app-configuration';
|
||||
import { Public } from '@noahspan/noahspan-modules';
|
||||
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||
import { MsGraphService } from './msGraph/ms-graph.service'
|
||||
import { Person } from '@microsoft/microsoft-graph-types';
|
||||
import { AppService } from './app.service';
|
||||
import { createReadStream } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { arrayBuffer } from 'stream/consumers';
|
||||
import type { Response } from 'express';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Controller()
|
||||
@UseGuards(AuthGuard('azure-ad'))
|
||||
export class AppController {
|
||||
constructor(
|
||||
private readonly appService: AppService,
|
||||
private readonly appConfigService: AppConfigService,
|
||||
private readonly msGraphService: MsGraphService
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@Get('featureFlags')
|
||||
async getFeatureFlags(
|
||||
@Query() query: any
|
||||
): Promise<{ key: string; enabled: boolean }[]> {
|
||||
try {
|
||||
const featureFlagKeys: string[] =
|
||||
query.keys && query.keys.toString().includes(';')
|
||||
? query.keys.split(';')
|
||||
: [query.keys];
|
||||
const featureFlagLabel: string = query.label;
|
||||
const featureFlags: { key: string; enabled: boolean }[] =
|
||||
await this.appConfigService.getFeatureFlags(
|
||||
featureFlagKeys,
|
||||
featureFlagLabel
|
||||
);
|
||||
|
||||
return featureFlags;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
@Get('userPhoto')
|
||||
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
|
||||
try {
|
||||
@@ -106,7 +75,6 @@ export class AppController {
|
||||
}
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('hello')
|
||||
async getHello(): Promise<string> {
|
||||
return this.appService.getHello();
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import {
|
||||
AppConfigModule,
|
||||
AuthModule,
|
||||
AuthGuard,
|
||||
MsGraphModule
|
||||
} from '@noahspan/noahspan-modules';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { LogbookModule } from './logbook/logbook.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { MsGraphModule } from './msGraph/ms-graph.module';
|
||||
import { FeatureFlagModule } from './featureFlag/feature-flag.module'
|
||||
import { LogModule } from './log/log.module';
|
||||
import { PilotModule } from './pilot/pilot.module';
|
||||
import { APP_FILTER } from '@nestjs/core';
|
||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import configuration from './config/configuration';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
AppConfigModule.register({
|
||||
url: process.env.APP_CONFIG_URL,
|
||||
tenantId: process.env.TENANT_ID,
|
||||
clientId: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET
|
||||
AuthModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
clientId: configService.get<string>('clientId'),
|
||||
clientSecret: configService.get<string>('clientSecret'),
|
||||
tenantId: configService.get<string>('tenantId')
|
||||
};
|
||||
},
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
AuthModule.register({
|
||||
tenantId: process.env.TENANT_ID,
|
||||
clientId: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET
|
||||
ConfigModule.forRoot({
|
||||
load: [configuration]
|
||||
}),
|
||||
MsGraphModule.register({
|
||||
tenantId: process.env.TENANT_ID,
|
||||
clientId: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET
|
||||
FeatureFlagModule,
|
||||
LogModule,
|
||||
MsGraphModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
clientId: configService.get<string>('clientId'),
|
||||
clientSecret: configService.get<string>('clientSecret'),
|
||||
tenantId: configService.get<string>('tenantId')
|
||||
};
|
||||
},
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
LogbookModule,
|
||||
PilotModule
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthGuard
|
||||
},
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: HttpExceptionFilter
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
||||
// import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
||||
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||
import { MsGraphService } from './msGraph/ms-graph.service';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
constructor(private readonly msGraphService: MsGraphService) {}
|
||||
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
return JSON.stringify(process.env);
|
||||
}
|
||||
|
||||
async getPersonSearchResults(
|
||||
|
||||
5
api/src/auth/auth.interface.ts
Normal file
5
api/src/auth/auth.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface AuthModuleOptions {
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
4
api/src/auth/auth.module-definition.ts
Normal file
4
api/src/auth/auth.module-definition.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
||||
import { AuthModuleOptions } from './auth.interface';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<AuthModuleOptions>().build()
|
||||
14
api/src/auth/auth.module.ts
Normal file
14
api/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { AzureAdStrategy } from './auth.strategy';
|
||||
import { ConfigurableModuleClass } from './auth.module-definition';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PassportModule.register({
|
||||
defaultStrategy: 'azure-ad'
|
||||
})
|
||||
],
|
||||
providers: [AzureAdStrategy]
|
||||
})
|
||||
export class AuthModule extends ConfigurableModuleClass {}
|
||||
26
api/src/auth/auth.strategy.ts
Normal file
26
api/src/auth/auth.strategy.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { AuthModuleOptions } from './auth.interface'
|
||||
import { MODULE_OPTIONS_TOKEN } from "./auth.module-definition";
|
||||
import { BearerStrategy } from 'passport-azure-ad'
|
||||
|
||||
@Injectable()
|
||||
export class AzureAdStrategy extends PassportStrategy(
|
||||
BearerStrategy,
|
||||
'azure-ad'
|
||||
) {
|
||||
constructor(@Inject(MODULE_OPTIONS_TOKEN) authModuleOptions: AuthModuleOptions) {
|
||||
console.log(authModuleOptions)
|
||||
super({
|
||||
identityMetadata: `https://login.microsoftonline.com/${authModuleOptions.tenantId}/.well-known/openid-configuration`,
|
||||
clientID: authModuleOptions.clientId,
|
||||
audience: `api://${authModuleOptions.clientId}`,
|
||||
loggingLevel: 'info',
|
||||
loggingNoPII: false
|
||||
})
|
||||
}
|
||||
|
||||
async validate(data: any): Promise<any> {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
6
api/src/config/configuration.ts
Normal file
6
api/src/config/configuration.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default () => ({
|
||||
azureStorageConnectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
|
||||
clientId: process.env.CLIENT_ID,
|
||||
clientSecret: process.env.CLIENT_SECRET,
|
||||
tenantId: process.env.TENANT_ID
|
||||
})
|
||||
@@ -6,4 +6,4 @@ export class CustomError extends Error {
|
||||
this.name = name;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
api/src/featureFlag/feature-flag.controller.ts
Normal file
41
api/src/featureFlag/feature-flag.controller.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
Param,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { FeatureFlagService } from './feature-flag.service';
|
||||
import { CustomError } from '../error/customError';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Controller('featureFlags')
|
||||
@UseGuards(AuthGuard('azure-ad'))
|
||||
export class FeatureFlagController {
|
||||
constructor(private readonly featureFlagService: FeatureFlagService) {}
|
||||
|
||||
@Get(':partitionKey/:rowKey')
|
||||
async find(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string
|
||||
) {
|
||||
try {
|
||||
return await this.featureFlagService.find(partitionKey, rowKey);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Get()
|
||||
async findAll() {
|
||||
try {
|
||||
return await this.featureFlagService.findAll();
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
api/src/featureFlag/feature-flag.dto.ts
Normal file
5
api/src/featureFlag/feature-flag.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class FeatureFlagDto {
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
active: string;
|
||||
}
|
||||
7
api/src/featureFlag/feature-flag.entity.ts
Normal file
7
api/src/featureFlag/feature-flag.entity.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { EntityString } from '@noahspan/azure-database';
|
||||
|
||||
export class FeatureFlag {
|
||||
@EntityString() partitionKey: string;
|
||||
@EntityString() rowKey: string;
|
||||
@EntityString() active: string;
|
||||
}
|
||||
27
api/src/featureFlag/feature-flag.module.ts
Normal file
27
api/src/featureFlag/feature-flag.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FeatureFlagController } from './feature-flag.controller';
|
||||
import { FeatureFlagService } from './feature-flag.service';
|
||||
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { FeatureFlag } from './feature-flag.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AzureTableStorageModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
connectionString: configService.get<string>('azureStorageConnectionString')
|
||||
};
|
||||
},
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
AzureTableStorageModule.forFeature(FeatureFlag, {
|
||||
createTableIfNotExists: false,
|
||||
table: 'featureFlags'
|
||||
}),
|
||||
],
|
||||
controllers: [FeatureFlagController],
|
||||
providers: [FeatureFlagService]
|
||||
})
|
||||
export class FeatureFlagModule {}
|
||||
18
api/src/featureFlag/feature-flag.service.ts
Normal file
18
api/src/featureFlag/feature-flag.service.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||
import { FeatureFlag } from './feature-flag.entity';
|
||||
|
||||
@Injectable()
|
||||
export class FeatureFlagService {
|
||||
constructor(
|
||||
@InjectRepository(FeatureFlag) private readonly featureFlagRepository: Repository<FeatureFlag>
|
||||
) {}
|
||||
|
||||
async find(partitionKey: string, rowKey: string): Promise<FeatureFlag> {
|
||||
return await this.featureFlagRepository.find(partitionKey, rowKey);
|
||||
}
|
||||
|
||||
async findAll(): Promise<FeatureFlag[]> {
|
||||
return await this.featureFlagRepository.findAll();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
const status = excpetion.getStatus();
|
||||
console.log(excpetion.cause);
|
||||
|
||||
response.status(status).json({
|
||||
name: excpetion.cause,
|
||||
|
||||
97
api/src/log/log.controller.ts
Normal file
97
api/src/log/log.controller.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards
|
||||
} from '@nestjs/common';
|
||||
import { LogDto } from './log.dto';
|
||||
import { Log } from './log.entity';
|
||||
import { LogService } from './log.service';
|
||||
import { CustomError } from '../error/customError';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
|
||||
@Controller('logs')
|
||||
@UseGuards(AuthGuard('azure-ad'))
|
||||
export class LogController {
|
||||
constructor(private readonly logService: LogService) {}
|
||||
|
||||
@Get(':partitionKey/:rowKey')
|
||||
async find(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string
|
||||
): Promise<Log> {
|
||||
try {
|
||||
return await this.logService.find(partitionKey, rowKey);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
// @Public()
|
||||
@Get()
|
||||
async findAll(): Promise<Log[]> {
|
||||
try {
|
||||
return await this.logService.findAll();
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() logDto: LogDto): Promise<Log> {
|
||||
try {
|
||||
const log = new Log();
|
||||
|
||||
Object.assign(log, logDto);
|
||||
|
||||
return await this.logService.create(log);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Put(':partitionKey/:rowKey')
|
||||
async update(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string,
|
||||
@Body() logDto: LogDto
|
||||
): Promise<Log> {
|
||||
try {
|
||||
const log = new Log();
|
||||
|
||||
Object.assign(log, logDto);
|
||||
|
||||
return await this.logService.update(partitionKey, rowKey, log);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':partitionKey/:rowKey')
|
||||
async delete(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
return await this.logService.delete(partitionKey, rowKey);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export class LogbookDto {
|
||||
export class LogDto {
|
||||
pilotId: string;
|
||||
pilotName: string;
|
||||
date: string;
|
||||
@@ -1,4 +1,4 @@
|
||||
export class LogbookEntity {
|
||||
export class Log {
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
pilotId: string;
|
||||
27
api/src/log/log.module.ts
Normal file
27
api/src/log/log.module.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LogController } from './log.controller';
|
||||
import { LogService } from './log.service';
|
||||
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||
import { Log } from './log.entity';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AzureTableStorageModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
connectionString: configService.get<string>('azureStorageConnectionString')
|
||||
};
|
||||
},
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
AzureTableStorageModule.forFeature(Log, {
|
||||
createTableIfNotExists: false,
|
||||
table: 'logs'
|
||||
}),
|
||||
],
|
||||
controllers: [LogController],
|
||||
providers: [LogService]
|
||||
})
|
||||
export class LogModule {}
|
||||
34
api/src/log/log.service.ts
Normal file
34
api/src/log/log.service.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||
import { Log } from './log.entity';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Injectable()
|
||||
export class LogService {
|
||||
constructor(
|
||||
@InjectRepository(Log) private readonly logRepository: Repository<Log>
|
||||
) {}
|
||||
|
||||
async find(partitionKey: string, rowKey: string): Promise<Log> {
|
||||
return await this.logRepository.find(partitionKey, rowKey);
|
||||
}
|
||||
|
||||
async findAll(): Promise<Log[]> {
|
||||
return await this.logRepository.findAll();
|
||||
}
|
||||
|
||||
async create(log: Log): Promise<Log> {
|
||||
log.partitionKey = 'log';
|
||||
log.rowKey = uuidv4();
|
||||
|
||||
return await this.logRepository.create(log);
|
||||
}
|
||||
|
||||
async update(partitionKey: string, rowKey: string, log: Log): Promise<Log> {
|
||||
return await this.logRepository.update(partitionKey, rowKey, log);
|
||||
}
|
||||
|
||||
async delete(partitionKey: string, rowKey: string): Promise<void> {
|
||||
await this.logRepository.delete(partitionKey, rowKey);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -3,13 +3,16 @@ import { AppModule } from './app.module';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||
import { InternalServerErrorException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
async function bootstrap() {
|
||||
const httpService = new HttpService();
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.enableCors();
|
||||
app.setGlobalPrefix('api');
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
||||
httpService.axiosRef.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
@@ -20,6 +23,7 @@ async function bootstrap() {
|
||||
throw new InternalServerErrorException();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
|
||||
5
api/src/msGraph/ms-graph.interface.ts
Normal file
5
api/src/msGraph/ms-graph.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface MsGraphModuleOptions {
|
||||
tenantId: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
4
api/src/msGraph/ms-graph.module-definition.ts
Normal file
4
api/src/msGraph/ms-graph.module-definition.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { ConfigurableModuleBuilder } from '@nestjs/common';
|
||||
import { MsGraphModuleOptions } from './ms-graph.interface';
|
||||
|
||||
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<MsGraphModuleOptions>().build()
|
||||
9
api/src/msGraph/ms-graph.module.ts
Normal file
9
api/src/msGraph/ms-graph.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MsGraphService } from './ms-graph.service';
|
||||
import { ConfigurableModuleClass } from './ms-graph.module-definition';
|
||||
|
||||
@Module({
|
||||
providers: [MsGraphService],
|
||||
exports: [MsGraphService]
|
||||
})
|
||||
export class MsGraphModule extends ConfigurableModuleClass {}
|
||||
41
api/src/msGraph/ms-graph.service.ts
Normal file
41
api/src/msGraph/ms-graph.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { MsGraphModuleOptions } from './ms-graph.interface';
|
||||
import { Client } from '@microsoft/microsoft-graph-client';
|
||||
import { AuthenticationResult, ConfidentialClientApplication, OnBehalfOfRequest } from '@azure/msal-node';
|
||||
import { MODULE_OPTIONS_TOKEN } from './ms-graph.module-definition';
|
||||
|
||||
@Injectable()
|
||||
export class MsGraphService {
|
||||
constructor(@Inject(MODULE_OPTIONS_TOKEN) private msGraphModuleOptions: MsGraphModuleOptions) {}
|
||||
|
||||
async getMsGraphAuth(accessToken: string, scopes: string[]): Promise<string> {
|
||||
try {
|
||||
const oboRequest: OnBehalfOfRequest = {
|
||||
oboAssertion: accessToken,
|
||||
scopes: scopes
|
||||
}
|
||||
const cca = new ConfidentialClientApplication({
|
||||
auth: {
|
||||
clientId: this.msGraphModuleOptions.clientId,
|
||||
clientSecret: this.msGraphModuleOptions.clientSecret,
|
||||
authority: `https://login.microsoftonline.com/${this.msGraphModuleOptions.tenantId}`
|
||||
}
|
||||
});
|
||||
const authenticationResult: AuthenticationResult = await cca.acquireTokenOnBehalfOf(oboRequest);
|
||||
|
||||
return authenticationResult.accessToken
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
async getMsGraphClientDelegated(accessToken): Promise<Client> {
|
||||
const client = await Client.init({
|
||||
authProvider: (done) => {
|
||||
done(null, accessToken);
|
||||
}
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
||||
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||
import { Certificate } from './certificate.entity';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InjectRepository, Repository } from '@nestjs/azure-database';
|
||||
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endorsement } from './endorsement.entity';
|
||||
|
||||
|
||||
@@ -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,117 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PilotInfoDto } from './pilot-info.dto';
|
||||
import { PilotInfoEntity } from './pilot-info.entity';
|
||||
import { TableClient, TableService } from '@noahspan/noahspan-modules';
|
||||
import { odata, RestError, TableInsertEntityHeaders } from '@azure/data-tables';
|
||||
import { CustomError } from '../../customError/CustomError';
|
||||
|
||||
@Injectable()
|
||||
export class PilotInfoService {
|
||||
private readonly partitionKey: string = 'info';
|
||||
|
||||
constructor(private readonly tableService: TableService) {}
|
||||
|
||||
async find(pilotId: string): Promise<PilotInfoEntity> {
|
||||
try {
|
||||
const client: TableClient =
|
||||
await this.tableService.getTableClient('Pilots');
|
||||
const entities = await client.listEntities({
|
||||
queryOptions: {
|
||||
filter: odata`PartitionKey eq 'pilot' and RowKey eq '${pilotId}'`
|
||||
}
|
||||
});
|
||||
let pilot: PilotInfoEntity;
|
||||
console.log(entities);
|
||||
for await (const entity of entities) {
|
||||
pilot = {
|
||||
partitionKey: entity.partitionKey,
|
||||
rowKey: entity.rowKey,
|
||||
id: entity.id.toString(),
|
||||
name: entity.name.toString(),
|
||||
address: entity.address.toString(),
|
||||
city: entity.city.toString(),
|
||||
state: entity.state.toString(),
|
||||
postalCode: entity.postalCode.toString(),
|
||||
email: entity.email.toString(),
|
||||
phone: entity.phone.toString()
|
||||
};
|
||||
}
|
||||
|
||||
return pilot;
|
||||
} catch (error) {
|
||||
const restError: RestError = error as RestError;
|
||||
|
||||
throw new CustomError(
|
||||
restError.details['odataError']['message']['value'],
|
||||
restError.details['odataError']['code'],
|
||||
restError.statusCode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findAll(): Promise<PilotInfoEntity[]> {
|
||||
try {
|
||||
const client: TableClient =
|
||||
await this.tableService.getTableClient('Pilots');
|
||||
const entities = await client.listEntities({
|
||||
queryOptions: { filter: odata`PartitionKey eq 'pilot'` }
|
||||
});
|
||||
const pilots: PilotInfoEntity[] = [];
|
||||
|
||||
for await (const entity of entities) {
|
||||
const pilot: PilotInfoEntity = {
|
||||
partitionKey: entity.partitionKey,
|
||||
rowKey: entity.rowKey,
|
||||
id: entity.id.toString(),
|
||||
name: entity.name.toString()
|
||||
};
|
||||
|
||||
pilots.push(pilot);
|
||||
}
|
||||
|
||||
return pilots;
|
||||
} catch (error) {
|
||||
const restError: RestError = error as RestError;
|
||||
|
||||
throw new CustomError(
|
||||
restError.details['odataError']['message']['value'],
|
||||
restError.details['odataError']['code'],
|
||||
restError.statusCode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async create(pilotInfoData: PilotInfoDto): Promise<TableInsertEntityHeaders> {
|
||||
try {
|
||||
const client: TableClient =
|
||||
await this.tableService.getTableClient('Pilots');
|
||||
const pilotInfo: PilotInfoEntity = new PilotInfoEntity();
|
||||
|
||||
Object.assign(pilotInfo, pilotInfoData);
|
||||
pilotInfo.partitionKey = 'pilot';
|
||||
pilotInfo.rowKey = pilotInfo.id;
|
||||
console.log(pilotInfo);
|
||||
return await client.createEntity(pilotInfo);
|
||||
} catch (error) {
|
||||
const restError: RestError = error as RestError;
|
||||
console.log(restError);
|
||||
throw new CustomError(
|
||||
restError.details['odataError']['message']['value'],
|
||||
restError.details['odataError']['code'],
|
||||
restError.statusCode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// async update(rowKey: string, pilotInfo: PilotInfo): Promise<PilotInfo> {
|
||||
// return await this.pilotInfoRepository.update(
|
||||
// this.partitionKey,
|
||||
// rowKey,
|
||||
// pilotInfo
|
||||
// );
|
||||
// }
|
||||
|
||||
// async delete(rowKey: string): Promise<void> {
|
||||
// await this.pilotInfoRepository.delete(this.partitionKey, rowKey);
|
||||
// }
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
||||
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||
import { Medical } from './medical.entity';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -1,71 +1,95 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
Param,
|
||||
Post,
|
||||
UseInterceptors
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { PilotInfoService } from './info/pilot-info.service';
|
||||
import { PilotInfoDto } from './info/pilot-info.dto';
|
||||
import { TableInsertEntityHeaders } from '@azure/data-tables';
|
||||
import { CustomError } from '../customError/CustomError';
|
||||
import { PilotInfoEntity } from './info/pilot-info.entity';
|
||||
import { PilotInterceptor } from 'src/pilot/interceptors/pilot.interceptor';
|
||||
import { Public } from '@noahspan/noahspan-modules';
|
||||
import { PilotDto } from './pilot.dto';
|
||||
import { Pilot } from './pilot.entity';
|
||||
import { PilotService } from './pilot.service';
|
||||
import { CustomError } from '../error/customError';
|
||||
import { AuthGuard } from '@nestjs/passport'
|
||||
|
||||
@Controller('pilots')
|
||||
@UseGuards(AuthGuard('azure-ad'))
|
||||
export class PilotController {
|
||||
constructor(private readonly pilotInfoService: PilotInfoService) {}
|
||||
constructor(private readonly pilotService: PilotService) {}
|
||||
|
||||
@Get(':pilotId')
|
||||
@Public()
|
||||
@UseInterceptors(PilotInterceptor)
|
||||
async find(@Param() params: any): Promise<PilotInfoEntity> {
|
||||
@Get(':partitionKey/:rowKey')
|
||||
async find(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string
|
||||
) {
|
||||
try {
|
||||
const pilot: PilotInfoEntity = await this.pilotInfoService.find(
|
||||
params.pilotId
|
||||
);
|
||||
|
||||
return pilot;
|
||||
return await this.pilotService.find(partitionKey, rowKey);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode, {
|
||||
cause: customError.name
|
||||
});
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Public()
|
||||
@UseInterceptors(PilotInterceptor)
|
||||
async findAll(): Promise<PilotInfoEntity[]> {
|
||||
async findAll() {
|
||||
try {
|
||||
const pilots: PilotInfoEntity[] = await this.pilotInfoService.findAll();
|
||||
|
||||
return pilots;
|
||||
return await this.pilotService.findAll();
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode, {
|
||||
cause: customError.name
|
||||
});
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
|
||||
async create(@Body() pilotDto: PilotDto) {
|
||||
try {
|
||||
const response: TableInsertEntityHeaders =
|
||||
await this.pilotInfoService.create(pilotInfoData);
|
||||
const pilot = new Pilot();
|
||||
|
||||
Object.assign(pilot, pilotDto);
|
||||
|
||||
return await this.pilotService.create(pilot);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode, {
|
||||
cause: customError.name
|
||||
});
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Put(':partitionKey/:rowKey')
|
||||
async update(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string,
|
||||
@Body() pilotDto: PilotDto
|
||||
) {
|
||||
try {
|
||||
const pilot = new Pilot();
|
||||
|
||||
Object.assign(pilot, pilotDto);
|
||||
|
||||
return await this.pilotService.update(partitionKey, rowKey, pilot);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':partitionKey/:rowKey')
|
||||
async delete(
|
||||
@Param('partitionKey') partitionKey: string,
|
||||
@Param('rowKey') rowKey: string
|
||||
) {
|
||||
try {
|
||||
return await this.pilotService.delete(partitionKey, rowKey);
|
||||
} catch (error) {
|
||||
const customError = error as CustomError;
|
||||
|
||||
throw new HttpException(customError.message, customError.statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export class PilotInfoDto {
|
||||
export class PilotDto {
|
||||
partitionKey: string;
|
||||
rowKey: string;
|
||||
id: string;
|
||||
name: 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,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PilotController } from './pilot.controller';
|
||||
import { TableModule } from '@noahspan/noahspan-modules';
|
||||
import { PilotInfoService } from './info/pilot-info.service';
|
||||
import { PilotService } from './pilot.service';
|
||||
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||
import { Pilot } from './pilot.entity';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TableModule.register({
|
||||
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
|
||||
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
|
||||
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
|
||||
allowInsecureConnection: Boolean(
|
||||
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
|
||||
)
|
||||
})
|
||||
AzureTableStorageModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => {
|
||||
return {
|
||||
connectionString: configService.get<string>('azureStorageConnectionString')
|
||||
};
|
||||
},
|
||||
inject: [ConfigService]
|
||||
}),
|
||||
AzureTableStorageModule.forFeature(Pilot, {
|
||||
createTableIfNotExists: false,
|
||||
table: 'pilots'
|
||||
}),
|
||||
],
|
||||
controllers: [PilotController],
|
||||
providers: [PilotInfoService]
|
||||
providers: [PilotService]
|
||||
})
|
||||
export class PilotModule {}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
{
|
||||
"name": "@noahspan/flying-client",
|
||||
"name": "app",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.19",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"serve": "serve -s dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^3.17.0",
|
||||
"@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",
|
||||
"@azure/msal-browser": "^4.0.1",
|
||||
"@azure/msal-react": "^3.0.1",
|
||||
"@noahspan/noahspan-components": "^0.8.9",
|
||||
"axios": "^1.7.2",
|
||||
"framer-motion": "^11.1.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.51.4",
|
||||
"react-router-dom": "^6.23.0"
|
||||
},
|
||||
@@ -29,13 +26,13 @@
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
@@ -7,8 +7,14 @@ import { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useHttpClient } from './hooks/httpClient/UseHttpClient';
|
||||
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag';
|
||||
import SiteNav from './components/siteNav/SiteNav';
|
||||
import { IPublicClientApplication } from '@azure/msal-browser';
|
||||
import { MsalProvider } from '@azure/msal-react';
|
||||
|
||||
const App: React.FC<unknown> = () => {
|
||||
interface AppProps {
|
||||
pca: IPublicClientApplication
|
||||
}
|
||||
|
||||
const App: React.FC<AppProps> = ({ pca }: AppProps) => {
|
||||
const httpClient: AxiosInstance = useHttpClient();
|
||||
const appContext = useAppContext();
|
||||
|
||||
@@ -36,7 +42,7 @@ const App: React.FC<unknown> = () => {
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MsalProvider instance={pca}>
|
||||
<SiteNav />
|
||||
<Routes>
|
||||
{/* {useFeatureFlag('flying-pilots')?.enabled && ( */}
|
||||
@@ -44,8 +50,8 @@ const App: React.FC<unknown> = () => {
|
||||
{/* )} */}
|
||||
<Route path="/" element={<Logbook />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</MsalProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
70
app/src/auth/msalConfig.ts
Normal file
70
app/src/auth/msalConfig.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
import { LogLevel } from '@azure/msal-browser';
|
||||
|
||||
/**
|
||||
* Configuration object to be passed to MSAL instance on creation.
|
||||
* For a full list of MSAL.js configuration parameters, visit:
|
||||
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
|
||||
*/
|
||||
|
||||
export const msalConfig = {
|
||||
auth: {
|
||||
clientId: import.meta.env.VITE_CLIENT_ID, // This is the ONLY mandatory field that you need to supply.
|
||||
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, // Replace the placeholder with your tenant subdomain
|
||||
redirectUri: import.meta.env.VITE_REDIRECT_URL, // Points to window.location.origin. You must register this URI on Microsoft Entra admin center/App Registration.
|
||||
postLogoutRedirectUri: '/', // Indicates the page to navigate after logout.
|
||||
navigateToLoginRequestUrl: false, // If "true", will navigate back to the original request location before processing the auth code response.
|
||||
},
|
||||
cache: {
|
||||
cacheLocation: 'sessionStorage', // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO between tabs.
|
||||
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
|
||||
},
|
||||
system: {
|
||||
loggerOptions: {
|
||||
loggerCallback: (level: any, message: any, containsPii: any) => {
|
||||
if (containsPii) {
|
||||
return;
|
||||
}
|
||||
switch (level) {
|
||||
case LogLevel.Error:
|
||||
console.error(message);
|
||||
return;
|
||||
case LogLevel.Info:
|
||||
console.info(message);
|
||||
return;
|
||||
case LogLevel.Verbose:
|
||||
console.debug(message);
|
||||
return;
|
||||
case LogLevel.Warning:
|
||||
console.warn(message);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Scopes you add here will be prompted for user consent during sign-in.
|
||||
* By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
|
||||
* For more information about OIDC scopes, visit:
|
||||
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
|
||||
*/
|
||||
export const loginRequest = {
|
||||
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`],
|
||||
};
|
||||
|
||||
/**
|
||||
* An optional silentRequest object can be used to achieve silent SSO
|
||||
* between applications by providing a "login_hint" property.
|
||||
*/
|
||||
// export const silentRequest = {
|
||||
// scopes: ["openid", "profile"],
|
||||
// loginHint: "example@domain.net"
|
||||
// };
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
|
||||
export interface ILogbookEntryFormProps {
|
||||
export interface ILogFormProps {
|
||||
entryId?: string;
|
||||
isDrawerOpen: boolean;
|
||||
mode: FormMode;
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface ILogbookEntryFormState {
|
||||
export interface ILogFormState {
|
||||
error: string | undefined;
|
||||
isDisabled: boolean;
|
||||
isLoading: boolean;
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
XmarkIcon
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||
import { ILogbookEntryFormProps } from './ILogbookEntryFormProps';
|
||||
import { ILogFormProps } from './ILogFormProps';
|
||||
import { initialState, reducer } from './reducer';
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
@@ -26,7 +26,7 @@ import { useIsAuthenticated } from '@azure/msal-react';
|
||||
import { FormMode } from '../../enums/formMode';
|
||||
import { usePilots } from '../../hooks/pilots/UsePilots';
|
||||
|
||||
const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
||||
const LogForm: React.FC<ILogFormProps> = ({
|
||||
entryId,
|
||||
isDrawerOpen,
|
||||
mode,
|
||||
@@ -37,8 +37,6 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
||||
const { getAccessToken } = useAccessToken();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const defaultValues = {
|
||||
partitionKey: '',
|
||||
rowKey: '',
|
||||
pilotId: '',
|
||||
pilotName: '',
|
||||
date: null,
|
||||
@@ -80,13 +78,13 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
||||
const accessToken: string = await getAccessToken();
|
||||
|
||||
if (!entryId) {
|
||||
await httpClient.post(`api/logbook`, data, {
|
||||
await httpClient.post(`api/logs`, data, {
|
||||
headers: {
|
||||
Authorization: accessToken
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await httpClient.put(`api/logbook/${entryId}`, data, {
|
||||
await httpClient.put(`api/logs/${entryId}`, data, {
|
||||
headers: {
|
||||
Authorization: accessToken
|
||||
}
|
||||
@@ -120,7 +118,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
||||
? { headers: { Authorization: await getAccessToken() } }
|
||||
: {};
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/logbook/${entryId}`,
|
||||
`api/logs/${entryId}`,
|
||||
config
|
||||
);
|
||||
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: 'SET_ERROR'; payload: string | undefined }
|
||||
@@ -7,7 +7,7 @@ type Action =
|
||||
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
||||
|
||||
export const initialState: ILogbookEntryFormState = {
|
||||
export const initialState: ILogFormState = {
|
||||
error: undefined,
|
||||
isDisabled: false,
|
||||
isLoading: true,
|
||||
@@ -16,9 +16,9 @@ export const initialState: ILogbookEntryFormState = {
|
||||
};
|
||||
|
||||
export const reducer = (
|
||||
state: ILogbookEntryFormState,
|
||||
state: ILogFormState,
|
||||
action: Action
|
||||
): ILogbookEntryFormState => {
|
||||
): ILogFormState => {
|
||||
switch (action.type) {
|
||||
case 'SET_ERROR': {
|
||||
return {
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useReducer, useState } from 'react';
|
||||
import LogbookEntryForm from '../logbookEntryForm/LogbookEntryForm';
|
||||
import { useEffect, useReducer } from 'react';
|
||||
import LogForm from '../logForm/LogForm';
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
@@ -35,11 +35,8 @@ const Logbook: React.FC<unknown> = () => {
|
||||
const config = isAuthenticated
|
||||
? { headers: { Authorization: `${token}` } }
|
||||
: {};
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/logbook`,
|
||||
config
|
||||
);
|
||||
|
||||
const response: AxiosResponse = await httpClient.get(`api/logs`, config);
|
||||
console.log(response)
|
||||
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
@@ -98,7 +95,7 @@ const Logbook: React.FC<unknown> = () => {
|
||||
? { headers: { Authorization: `${token}` } }
|
||||
: {};
|
||||
|
||||
await httpClient.delete(`api/logbook/${state.selectedEntryId}`, config);
|
||||
await httpClient.delete(`api/logs/${state.selectedEntryId}`, config);
|
||||
|
||||
dispatch({
|
||||
type: 'SET_DELETE',
|
||||
@@ -418,20 +415,24 @@ const Logbook: React.FC<unknown> = () => {
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<LogbookEntryForm
|
||||
entryId={state.selectedEntryId}
|
||||
isDrawerOpen={state.isFormOpen}
|
||||
mode={state.formMode}
|
||||
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
||||
/>
|
||||
<ConfirmationDialog
|
||||
contentText="Are you sure you want to delete the logbook entry?"
|
||||
isLoading={state.isConfirmDialogLoading}
|
||||
isOpen={state.isConfirmDialogOpen}
|
||||
onCancel={onConfirmationDialogCancel}
|
||||
onConfirm={onConfirmationDialogConfirm}
|
||||
title="Confirm Delete"
|
||||
/>
|
||||
{state.isFormOpen && (
|
||||
<LogForm
|
||||
entryId={state.selectedEntryId}
|
||||
isDrawerOpen={state.isFormOpen}
|
||||
mode={state.formMode}
|
||||
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
||||
/>
|
||||
)}
|
||||
{state.isConfirmDialogOpen && (
|
||||
<ConfirmationDialog
|
||||
contentText="Are you sure you want to delete the logbook entry?"
|
||||
isLoading={state.isConfirmDialogLoading}
|
||||
isOpen={state.isConfirmDialogOpen}
|
||||
onCancel={onConfirmationDialogCancel}
|
||||
onConfirm={onConfirmationDialogConfirm}
|
||||
title="Confirm Delete"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
XmarkIcon
|
||||
} from '@noahspan/noahspan-components';
|
||||
import { IPilotFormProps } from './IPilotFormProps';
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||
import { useIsAuthenticated } from '@azure/msal-react';
|
||||
@@ -38,8 +38,8 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
const { getAccessToken } = useAccessToken();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const defaultValues = {
|
||||
partitionKey: '',
|
||||
rowKey: '',
|
||||
partitionKey: 'pilot',
|
||||
rowKey: 'noah@noahspannbauer.com',
|
||||
id: '',
|
||||
name: '',
|
||||
address: '',
|
||||
@@ -53,6 +53,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
defaultValues: defaultValues
|
||||
});
|
||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
|
||||
const onPeoplePickerSearch = async (
|
||||
_event: React.SyntheticEvent,
|
||||
@@ -105,24 +106,19 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
||||
setIsLoading(true);
|
||||
|
||||
const accessToken: string = await getAccessToken();
|
||||
const response: AxiosResponse = await httpClient.post(
|
||||
`api/pilots`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: accessToken
|
||||
}
|
||||
|
||||
await httpClient.post(`api/pilots`, data, {
|
||||
headers: {
|
||||
Authorization: accessToken
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (response) console.log(response);
|
||||
onOpenClose(FormMode.CANCEL);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const errResp = error.response;
|
||||
const axiosError = error as AxiosError;
|
||||
const responseData = axiosError.response?.data as any;
|
||||
|
||||
console.log(errResp?.data.message);
|
||||
} else {
|
||||
}
|
||||
console.log(responseData.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,25 @@ const Pilots: React.FC<unknown> = () => {
|
||||
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
|
||||
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
|
||||
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) {
|
||||
case FormMode.ADD:
|
||||
case FormMode.EDIT:
|
||||
@@ -49,6 +67,9 @@ const Pilots: React.FC<unknown> = () => {
|
||||
setIsDrawerOpen(true);
|
||||
break;
|
||||
case FormMode.CANCEL:
|
||||
const pilots = await getPilots();
|
||||
|
||||
setPilots(pilots);
|
||||
setPilotFormMode(mode);
|
||||
setSelectedPilotId(undefined);
|
||||
setIsDrawerOpen(false);
|
||||
@@ -73,6 +94,7 @@ const Pilots: React.FC<unknown> = () => {
|
||||
const onCloseActionMenu = () => {
|
||||
setAnchorElAction(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton onClick={onOpenActionMenu}>
|
||||
@@ -124,23 +146,17 @@ const Pilots: React.FC<unknown> = () => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const getPilots = async () => {
|
||||
const loadPilots = async () => {
|
||||
try {
|
||||
const config = isAuthenticated
|
||||
? { headers: { Authorization: await getAccessToken() } }
|
||||
: {};
|
||||
const response: AxiosResponse = await httpClient.get(
|
||||
`api/pilots`,
|
||||
config
|
||||
);
|
||||
console.log(response.data);
|
||||
setPilots(response.data);
|
||||
const pilots = await getPilots();
|
||||
|
||||
setPilots(pilots);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
getPilots();
|
||||
loadPilots();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,8 +43,8 @@ const SiteNav: React.FC<unknown> = () => {
|
||||
url: '/pilots'
|
||||
}
|
||||
];
|
||||
const handleSignIn = async () => {
|
||||
await instance.loginRedirect({
|
||||
const handleSignIn = () => {
|
||||
instance.loginRedirect({
|
||||
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
||||
});
|
||||
};
|
||||
@@ -52,6 +52,7 @@ const SiteNav: React.FC<unknown> = () => {
|
||||
instance.logoutRedirect();
|
||||
};
|
||||
const getUserProfile = async (accessToken: string): Promise<User> => {
|
||||
console.log(accessToken)
|
||||
try {
|
||||
const response: AxiosResponse = await httpClient.get(`api/userProfile`, {
|
||||
headers: {
|
||||
@@ -66,6 +67,7 @@ const SiteNav: React.FC<unknown> = () => {
|
||||
}
|
||||
};
|
||||
const getUserPhoto = async (accessToken: string): Promise<string> => {
|
||||
console.log(accessToken)
|
||||
try {
|
||||
const response: AxiosResponse = await httpClient.get(`api/userPhoto`, {
|
||||
headers: {
|
||||
@@ -98,40 +100,44 @@ const SiteNav: React.FC<unknown> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const callback = instance.addEventCallback(
|
||||
async (message: EventMessage) => {
|
||||
if (message.eventType === EventType.LOGIN_SUCCESS) {
|
||||
try {
|
||||
setLoading(true);
|
||||
// useEffect(() => {
|
||||
// const callback = instance.addEventCallback(
|
||||
// async (message: EventMessage) => {
|
||||
// if (message.eventType === EventType.LOGIN_SUCCESS) {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
|
||||
const eventPayload: EventPayloadExtended =
|
||||
message.payload as EventPayloadExtended;
|
||||
const userProfile: User = await getUserProfile(
|
||||
eventPayload.accessToken
|
||||
);
|
||||
const userPhoto = await getUserPhoto(eventPayload.accessToken);
|
||||
// const eventPayload: EventPayloadExtended =
|
||||
// message.payload as EventPayloadExtended;
|
||||
// const userProfile: User = await getUserProfile(
|
||||
// eventPayload.accessToken
|
||||
// );
|
||||
// const userPhoto = await getUserPhoto(eventPayload.accessToken);
|
||||
|
||||
appContext.dispatch({
|
||||
type: 'SET_USER_PROFILE',
|
||||
payload: userProfile
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// appContext.dispatch({
|
||||
// type: 'SET_USER_PROFILE',
|
||||
// payload: userProfile
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
return () => {
|
||||
if (callback) {
|
||||
instance.removeEventCallback(callback);
|
||||
appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} });
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
// instance.handleRedirectPromise().then((response) => {
|
||||
// console.log(response)
|
||||
// })
|
||||
|
||||
// return () => {
|
||||
// if (callback) {
|
||||
// instance.removeEventCallback(callback);
|
||||
// appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} });
|
||||
// }
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
useEffect(() => {
|
||||
const setUserProfile = async () => {
|
||||
@@ -176,4 +182,4 @@ const SiteNav: React.FC<unknown> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteNav;
|
||||
export default SiteNav;
|
||||
@@ -5,6 +5,6 @@ export const useFeatureFlag = (featureFlagKey: string) => {
|
||||
const featureFlag = appContext.state.featureFlags.find(
|
||||
(featureFlag) => featureFlag.key === featureFlagKey
|
||||
);
|
||||
|
||||
console.log(featureFlag)
|
||||
return featureFlag;
|
||||
};
|
||||
|
||||
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>
|
||||
@@ -5,27 +5,34 @@ import AppContextProvider from './context/appContext/AppContextProvider.tsx';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import './index.css';
|
||||
import '@noahspan/noahspan-components/noahspan-components.css';
|
||||
import { PublicClientApplication } from '@azure/msal-browser';
|
||||
import { AuthenticationResult, EventMessage, EventType, PublicClientApplication } from '@azure/msal-browser';
|
||||
import { MsalProvider } from '@azure/msal-react';
|
||||
import { msalConfig } from './auth/msalConfig';
|
||||
|
||||
const msalInstance: PublicClientApplication = new PublicClientApplication({
|
||||
auth: {
|
||||
clientId: import.meta.env.VITE_CLIENT_ID,
|
||||
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`,
|
||||
redirectUri: import.meta.env.VITE_REDIRECT_URL
|
||||
}
|
||||
});
|
||||
const msalInstance: PublicClientApplication = new PublicClientApplication(msalConfig);
|
||||
|
||||
msalInstance.initialize().then(() => {
|
||||
const accounts = msalInstance.getAllAccounts();
|
||||
|
||||
if (accounts.length > 0) {
|
||||
msalInstance.setActiveAccount(accounts[0]);
|
||||
}
|
||||
|
||||
msalInstance.addEventCallback((event: EventMessage) => {
|
||||
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
|
||||
const payload = event.payload as AuthenticationResult;
|
||||
const account = payload.account;
|
||||
msalInstance.setActiveAccount(account);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<MsalProvider instance={msalInstance}>
|
||||
<AppContextProvider>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</AppContextProvider>
|
||||
</MsalProvider>
|
||||
<AppContextProvider>
|
||||
<BrowserRouter>
|
||||
<App pca={msalInstance} />
|
||||
</BrowserRouter>
|
||||
</AppContextProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
});
|
||||
})
|
||||
@@ -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/
|
||||
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']
|
||||
@@ -11,17 +11,23 @@ services:
|
||||
command: 'azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose'
|
||||
|
||||
api:
|
||||
container_name: api
|
||||
container_name: flying-api
|
||||
build:
|
||||
context: ./api
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
target: api
|
||||
ports:
|
||||
- '7071:3000'
|
||||
- '3000:3000'
|
||||
env_file:
|
||||
- ./api/.env
|
||||
|
||||
# swa:
|
||||
# image: swacli/static-web-apps-cli
|
||||
# ports:
|
||||
# - "4280:4280"
|
||||
# command: "swa start "
|
||||
app:
|
||||
container_name: flying-app
|
||||
build:
|
||||
context: .
|
||||
target: app
|
||||
ports:
|
||||
- '8080:8080'
|
||||
# env_file:
|
||||
# - ./app/.env
|
||||
|
||||
|
||||
4
infrastructure/config.tf
Normal file
4
infrastructure/config.tf
Normal file
@@ -0,0 +1,4 @@
|
||||
module "environment" {
|
||||
source = "./config"
|
||||
environment = var.WORKSPACE
|
||||
}
|
||||
61
infrastructure/config/main.tf
Normal file
61
infrastructure/config/main.tf
Normal file
@@ -0,0 +1,61 @@
|
||||
locals {
|
||||
container_app_app_name = {
|
||||
test = "flying-app-test"
|
||||
prod = "flying-app-prod"
|
||||
}
|
||||
|
||||
container_app_app_container_image = {
|
||||
test = "noahspan/flying-app:v0.0.1"
|
||||
prod = "noahspan/flying-app:v0.0.1"
|
||||
}
|
||||
|
||||
container_app_app_container_name = {
|
||||
test = "flying-app-test"
|
||||
prod = "flying-app-prod"
|
||||
}
|
||||
|
||||
container_app_api_name = {
|
||||
test = "flying-api-test"
|
||||
prod = "flying-api-prod"
|
||||
}
|
||||
|
||||
container_app_api_container_image = {
|
||||
test = "noahspan/flying-api:v0.0.1"
|
||||
prod = "noahspan/flying-api:v0.0.1"
|
||||
}
|
||||
|
||||
container_app_api_container_name = {
|
||||
test = "flying-api-test"
|
||||
prod = "flying-api-prod"
|
||||
}
|
||||
|
||||
container_app_api_dapr_app_id = {
|
||||
test = "flyingapitest"
|
||||
prod = "flyingpapiprod"
|
||||
}
|
||||
|
||||
container_app_environment_name = {
|
||||
test = "flying-test"
|
||||
prod = "flying-prod"
|
||||
}
|
||||
|
||||
logbook_feature_flag_active = {
|
||||
test = "true"
|
||||
prod = "true"
|
||||
}
|
||||
|
||||
log_analytics_workspace_name = {
|
||||
test = "flying-log-analytics-workspace-test"
|
||||
prod = "flying-log-analytics-workspace-prod"
|
||||
}
|
||||
|
||||
pilots_feature_flag_active = {
|
||||
test = "true"
|
||||
prod = "true"
|
||||
}
|
||||
|
||||
storage_account_name = {
|
||||
test = "noahspanflyingtest"
|
||||
prod = "noahspanflyingprod"
|
||||
}
|
||||
}
|
||||
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 "logbook_feature_flag_active" {
|
||||
value = local.logbook_feature_flag_active[var.environment]
|
||||
}
|
||||
|
||||
output "log_analytics_workspace_name" {
|
||||
value = local.log_analytics_workspace_name[var.environment]
|
||||
}
|
||||
|
||||
output "pilots_feature_flag_active" {
|
||||
value = local.pilots_feature_flag_active[var.environment]
|
||||
}
|
||||
|
||||
output "storage_account_name" {
|
||||
value = local.storage_account_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"
|
||||
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 = 1
|
||||
|
||||
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 +0,0 @@
|
||||
resource "azurerm_log_analytics_workspace" "log_analytics_workspace" {
|
||||
name = "noahspan-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-dev"
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,15 @@
|
||||
data "azurerm_dns_zone" "dns_zone" {
|
||||
name = var.DOMAIN_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
|
||||
# }
|
||||
# }
|
||||
@@ -1,23 +1,158 @@
|
||||
resource "azurerm_static_web_app" "static_web_app" {
|
||||
name = var.STATIC_WEB_APP_NAME
|
||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||
location = var.REGION
|
||||
sku_size = "Free"
|
||||
sku_tier = "Free"
|
||||
data "azurerm_client_config" "current" {
|
||||
}
|
||||
|
||||
resource "azurerm_dns_cname_record" "dns_cname_record_app" {
|
||||
name = var.APP_SUBDOMAIN_NAME
|
||||
zone_name = data.azurerm_dns_zone.dns_zone.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
|
||||
ttl = 14400
|
||||
record = azurerm_static_web_app.static_web_app.default_host_name
|
||||
sku = "PerGB2018"
|
||||
retention_in_days = 30
|
||||
}
|
||||
|
||||
resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain" {
|
||||
static_web_app_id = azurerm_static_web_app.static_web_app.id
|
||||
domain_name = "${var.APP_SUBDOMAIN_NAME}.${var.DOMAIN_NAME}"
|
||||
validation_type = "cname-delegation"
|
||||
resource "azurerm_container_app_environment" "container_app_environment" {
|
||||
name = module.environment.container_app_environment_name
|
||||
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
|
||||
}
|
||||
|
||||
depends_on = [ azurerm_dns_cname_record.dns_cname_record_app ]
|
||||
}
|
||||
resource "azurerm_container_app" "container_app_api" {
|
||||
name = module.environment.container_app_api_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_api_container_name
|
||||
image = module.environment.container_app_api_container_image
|
||||
cpu = 0.25
|
||||
memory = "0.5Gi"
|
||||
|
||||
env {
|
||||
name = "AZURE_STORAGE_CONNECTION_STRING"
|
||||
secret_name = "azure-storage-connection-string"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "CLIENT_ID"
|
||||
secret_name = "client-id"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "CLIENT_SECRET"
|
||||
secret_name = "client-secret"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "TENANT_ID"
|
||||
secret_name = "tenant-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ingress {
|
||||
allow_insecure_connections = false
|
||||
external_enabled = true
|
||||
target_port = 3000
|
||||
transport = "auto"
|
||||
|
||||
traffic_weight {
|
||||
latest_revision = true
|
||||
percentage = 100
|
||||
}
|
||||
}
|
||||
|
||||
secret {
|
||||
name = "docker-io-password"
|
||||
value = var.DOCKER_IO_PASSWORD
|
||||
}
|
||||
|
||||
secret {
|
||||
name = "azure-storage-connection-string"
|
||||
value = azurerm_storage_account.storage_account.primary_connection_string
|
||||
}
|
||||
|
||||
secret {
|
||||
name = "client-id"
|
||||
value = var.CLIENT_ID
|
||||
}
|
||||
|
||||
secret {
|
||||
name = "client-secret"
|
||||
value = var.CLIENT_SECRET
|
||||
}
|
||||
|
||||
secret {
|
||||
name = "tenant-id"
|
||||
value = var.TENANT_ID
|
||||
}
|
||||
|
||||
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 = 8080
|
||||
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 +0,0 @@
|
||||
output "api_key" {
|
||||
value = azurerm_static_web_app.static_web_app.api_key
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "default_host_name" {
|
||||
value = azurerm_static_web_app.static_web_app.default_host_name
|
||||
}
|
||||
@@ -1,23 +1,44 @@
|
||||
resource "azurerm_storage_account" "storage_account_dev" {
|
||||
name = "${var.STORAGE_ACCOUNT_NAME}dev"
|
||||
resource "azurerm_storage_account" "storage_account" {
|
||||
name = module.environment.storage_account_name
|
||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||
location = data.azurerm_resource_group.resource_group.location
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
}
|
||||
|
||||
resource "azurerm_storage_account" "storage_account_staging" {
|
||||
name = "${var.STORAGE_ACCOUNT_NAME}staging"
|
||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||
location = data.azurerm_resource_group.resource_group.location
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
resource "azurerm_storage_table" "logs_table" {
|
||||
name = "logs"
|
||||
storage_account_name = azurerm_storage_account.storage_account.name
|
||||
}
|
||||
|
||||
resource "azurerm_storage_account" "storage_account_prod" {
|
||||
name = "${var.STORAGE_ACCOUNT_NAME}prod"
|
||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||
location = data.azurerm_resource_group.resource_group.location
|
||||
account_tier = "Standard"
|
||||
account_replication_type = "LRS"
|
||||
resource "azurerm_storage_table" "pilot_table" {
|
||||
name = "pilots"
|
||||
storage_account_name = azurerm_storage_account.storage_account.name
|
||||
}
|
||||
|
||||
resource "azurerm_storage_table" "feature_flags_table" {
|
||||
name = "featureFlags"
|
||||
storage_account_name = azurerm_storage_account.storage_account.name
|
||||
}
|
||||
|
||||
resource "azurerm_storage_table_entity" "logbook_feature_flag_entity" {
|
||||
storage_table_id = azurerm_storage_table.feature_flags_table.id
|
||||
|
||||
partition_key = "featureFlag"
|
||||
row_key = "logbook"
|
||||
|
||||
entity = {
|
||||
active = module.environment.logbook_feature_flag_active
|
||||
}
|
||||
}
|
||||
|
||||
resource "azurerm_storage_table_entity" "pilots_feature_flag_entity" {
|
||||
storage_table_id = azurerm_storage_table.feature_flags_table.id
|
||||
|
||||
partition_key = "featureFlag"
|
||||
row_key = "pilots"
|
||||
|
||||
entity = {
|
||||
active = module.environment.pilots_feature_flag_active
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@ variable "APP_SUBDOMAIN_NAME" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "AZURE_STORAGE_ACCOUNT_KEY" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "CLIENT_ID" {
|
||||
type = string
|
||||
}
|
||||
@@ -33,10 +28,6 @@ variable "DOMAIN_NAME" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "CUSTOM_DOMAIN_NAME_COUNT" {
|
||||
type = number
|
||||
}
|
||||
|
||||
variable "REGION" {
|
||||
type = string
|
||||
}
|
||||
@@ -45,14 +36,6 @@ variable "RESOURCE_GROUP_NAME" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "STATIC_WEB_APP_NAME" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "STORAGE_ACCOUNT_NAME" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "TENANT_ID" {
|
||||
type = string
|
||||
}
|
||||
|
||||
16963
package-lock.json
generated
16963
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"name": "@noahspan/flying",
|
||||
"version": "1.0.0",
|
||||
"workspaces": [
|
||||
"api",
|
||||
"app",
|
||||
"tests"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "concurrently 'npm run start:azure -w api' 'wait-on tcp:0.0.0.0:7071 && npm run dev -w app'",
|
||||
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
||||
"lint": "npm run lint -w api && npm run lint -w app",
|
||||
"prepare": "husky || true"
|
||||
"prepare": "husky || true",
|
||||
"preprod": "rimraf ./.prod && pnpm --filter api build && pnpm --filter app build",
|
||||
"prod": "pnpm --filter api --prod deploy ./.prod/api && pnpm --filter app --prod deploy ./.prod/app"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
@@ -23,6 +20,7 @@
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "3.2.5",
|
||||
"rimraf": "^6.0.1",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
11879
pnpm-lock.yaml
generated
Normal file
11879
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- 'api'
|
||||
- 'app'
|
||||
Reference in New Issue
Block a user