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:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-deploy-api:
|
build-api:
|
||||||
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
name: build-api
|
||||||
name: build-deploy-api
|
uses: ./.github/workflows/api_build.yaml
|
||||||
uses: ./.github/workflows/api.yaml
|
|
||||||
with:
|
with:
|
||||||
calling_workflow: dev
|
environment_name: pull_request
|
||||||
|
version_number: ${{ github.run_id }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-deploy-app:
|
build-app:
|
||||||
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
name: build-app
|
||||||
name: build-deploy-app
|
uses: ./.github/workflows/app_build.yaml
|
||||||
uses: ./.github/workflows/app.yaml
|
|
||||||
with:
|
with:
|
||||||
calling_workflow: dev
|
environment_name: pull_request
|
||||||
deployment_environment: staging
|
version_number: ${{ github.run_id }}
|
||||||
secrets: inherit
|
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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,3 +3,6 @@ node_modules
|
|||||||
**/.env
|
**/.env
|
||||||
**/.env.*
|
**/.env.*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.dapr
|
||||||
|
**/**/secrets.json
|
||||||
|
.prod
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env sh
|
#!/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",
|
"name": "api",
|
||||||
"version": "0.0.1",
|
"version": "0.0.22",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -16,23 +16,25 @@
|
|||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
"start:azure": "npm run build && func host start"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/app-configuration": "^1.8.0",
|
||||||
"@azure/data-tables": "^13.2.2",
|
"@azure/data-tables": "^13.2.2",
|
||||||
"@azure/functions": "^1.0.3",
|
"@azure/msal-node": "^2.16.2",
|
||||||
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||||
"@nestjs/axios": "^3.0.3",
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/azure-database": "^3.0.0",
|
"@nestjs/azure-database": "^3.0.0",
|
||||||
"@nestjs/azure-func-http": "^0.10.0",
|
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.2",
|
"@nestjs/config": "^3.2.2",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@noahspan/noahspan-modules": "^0.4.7",
|
"@noahspan/azure-database": "^3.1.2",
|
||||||
"@schematics/angular": "^17.3.7",
|
"@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",
|
"reflect-metadata": "0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
@@ -51,11 +53,13 @@
|
|||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"ts-loader": "^9.4.3",
|
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"js",
|
"js",
|
||||||
|
|||||||
@@ -3,54 +3,23 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Headers,
|
Headers,
|
||||||
Query,
|
Query,
|
||||||
Res,
|
StreamableFile,
|
||||||
StreamableFile
|
UseGuards
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||||
AppConfigService,
|
import { MsGraphService } from './msGraph/ms-graph.service'
|
||||||
MsGraphService,
|
|
||||||
MsGraphClient
|
|
||||||
} from '@noahspan/noahspan-modules';
|
|
||||||
import { FeatureFlagValue } from '@azure/app-configuration';
|
|
||||||
import { Public } from '@noahspan/noahspan-modules';
|
|
||||||
import { Person } from '@microsoft/microsoft-graph-types';
|
import { Person } from '@microsoft/microsoft-graph-types';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { createReadStream } from 'fs';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { join } from 'path';
|
|
||||||
import { arrayBuffer } from 'stream/consumers';
|
|
||||||
import type { Response } from 'express';
|
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
|
@UseGuards(AuthGuard('azure-ad'))
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly appService: AppService,
|
private readonly appService: AppService,
|
||||||
private readonly appConfigService: AppConfigService,
|
|
||||||
private readonly msGraphService: MsGraphService
|
private readonly msGraphService: MsGraphService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Get('featureFlags')
|
|
||||||
async getFeatureFlags(
|
|
||||||
@Query() query: any
|
|
||||||
): Promise<{ key: string; enabled: boolean }[]> {
|
|
||||||
try {
|
|
||||||
const featureFlagKeys: string[] =
|
|
||||||
query.keys && query.keys.toString().includes(';')
|
|
||||||
? query.keys.split(';')
|
|
||||||
: [query.keys];
|
|
||||||
const featureFlagLabel: string = query.label;
|
|
||||||
const featureFlags: { key: string; enabled: boolean }[] =
|
|
||||||
await this.appConfigService.getFeatureFlags(
|
|
||||||
featureFlagKeys,
|
|
||||||
featureFlagLabel
|
|
||||||
);
|
|
||||||
|
|
||||||
return featureFlags;
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('userPhoto')
|
@Get('userPhoto')
|
||||||
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
|
async getProfilePhoto(@Headers() headers: any): Promise<StreamableFile> {
|
||||||
try {
|
try {
|
||||||
@@ -106,7 +75,6 @@ export class AppController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Get('hello')
|
@Get('hello')
|
||||||
async getHello(): Promise<string> {
|
async getHello(): Promise<string> {
|
||||||
return this.appService.getHello();
|
return this.appService.getHello();
|
||||||
|
|||||||
@@ -1,47 +1,49 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import {
|
import { AuthModule } from './auth/auth.module';
|
||||||
AppConfigModule,
|
import { MsGraphModule } from './msGraph/ms-graph.module';
|
||||||
AuthModule,
|
import { FeatureFlagModule } from './featureFlag/feature-flag.module'
|
||||||
AuthGuard,
|
import { LogModule } from './log/log.module';
|
||||||
MsGraphModule
|
|
||||||
} from '@noahspan/noahspan-modules';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
|
||||||
import { LogbookModule } from './logbook/logbook.module';
|
|
||||||
import { PilotModule } from './pilot/pilot.module';
|
import { PilotModule } from './pilot/pilot.module';
|
||||||
import { APP_FILTER } from '@nestjs/core';
|
import { APP_FILTER } from '@nestjs/core';
|
||||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import configuration from './config/configuration';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
AuthModule.registerAsync({
|
||||||
AppConfigModule.register({
|
imports: [ConfigModule],
|
||||||
url: process.env.APP_CONFIG_URL,
|
useFactory: async (configService: ConfigService) => {
|
||||||
tenantId: process.env.TENANT_ID,
|
return {
|
||||||
clientId: process.env.CLIENT_ID,
|
clientId: configService.get<string>('clientId'),
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
clientSecret: configService.get<string>('clientSecret'),
|
||||||
|
tenantId: configService.get<string>('tenantId')
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [ConfigService]
|
||||||
}),
|
}),
|
||||||
AuthModule.register({
|
ConfigModule.forRoot({
|
||||||
tenantId: process.env.TENANT_ID,
|
load: [configuration]
|
||||||
clientId: process.env.CLIENT_ID,
|
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
|
||||||
}),
|
}),
|
||||||
MsGraphModule.register({
|
FeatureFlagModule,
|
||||||
tenantId: process.env.TENANT_ID,
|
LogModule,
|
||||||
clientId: process.env.CLIENT_ID,
|
MsGraphModule.registerAsync({
|
||||||
clientSecret: process.env.CLIENT_SECRET
|
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
|
PilotModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
|
||||||
provide: APP_GUARD,
|
|
||||||
useClass: AuthGuard
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: APP_FILTER,
|
provide: APP_FILTER,
|
||||||
useClass: HttpExceptionFilter
|
useClass: HttpExceptionFilter
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
// import { MsGraphService, MsGraphClient } from '@noahspan/noahspan-modules';
|
||||||
|
import { Client as MsGraphClient } from '@microsoft/microsoft-graph-client';
|
||||||
|
import { MsGraphService } from './msGraph/ms-graph.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
constructor(private readonly msGraphService: MsGraphService) {}
|
constructor(private readonly msGraphService: MsGraphService) {}
|
||||||
|
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
return 'Hello World!';
|
return JSON.stringify(process.env);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPersonSearchResults(
|
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
|
||||||
|
})
|
||||||
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 response = ctx.getResponse<Response>();
|
||||||
const request = ctx.getRequest<Request>();
|
const request = ctx.getRequest<Request>();
|
||||||
const status = excpetion.getStatus();
|
const status = excpetion.getStatus();
|
||||||
console.log(excpetion.cause);
|
|
||||||
|
|
||||||
response.status(status).json({
|
response.status(status).json({
|
||||||
name: excpetion.cause,
|
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;
|
pilotId: string;
|
||||||
pilotName: string;
|
pilotName: string;
|
||||||
date: string;
|
date: string;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export class LogbookEntity {
|
export class Log {
|
||||||
partitionKey: string;
|
partitionKey: string;
|
||||||
rowKey: string;
|
rowKey: string;
|
||||||
pilotId: string;
|
pilotId: string;
|
||||||
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 { HttpService } from '@nestjs/axios';
|
||||||
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||||
import { InternalServerErrorException } from '@nestjs/common';
|
import { InternalServerErrorException } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const httpService = new HttpService();
|
const httpService = new HttpService();
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
app.enableCors();
|
||||||
app.setGlobalPrefix('api');
|
app.setGlobalPrefix('api');
|
||||||
app.useGlobalFilters(new HttpExceptionFilter());
|
app.useGlobalFilters(new HttpExceptionFilter());
|
||||||
|
|
||||||
httpService.axiosRef.interceptors.response.use(
|
httpService.axiosRef.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
return response;
|
return response;
|
||||||
@@ -21,6 +24,7 @@ async function bootstrap() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
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 { Injectable } from '@nestjs/common';
|
||||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||||
import { Certificate } from './certificate.entity';
|
import { Certificate } from './certificate.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { InjectRepository, Repository } from '@nestjs/azure-database';
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Endorsement } from './endorsement.entity';
|
import { Endorsement } from './endorsement.entity';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
export class PilotInfoEntity {
|
|
||||||
partitionKey: string;
|
|
||||||
rowKey: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
address?: string;
|
|
||||||
city?: string;
|
|
||||||
state?: string;
|
|
||||||
postalCode?: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
}
|
|
||||||
@@ -1,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 { Injectable } from '@nestjs/common';
|
||||||
import { Repository, InjectRepository } from '@nestjs/azure-database';
|
import { Repository, InjectRepository } from '@noahspan/azure-database';
|
||||||
import { Medical } from './medical.entity';
|
import { Medical } from './medical.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|||||||
@@ -1,71 +1,95 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
HttpException,
|
HttpException,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
UseInterceptors
|
Put,
|
||||||
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PilotInfoService } from './info/pilot-info.service';
|
import { PilotDto } from './pilot.dto';
|
||||||
import { PilotInfoDto } from './info/pilot-info.dto';
|
import { Pilot } from './pilot.entity';
|
||||||
import { TableInsertEntityHeaders } from '@azure/data-tables';
|
import { PilotService } from './pilot.service';
|
||||||
import { CustomError } from '../customError/CustomError';
|
import { CustomError } from '../error/customError';
|
||||||
import { PilotInfoEntity } from './info/pilot-info.entity';
|
import { AuthGuard } from '@nestjs/passport'
|
||||||
import { PilotInterceptor } from 'src/pilot/interceptors/pilot.interceptor';
|
|
||||||
import { Public } from '@noahspan/noahspan-modules';
|
|
||||||
|
|
||||||
@Controller('pilots')
|
@Controller('pilots')
|
||||||
|
@UseGuards(AuthGuard('azure-ad'))
|
||||||
export class PilotController {
|
export class PilotController {
|
||||||
constructor(private readonly pilotInfoService: PilotInfoService) {}
|
constructor(private readonly pilotService: PilotService) {}
|
||||||
|
|
||||||
@Get(':pilotId')
|
@Get(':partitionKey/:rowKey')
|
||||||
@Public()
|
async find(
|
||||||
@UseInterceptors(PilotInterceptor)
|
@Param('partitionKey') partitionKey: string,
|
||||||
async find(@Param() params: any): Promise<PilotInfoEntity> {
|
@Param('rowKey') rowKey: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const pilot: PilotInfoEntity = await this.pilotInfoService.find(
|
return await this.pilotService.find(partitionKey, rowKey);
|
||||||
params.pilotId
|
|
||||||
);
|
|
||||||
|
|
||||||
return pilot;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Public()
|
async findAll() {
|
||||||
@UseInterceptors(PilotInterceptor)
|
|
||||||
async findAll(): Promise<PilotInfoEntity[]> {
|
|
||||||
try {
|
try {
|
||||||
const pilots: PilotInfoEntity[] = await this.pilotInfoService.findAll();
|
return await this.pilotService.findAll();
|
||||||
|
|
||||||
return pilots;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async create(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
|
async create(@Body() pilotDto: PilotDto) {
|
||||||
try {
|
try {
|
||||||
const response: TableInsertEntityHeaders =
|
const pilot = new Pilot();
|
||||||
await this.pilotInfoService.create(pilotInfoData);
|
|
||||||
|
Object.assign(pilot, pilotDto);
|
||||||
|
|
||||||
|
return await this.pilotService.create(pilot);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const customError = error as CustomError;
|
const customError = error as CustomError;
|
||||||
|
|
||||||
throw new HttpException(customError.message, customError.statusCode, {
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
cause: customError.name
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Put(':partitionKey/:rowKey')
|
||||||
|
async update(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string,
|
||||||
|
@Body() pilotDto: PilotDto
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const pilot = new Pilot();
|
||||||
|
|
||||||
|
Object.assign(pilot, pilotDto);
|
||||||
|
|
||||||
|
return await this.pilotService.update(partitionKey, rowKey, pilot);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':partitionKey/:rowKey')
|
||||||
|
async delete(
|
||||||
|
@Param('partitionKey') partitionKey: string,
|
||||||
|
@Param('rowKey') rowKey: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await this.pilotService.delete(partitionKey, rowKey);
|
||||||
|
} catch (error) {
|
||||||
|
const customError = error as CustomError;
|
||||||
|
|
||||||
|
throw new HttpException(customError.message, customError.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export class PilotInfoDto {
|
export class PilotDto {
|
||||||
|
partitionKey: string;
|
||||||
|
rowKey: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
14
api/src/pilot/pilot.entity.ts
Normal file
14
api/src/pilot/pilot.entity.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { EntityString } from '@noahspan/azure-database';
|
||||||
|
|
||||||
|
export class Pilot {
|
||||||
|
@EntityString() partitionKey: string;
|
||||||
|
@EntityString() rowKey: string;
|
||||||
|
@EntityString() id: string;
|
||||||
|
@EntityString() name: string;
|
||||||
|
@EntityString() address?: string;
|
||||||
|
@EntityString() city?: string;
|
||||||
|
@EntityString() state?: string;
|
||||||
|
@EntityString() postalCode?: string;
|
||||||
|
@EntityString() email?: string;
|
||||||
|
@EntityString() phone?: string;
|
||||||
|
}
|
||||||
@@ -1,20 +1,27 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { PilotController } from './pilot.controller';
|
import { PilotController } from './pilot.controller';
|
||||||
import { TableModule } from '@noahspan/noahspan-modules';
|
import { PilotService } from './pilot.service';
|
||||||
import { PilotInfoService } from './info/pilot-info.service';
|
import { AzureTableStorageModule } from '@noahspan/azure-database';
|
||||||
|
import { Pilot } from './pilot.entity';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TableModule.register({
|
AzureTableStorageModule.forRootAsync({
|
||||||
accountName: process.env.AZURE_STORAGE_ACCOUNT_NAME,
|
imports: [ConfigModule],
|
||||||
accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY,
|
useFactory: async (configService: ConfigService) => {
|
||||||
accountUrl: process.env.AZURE_STORAGE_ACCOUNT_URL,
|
return {
|
||||||
allowInsecureConnection: Boolean(
|
connectionString: configService.get<string>('azureStorageConnectionString')
|
||||||
process.env.AZURE_STORAGE_ALLOW_INSECURE_CONNECTION
|
};
|
||||||
)
|
},
|
||||||
})
|
inject: [ConfigService]
|
||||||
|
}),
|
||||||
|
AzureTableStorageModule.forFeature(Pilot, {
|
||||||
|
createTableIfNotExists: false,
|
||||||
|
table: 'pilots'
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
controllers: [PilotController],
|
controllers: [PilotController],
|
||||||
providers: [PilotInfoService]
|
providers: [PilotService]
|
||||||
})
|
})
|
||||||
export class PilotModule {}
|
export class PilotModule {}
|
||||||
|
|||||||
40
api/src/pilot/pilot.service.ts
Normal file
40
api/src/pilot/pilot.service.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { InjectRepository, Repository } from '@noahspan/azure-database';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Pilot } from './pilot.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PilotService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Pilot)
|
||||||
|
private readonly pilotRepository: Repository<Pilot>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async find(partitionKey: string, rowKey: string): Promise<Pilot> {
|
||||||
|
return await this.pilotRepository.find(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<Pilot[]> {
|
||||||
|
return await this.pilotRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(pilot: Pilot): Promise<Pilot> {
|
||||||
|
// try {
|
||||||
|
// return await this.pilotRepository.create(pilot);
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new Error(error);
|
||||||
|
// }
|
||||||
|
return await this.pilotRepository.create(pilot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
partitionKey: string,
|
||||||
|
rowKey: string,
|
||||||
|
pilot: Pilot
|
||||||
|
): Promise<Pilot> {
|
||||||
|
return await this.pilotRepository.update(partitionKey, rowKey, pilot);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(partitionKey: string, rowKey: string): Promise<void> {
|
||||||
|
await this.pilotRepository.delete(partitionKey, rowKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "@noahspan/flying-client",
|
"name": "app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.0.19",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"serve": "serve -s dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^3.17.0",
|
"@azure/msal-browser": "^4.0.1",
|
||||||
"@azure/msal-react": "^2.0.19",
|
"@azure/msal-react": "^3.0.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
|
||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
||||||
"@noahspan/noahspan-components": "^0.8.9",
|
"@noahspan/noahspan-components": "^0.8.9",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"framer-motion": "^11.1.7",
|
"dotenv": "^16.4.7",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.51.4",
|
"react-hook-form": "^7.51.4",
|
||||||
"react-router-dom": "^6.23.0"
|
"react-router-dom": "^6.23.0"
|
||||||
},
|
},
|
||||||
@@ -29,13 +26,13 @@
|
|||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.19",
|
|
||||||
"postcss": "^8.4.38",
|
|
||||||
"tailwindcss": "^3.4.3",
|
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"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 { useHttpClient } from './hooks/httpClient/UseHttpClient';
|
||||||
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag';
|
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag';
|
||||||
import SiteNav from './components/siteNav/SiteNav';
|
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 httpClient: AxiosInstance = useHttpClient();
|
||||||
const appContext = useAppContext();
|
const appContext = useAppContext();
|
||||||
|
|
||||||
@@ -36,7 +42,7 @@ const App: React.FC<unknown> = () => {
|
|||||||
// }, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<MsalProvider instance={pca}>
|
||||||
<SiteNav />
|
<SiteNav />
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* {useFeatureFlag('flying-pilots')?.enabled && ( */}
|
{/* {useFeatureFlag('flying-pilots')?.enabled && ( */}
|
||||||
@@ -44,7 +50,7 @@ const App: React.FC<unknown> = () => {
|
|||||||
{/* )} */}
|
{/* )} */}
|
||||||
<Route path="/" element={<Logbook />} />
|
<Route path="/" element={<Logbook />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</MsalProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
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';
|
import { FormMode } from '../../enums/formMode';
|
||||||
|
|
||||||
export interface ILogbookEntryFormProps {
|
export interface ILogFormProps {
|
||||||
entryId?: string;
|
entryId?: string;
|
||||||
isDrawerOpen: boolean;
|
isDrawerOpen: boolean;
|
||||||
mode: FormMode;
|
mode: FormMode;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface ILogbookEntryFormState {
|
export interface ILogFormState {
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
XmarkIcon
|
XmarkIcon
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||||
import { ILogbookEntryFormProps } from './ILogbookEntryFormProps';
|
import { ILogFormProps } from './ILogFormProps';
|
||||||
import { initialState, reducer } from './reducer';
|
import { initialState, reducer } from './reducer';
|
||||||
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
@@ -26,7 +26,7 @@ import { useIsAuthenticated } from '@azure/msal-react';
|
|||||||
import { FormMode } from '../../enums/formMode';
|
import { FormMode } from '../../enums/formMode';
|
||||||
import { usePilots } from '../../hooks/pilots/UsePilots';
|
import { usePilots } from '../../hooks/pilots/UsePilots';
|
||||||
|
|
||||||
const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
const LogForm: React.FC<ILogFormProps> = ({
|
||||||
entryId,
|
entryId,
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
mode,
|
mode,
|
||||||
@@ -37,8 +37,6 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
const { getAccessToken } = useAccessToken();
|
const { getAccessToken } = useAccessToken();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
partitionKey: '',
|
|
||||||
rowKey: '',
|
|
||||||
pilotId: '',
|
pilotId: '',
|
||||||
pilotName: '',
|
pilotName: '',
|
||||||
date: null,
|
date: null,
|
||||||
@@ -80,13 +78,13 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
const accessToken: string = await getAccessToken();
|
const accessToken: string = await getAccessToken();
|
||||||
|
|
||||||
if (!entryId) {
|
if (!entryId) {
|
||||||
await httpClient.post(`api/logbook`, data, {
|
await httpClient.post(`api/logs`, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await httpClient.put(`api/logbook/${entryId}`, data, {
|
await httpClient.put(`api/logs/${entryId}`, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
@@ -120,7 +118,7 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
? { headers: { Authorization: await getAccessToken() } }
|
? { headers: { Authorization: await getAccessToken() } }
|
||||||
: {};
|
: {};
|
||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(
|
||||||
`api/logbook/${entryId}`,
|
`api/logs/${entryId}`,
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
const entry = response.data;
|
const entry = response.data;
|
||||||
@@ -939,4 +937,4 @@ const LogbookEntryForm: React.FC<ILogbookEntryFormProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LogbookEntryForm;
|
export default LogForm;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ILogbookEntryFormState } from './ILogbookEntryFormState';
|
import { ILogFormState } from './ILogFormState';
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| { type: 'SET_ERROR'; payload: string | undefined }
|
| { type: 'SET_ERROR'; payload: string | undefined }
|
||||||
@@ -7,7 +7,7 @@ type Action =
|
|||||||
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
| { type: 'SET_PILOT_OPTIONS'; payload: { label: string; value: string }[] }
|
||||||
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
| { type: 'SET_SELECTED_ENTRY_PILOT_NAME'; payload: string };
|
||||||
|
|
||||||
export const initialState: ILogbookEntryFormState = {
|
export const initialState: ILogFormState = {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@@ -16,9 +16,9 @@ export const initialState: ILogbookEntryFormState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const reducer = (
|
export const reducer = (
|
||||||
state: ILogbookEntryFormState,
|
state: ILogFormState,
|
||||||
action: Action
|
action: Action
|
||||||
): ILogbookEntryFormState => {
|
): ILogFormState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'SET_ERROR': {
|
case 'SET_ERROR': {
|
||||||
return {
|
return {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useReducer, useState } from 'react';
|
import { useEffect, useReducer } from 'react';
|
||||||
import LogbookEntryForm from '../logbookEntryForm/LogbookEntryForm';
|
import LogForm from '../logForm/LogForm';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
@@ -35,11 +35,8 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
const config = isAuthenticated
|
const config = isAuthenticated
|
||||||
? { headers: { Authorization: `${token}` } }
|
? { headers: { Authorization: `${token}` } }
|
||||||
: {};
|
: {};
|
||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(`api/logs`, config);
|
||||||
`api/logbook`,
|
console.log(response)
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
dispatch({ type: 'SET_ENTRIES', payload: response.data });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
@@ -98,7 +95,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
? { headers: { Authorization: `${token}` } }
|
? { headers: { Authorization: `${token}` } }
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
await httpClient.delete(`api/logbook/${state.selectedEntryId}`, config);
|
await httpClient.delete(`api/logs/${state.selectedEntryId}`, config);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_DELETE',
|
type: 'SET_DELETE',
|
||||||
@@ -418,12 +415,15 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<LogbookEntryForm
|
{state.isFormOpen && (
|
||||||
|
<LogForm
|
||||||
entryId={state.selectedEntryId}
|
entryId={state.selectedEntryId}
|
||||||
isDrawerOpen={state.isFormOpen}
|
isDrawerOpen={state.isFormOpen}
|
||||||
mode={state.formMode}
|
mode={state.formMode}
|
||||||
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
onOpenClose={(mode) => onOpenCloseEntryForm(mode)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{state.isConfirmDialogOpen && (
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
contentText="Are you sure you want to delete the logbook entry?"
|
contentText="Are you sure you want to delete the logbook entry?"
|
||||||
isLoading={state.isConfirmDialogLoading}
|
isLoading={state.isConfirmDialogLoading}
|
||||||
@@ -432,6 +432,7 @@ const Logbook: React.FC<unknown> = () => {
|
|||||||
onConfirm={onConfirmationDialogConfirm}
|
onConfirm={onConfirmationDialogConfirm}
|
||||||
title="Confirm Delete"
|
title="Confirm Delete"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
XmarkIcon
|
XmarkIcon
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { IPilotFormProps } from './IPilotFormProps';
|
import { IPilotFormProps } from './IPilotFormProps';
|
||||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||||
import { useIsAuthenticated } from '@azure/msal-react';
|
import { useIsAuthenticated } from '@azure/msal-react';
|
||||||
@@ -38,8 +38,8 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
const { getAccessToken } = useAccessToken();
|
const { getAccessToken } = useAccessToken();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
partitionKey: '',
|
partitionKey: 'pilot',
|
||||||
rowKey: '',
|
rowKey: 'noah@noahspannbauer.com',
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
address: '',
|
address: '',
|
||||||
@@ -53,6 +53,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
defaultValues: defaultValues
|
defaultValues: defaultValues
|
||||||
});
|
});
|
||||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||||
|
const [isError, setIsError] = useState<boolean>(false);
|
||||||
|
|
||||||
const onPeoplePickerSearch = async (
|
const onPeoplePickerSearch = async (
|
||||||
_event: React.SyntheticEvent,
|
_event: React.SyntheticEvent,
|
||||||
@@ -105,24 +106,19 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const accessToken: string = await getAccessToken();
|
const accessToken: string = await getAccessToken();
|
||||||
const response: AxiosResponse = await httpClient.post(
|
|
||||||
`api/pilots`,
|
await httpClient.post(`api/pilots`, data, {
|
||||||
data,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: accessToken
|
Authorization: accessToken
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (response) console.log(response);
|
onOpenClose(FormMode.CANCEL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error)) {
|
const axiosError = error as AxiosError;
|
||||||
const errResp = error.response;
|
const responseData = axiosError.response?.data as any;
|
||||||
|
|
||||||
console.log(errResp?.data.message);
|
console.log(responseData.message);
|
||||||
} else {
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,25 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
|
const [pilotFormMode, setPilotFormMode] = useState<FormMode>(FormMode.CANCEL);
|
||||||
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
|
const [selectedPilotId, setSelectedPilotId] = useState<string | undefined>();
|
||||||
const [pilots, setPilots] = useState<Pilot[]>([]);
|
const [pilots, setPilots] = useState<Pilot[]>([]);
|
||||||
const onOpenClosePilotForm = (mode: FormMode, pilotId?: string) => {
|
|
||||||
|
const getPilots = async (): Promise<Pilot[]> => {
|
||||||
|
try {
|
||||||
|
const config = isAuthenticated
|
||||||
|
? { headers: { Authorization: await getAccessToken() } }
|
||||||
|
: {};
|
||||||
|
const response: AxiosResponse = await httpClient.get(
|
||||||
|
`api/pilots`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
const pilots: Pilot[] = response.data;
|
||||||
|
|
||||||
|
return pilots;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('broken');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpenClosePilotForm = async (mode: FormMode, pilotId?: string) => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case FormMode.ADD:
|
case FormMode.ADD:
|
||||||
case FormMode.EDIT:
|
case FormMode.EDIT:
|
||||||
@@ -49,6 +67,9 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
setIsDrawerOpen(true);
|
setIsDrawerOpen(true);
|
||||||
break;
|
break;
|
||||||
case FormMode.CANCEL:
|
case FormMode.CANCEL:
|
||||||
|
const pilots = await getPilots();
|
||||||
|
|
||||||
|
setPilots(pilots);
|
||||||
setPilotFormMode(mode);
|
setPilotFormMode(mode);
|
||||||
setSelectedPilotId(undefined);
|
setSelectedPilotId(undefined);
|
||||||
setIsDrawerOpen(false);
|
setIsDrawerOpen(false);
|
||||||
@@ -73,6 +94,7 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
const onCloseActionMenu = () => {
|
const onCloseActionMenu = () => {
|
||||||
setAnchorElAction(null);
|
setAnchorElAction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<IconButton onClick={onOpenActionMenu}>
|
<IconButton onClick={onOpenActionMenu}>
|
||||||
@@ -124,23 +146,17 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getPilots = async () => {
|
const loadPilots = async () => {
|
||||||
try {
|
try {
|
||||||
const config = isAuthenticated
|
const pilots = await getPilots();
|
||||||
? { headers: { Authorization: await getAccessToken() } }
|
|
||||||
: {};
|
setPilots(pilots);
|
||||||
const response: AxiosResponse = await httpClient.get(
|
|
||||||
`api/pilots`,
|
|
||||||
config
|
|
||||||
);
|
|
||||||
console.log(response.data);
|
|
||||||
setPilots(response.data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getPilots();
|
loadPilots();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ const SiteNav: React.FC<unknown> = () => {
|
|||||||
url: '/pilots'
|
url: '/pilots'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const handleSignIn = async () => {
|
const handleSignIn = () => {
|
||||||
await instance.loginRedirect({
|
instance.loginRedirect({
|
||||||
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -52,6 +52,7 @@ const SiteNav: React.FC<unknown> = () => {
|
|||||||
instance.logoutRedirect();
|
instance.logoutRedirect();
|
||||||
};
|
};
|
||||||
const getUserProfile = async (accessToken: string): Promise<User> => {
|
const getUserProfile = async (accessToken: string): Promise<User> => {
|
||||||
|
console.log(accessToken)
|
||||||
try {
|
try {
|
||||||
const response: AxiosResponse = await httpClient.get(`api/userProfile`, {
|
const response: AxiosResponse = await httpClient.get(`api/userProfile`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -66,6 +67,7 @@ const SiteNav: React.FC<unknown> = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getUserPhoto = async (accessToken: string): Promise<string> => {
|
const getUserPhoto = async (accessToken: string): Promise<string> => {
|
||||||
|
console.log(accessToken)
|
||||||
try {
|
try {
|
||||||
const response: AxiosResponse = await httpClient.get(`api/userPhoto`, {
|
const response: AxiosResponse = await httpClient.get(`api/userPhoto`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -98,40 +100,44 @@ const SiteNav: React.FC<unknown> = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const callback = instance.addEventCallback(
|
// const callback = instance.addEventCallback(
|
||||||
async (message: EventMessage) => {
|
// async (message: EventMessage) => {
|
||||||
if (message.eventType === EventType.LOGIN_SUCCESS) {
|
// if (message.eventType === EventType.LOGIN_SUCCESS) {
|
||||||
try {
|
// try {
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
|
|
||||||
const eventPayload: EventPayloadExtended =
|
// const eventPayload: EventPayloadExtended =
|
||||||
message.payload as EventPayloadExtended;
|
// message.payload as EventPayloadExtended;
|
||||||
const userProfile: User = await getUserProfile(
|
// const userProfile: User = await getUserProfile(
|
||||||
eventPayload.accessToken
|
// eventPayload.accessToken
|
||||||
);
|
// );
|
||||||
const userPhoto = await getUserPhoto(eventPayload.accessToken);
|
// const userPhoto = await getUserPhoto(eventPayload.accessToken);
|
||||||
|
|
||||||
appContext.dispatch({
|
// appContext.dispatch({
|
||||||
type: 'SET_USER_PROFILE',
|
// type: 'SET_USER_PROFILE',
|
||||||
payload: userProfile
|
// payload: userProfile
|
||||||
});
|
// });
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
return () => {
|
// instance.handleRedirectPromise().then((response) => {
|
||||||
if (callback) {
|
// console.log(response)
|
||||||
instance.removeEventCallback(callback);
|
// })
|
||||||
appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} });
|
|
||||||
}
|
// return () => {
|
||||||
};
|
// if (callback) {
|
||||||
}, []);
|
// instance.removeEventCallback(callback);
|
||||||
|
// appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setUserProfile = async () => {
|
const setUserProfile = async () => {
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ export const useFeatureFlag = (featureFlagKey: string) => {
|
|||||||
const featureFlag = appContext.state.featureFlags.find(
|
const featureFlag = appContext.state.featureFlags.find(
|
||||||
(featureFlag) => featureFlag.key === featureFlagKey
|
(featureFlag) => featureFlag.key === featureFlagKey
|
||||||
);
|
);
|
||||||
|
console.log(featureFlag)
|
||||||
return 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 { BrowserRouter } from 'react-router-dom';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import '@noahspan/noahspan-components/noahspan-components.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 { MsalProvider } from '@azure/msal-react';
|
||||||
|
import { msalConfig } from './auth/msalConfig';
|
||||||
|
|
||||||
const msalInstance: PublicClientApplication = new PublicClientApplication({
|
const msalInstance: PublicClientApplication = new PublicClientApplication(msalConfig);
|
||||||
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
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
msalInstance.initialize().then(() => {
|
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(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<MsalProvider instance={msalInstance}>
|
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App pca={msalInstance} />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
</MsalProvider>
|
|
||||||
</React.StrictMode>
|
</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/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()]
|
plugins: [react()],
|
||||||
|
preview: {
|
||||||
|
port: 8080,
|
||||||
|
strictPort: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 8080,
|
||||||
|
strictPort: true,
|
||||||
|
host: '0.0.0.0'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
14
dapr.yaml
Normal file
14
dapr.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: 1
|
||||||
|
|
||||||
|
common:
|
||||||
|
resourcesPaths: ['./.dapr/components']
|
||||||
|
|
||||||
|
apps:
|
||||||
|
- appID: flying-api
|
||||||
|
appDirPath: ./api
|
||||||
|
appPort: 3000
|
||||||
|
command: ['npm', 'run', 'start']
|
||||||
|
- appID: flying-app
|
||||||
|
appDirPath: ./app
|
||||||
|
appPort: 5173
|
||||||
|
command: ['npm', 'run', 'dev']
|
||||||
@@ -11,17 +11,23 @@ services:
|
|||||||
command: 'azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose'
|
command: 'azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose'
|
||||||
|
|
||||||
api:
|
api:
|
||||||
container_name: api
|
container_name: flying-api
|
||||||
build:
|
build:
|
||||||
context: ./api
|
context: .
|
||||||
dockerfile: Dockerfile
|
target: api
|
||||||
ports:
|
ports:
|
||||||
- '7071:3000'
|
- '3000:3000'
|
||||||
env_file:
|
env_file:
|
||||||
- ./api/.env
|
- ./api/.env
|
||||||
|
|
||||||
# swa:
|
app:
|
||||||
# image: swacli/static-web-apps-cli
|
container_name: flying-app
|
||||||
# ports:
|
build:
|
||||||
# - "4280:4280"
|
context: .
|
||||||
# command: "swa start "
|
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
|
|
||||||
}
|
|
||||||
@@ -2,3 +2,14 @@ data "azurerm_dns_zone" "dns_zone" {
|
|||||||
name = var.DOMAIN_NAME
|
name = var.DOMAIN_NAME
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# resource "azurerm_dns_txt_record" "dns_txt_record" {
|
||||||
|
# name = "asuid.${var.APP_SUBDOMAIN_NAME}"
|
||||||
|
# resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
# zone_name = data.azurerm_dns_zone.dns_zone.name
|
||||||
|
# ttl = 14400
|
||||||
|
|
||||||
|
# record {
|
||||||
|
# value = azurerm_container_app.container_app_app.custom_domain_verification_id
|
||||||
|
# }
|
||||||
|
# }
|
||||||
@@ -1,23 +1,158 @@
|
|||||||
resource "azurerm_static_web_app" "static_web_app" {
|
data "azurerm_client_config" "current" {
|
||||||
name = var.STATIC_WEB_APP_NAME
|
}
|
||||||
|
|
||||||
|
resource "azurerm_log_analytics_workspace" "log_analytics_workspace" {
|
||||||
|
name = module.environment.log_analytics_workspace_name
|
||||||
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
location = var.REGION
|
sku = "PerGB2018"
|
||||||
sku_size = "Free"
|
retention_in_days = 30
|
||||||
sku_tier = "Free"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_dns_cname_record" "dns_cname_record_app" {
|
resource "azurerm_container_app_environment" "container_app_environment" {
|
||||||
name = var.APP_SUBDOMAIN_NAME
|
name = module.environment.container_app_environment_name
|
||||||
zone_name = data.azurerm_dns_zone.dns_zone.name
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
ttl = 14400
|
log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics_workspace.id
|
||||||
record = azurerm_static_web_app.static_web_app.default_host_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_static_web_app_custom_domain" "static_web_app_custom_domain" {
|
resource "azurerm_container_app" "container_app_api" {
|
||||||
static_web_app_id = azurerm_static_web_app.static_web_app.id
|
name = module.environment.container_app_api_name
|
||||||
domain_name = "${var.APP_SUBDOMAIN_NAME}.${var.DOMAIN_NAME}"
|
container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
|
||||||
validation_type = "cname-delegation"
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
|
revision_mode = "Single"
|
||||||
|
|
||||||
depends_on = [ azurerm_dns_cname_record.dns_cname_record_app ]
|
registry {
|
||||||
|
server = "index.docker.io"
|
||||||
|
username = var.DOCKER_IO_USERNAME
|
||||||
|
password_secret_name = "docker-io-password"
|
||||||
|
}
|
||||||
|
|
||||||
|
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" {
|
resource "azurerm_storage_account" "storage_account" {
|
||||||
name = "${var.STORAGE_ACCOUNT_NAME}dev"
|
name = module.environment.storage_account_name
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
resource_group_name = data.azurerm_resource_group.resource_group.name
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
location = data.azurerm_resource_group.resource_group.location
|
||||||
account_tier = "Standard"
|
account_tier = "Standard"
|
||||||
account_replication_type = "LRS"
|
account_replication_type = "LRS"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_storage_account" "storage_account_staging" {
|
resource "azurerm_storage_table" "logs_table" {
|
||||||
name = "${var.STORAGE_ACCOUNT_NAME}staging"
|
name = "logs"
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
storage_account_name = azurerm_storage_account.storage_account.name
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
|
||||||
account_tier = "Standard"
|
|
||||||
account_replication_type = "LRS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_storage_account" "storage_account_prod" {
|
resource "azurerm_storage_table" "pilot_table" {
|
||||||
name = "${var.STORAGE_ACCOUNT_NAME}prod"
|
name = "pilots"
|
||||||
resource_group_name = data.azurerm_resource_group.resource_group.name
|
storage_account_name = azurerm_storage_account.storage_account.name
|
||||||
location = data.azurerm_resource_group.resource_group.location
|
}
|
||||||
account_tier = "Standard"
|
|
||||||
account_replication_type = "LRS"
|
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
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "AZURE_STORAGE_ACCOUNT_KEY" {
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "CLIENT_ID" {
|
variable "CLIENT_ID" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
@@ -33,10 +28,6 @@ variable "DOMAIN_NAME" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "CUSTOM_DOMAIN_NAME_COUNT" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "REGION" {
|
variable "REGION" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
@@ -45,14 +36,6 @@ variable "RESOURCE_GROUP_NAME" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "STATIC_WEB_APP_NAME" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "STORAGE_ACCOUNT_NAME" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "TENANT_ID" {
|
variable "TENANT_ID" {
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|||||||
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",
|
"name": "@noahspan/flying",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"workspaces": [
|
|
||||||
"api",
|
|
||||||
"app",
|
|
||||||
"tests"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "concurrently 'npm run start:azure -w api' 'wait-on tcp:0.0.0.0:7071 && npm run dev -w app'",
|
"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\"",
|
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
||||||
"lint": "npm run lint -w api && npm run lint -w app",
|
"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": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
@@ -23,6 +20,7 @@
|
|||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
"wait-on": "^7.2.0"
|
"wait-on": "^7.2.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"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