Merged PR 1100: 2025/6/30 本番リリース
PH2開発分を本番リリース用ブランチにマージ
This commit is contained in:
parent
07043786c8
commit
0e8f0703e2
@ -1,16 +1,16 @@
|
||||
#ビルドイメージ
|
||||
FROM node:18.17.1-buster AS build-container
|
||||
FROM node:22.14-bookworm AS build-container
|
||||
WORKDIR /app
|
||||
RUN mkdir dictation_function
|
||||
COPY dictation_function/ dictation_function/
|
||||
RUN npm install --force -g n && n 18.17.1 \
|
||||
RUN npm install --force -g n && n 22.14 \
|
||||
&& cd dictation_function \
|
||||
&& npm ci \
|
||||
&& npm run build \
|
||||
&& cd ..
|
||||
|
||||
# 成果物イメージ
|
||||
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
||||
FROM mcr.microsoft.com/azure-functions/node:4-node22
|
||||
|
||||
WORKDIR /home/site/wwwroot
|
||||
RUN mkdir build \
|
||||
|
||||
47
DockerfileServerAutoTranscription.dockerfile
Normal file
47
DockerfileServerAutoTranscription.dockerfile
Normal file
@ -0,0 +1,47 @@
|
||||
FROM node:22.14-bookworm-slim AS build-container
|
||||
WORKDIR /app
|
||||
RUN mkdir dictation_auto_transcription_file_server
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y curl
|
||||
COPY dictation_auto_transcription_file_server/ dictation_auto_transcription_file_server/
|
||||
RUN npm install --force -g n && n 22.14 \
|
||||
&& cd dictation_auto_transcription_file_server \
|
||||
&& npm ci \
|
||||
&& npm run build \
|
||||
&& cd ..
|
||||
RUN apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV TZ=Asia/Tokyo
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y tzdata \
|
||||
&& apt-get install -y unzip \
|
||||
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& dpkg-reconfigure -f noninteractive tzdata \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# nodeをbuild-containerからコピー
|
||||
COPY --from=build-container /usr/local/include/ /usr/local/include/
|
||||
COPY --from=build-container /usr/local/lib/ /usr/local/lib/
|
||||
COPY --from=build-container /usr/local/bin/ /usr/local/bin/
|
||||
# シンボリックリンクをリセット
|
||||
RUN corepack disable && corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
RUN mkdir build \
|
||||
&& mkdir dist \
|
||||
&& mkdir node_modules \
|
||||
# 変換ツールのパス
|
||||
&& mkdir bin
|
||||
COPY --from=build-container app/dictation_auto_transcription_file_server/dist/ dist/
|
||||
COPY --from=build-container app/dictation_auto_transcription_file_server/.env ./
|
||||
COPY --from=build-container app/dictation_auto_transcription_file_server/node_modules/ node_modules/
|
||||
COPY --from=build-container app/dictation_auto_transcription_file_server/bin/ bin/
|
||||
ARG BUILD_VERSION
|
||||
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||
# 変換ツールのパスを通す
|
||||
ENV PATH="/app/bin:$PATH"
|
||||
CMD ["node", "./dist/main.js" ]
|
||||
@ -1,15 +1,18 @@
|
||||
FROM node:18.17.1-buster AS build-container
|
||||
FROM node:22.14-bookworm AS build-container
|
||||
WORKDIR /app
|
||||
RUN mkdir dictation_server
|
||||
COPY dictation_server/ dictation_server/
|
||||
RUN npm install --force -g n && n 18.17.1 \
|
||||
RUN npm install --force -g n && n 22.14 \
|
||||
&& cd dictation_server \
|
||||
&& npm ci \
|
||||
&& npm run build \
|
||||
&& cd ..
|
||||
|
||||
FROM node:18.17.1-alpine
|
||||
RUN apk --no-cache add tzdata \
|
||||
FROM node:22.14-alpine
|
||||
RUN apk add --no-cache \
|
||||
tzdata \
|
||||
zip \
|
||||
unzip \
|
||||
&& cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
|
||||
&& apk del tzdata \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
81
azure-pipelines-production-only-convert-server.yml
Normal file
81
azure-pipelines-production-only-convert-server.yml
Normal file
@ -0,0 +1,81 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
tags:
|
||||
include:
|
||||
- pre-release-*
|
||||
|
||||
# Job 1 : Initialize
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin main:main
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) main; then
|
||||
echo "This commit is in the main branch."
|
||||
else
|
||||
echo "This commit is not in the main branch."
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがmainブランチに存在するか確認'
|
||||
|
||||
# Job 2 : Convert Audio File Service Deploy
|
||||
- job: convert_audio_file_service_deploy
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Convert Audio File Service Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
appName: 'app-odms-convert-audio-prod'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'odms-prod-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/auto_transcription:$(Build.SourceVersion)'
|
||||
|
||||
# Job 3 : Smoke Test
|
||||
- job: smoke_test
|
||||
dependsOn: convert_audio_file_service_deploy
|
||||
condition: succeeded('convert_audio_file_service_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
|
||||
# Job 4 : Convert Audio File Service Slot Swap
|
||||
- job: convert_audio_file_swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Convert Audio File Service Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-convert-audio-prod'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-convert-audio-prod'
|
||||
ResourceGroupName: 'odms-prod-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
@ -5,6 +5,7 @@ trigger:
|
||||
include:
|
||||
- release-*
|
||||
|
||||
# Job 1 : Initialize
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
@ -24,6 +25,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがmainブランチに存在するか確認'
|
||||
|
||||
# Job 2 : Backend Deploy
|
||||
- job: backend_deploy
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
@ -42,6 +45,8 @@ jobs:
|
||||
resourceGroupName: 'odms-prod-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||
|
||||
# Job 3 : Frontend Deploy
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
@ -83,6 +88,8 @@ jobs:
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
|
||||
# Job 4 : Function Deploy
|
||||
- job: function_deploy
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
@ -98,9 +105,31 @@ jobs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
appName: 'func-odms-dictation-prod'
|
||||
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||
- job: smoke_test
|
||||
|
||||
# Job 5 : Convert Audio File Service Deploy
|
||||
- job: convert_audio_file_service_deploy
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: Convert Audio File Service Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
appName: 'app-odms-convert-audio-prod'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'odms-prod-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/auto_transcription:$(Build.SourceVersion)'
|
||||
|
||||
# Job 6 : Smoke Test
|
||||
- job: smoke_test
|
||||
dependsOn: convert_audio_file_service_deploy
|
||||
condition: succeeded('convert_audio_file_service_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
@ -109,10 +138,12 @@ jobs:
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
|
||||
# Job 7 : Backend Slot Swap
|
||||
- job: backend_swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Staging and Production'
|
||||
displayName: 'Swap Backend Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
@ -128,9 +159,32 @@ jobs:
|
||||
ResourceGroupName: 'odms-prod-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 8 : Convert Audio File Service Slot Swap
|
||||
- job: convert_audio_file_swap_slot
|
||||
dependsOn: backend_swap_slot
|
||||
condition: succeeded('backend_swap_slot')
|
||||
displayName: 'Swap Convert Audio File Service Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-convert-audio-prod'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-convert-audio-prod'
|
||||
ResourceGroupName: 'odms-prod-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 9 : DB migration
|
||||
- job: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('swap_slot')
|
||||
dependsOn: convert_audio_file_swap_slot
|
||||
condition: succeeded('convert_audio_file_swap_slot')
|
||||
displayName: DB migration
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
|
||||
488
azure-pipelines-staging-ph2.yml
Normal file
488
azure-pipelines-staging-ph2.yml
Normal file
@ -0,0 +1,488 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- release-ph2
|
||||
tags:
|
||||
include:
|
||||
- stage-*
|
||||
|
||||
# Job 1 : Initialize
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin release-ph2:release-ph2
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph2; then
|
||||
echo "Commit is in the release-ph2 branch."
|
||||
else
|
||||
echo "Commit is not in the release-ph2 branch."
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがrelease-ph2ブランチに存在するか確認'
|
||||
|
||||
# Job 2 : Backend Test
|
||||
- job: backend_test
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Unit Test Backend
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Backend Unit Tests)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_server/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_server sudo npm ci
|
||||
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||
docker-compose exec -T dictation_server sudo npm run test
|
||||
|
||||
# Job 3 : Backend Build & Push
|
||||
- job: backend_build
|
||||
dependsOn: backend_test
|
||||
condition: succeeded('backend_test')
|
||||
displayName: Build and Push Backend Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_server
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: Build Backend Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerDictation.dockerfile
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: Push Backend Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
|
||||
# Job 4 : Frontend Staging Build
|
||||
- job: frontend_build_staging
|
||||
dependsOn: backend_build
|
||||
condition: succeeded('backend_build')
|
||||
displayName: Build Frontend Files(staging)
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: staging
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_client
|
||||
verbose: false
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: cd dictation_client && npm run build:stg
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: dictation_client/build
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob upload \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
|
||||
# Job 5 : Frontend Production Build
|
||||
- job: frontend_build_production
|
||||
dependsOn: frontend_build_staging
|
||||
condition: succeeded('frontend_build_staging')
|
||||
displayName: Build Frontend Files(production)
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: production
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_client
|
||||
verbose: false
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: cd dictation_client && npm run build:prod
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: dictation_client/build
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob upload \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
|
||||
# Job 6 : Function Unit Test
|
||||
- job: function_test
|
||||
dependsOn: frontend_build_production
|
||||
condition: succeeded('frontend_build_production')
|
||||
displayName: Unit Test Function
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_function/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_function sudo npm ci
|
||||
docker-compose exec -T dictation_function sudo npm run test
|
||||
|
||||
# Job 7 : Function Build & Push
|
||||
- job: function_build
|
||||
dependsOn: function_test
|
||||
condition: succeeded('function_test')
|
||||
displayName: Build And Push Function Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_function
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileFunctionDictation.dockerfile
|
||||
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||
|
||||
# Job 8 : Convert Audio File Test
|
||||
- job: convert_audio_file_service_test
|
||||
dependsOn: function_build
|
||||
condition: succeeded('function_build')
|
||||
displayName: Unit Test Convert Audio File
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Convert Audio File Unit Tests)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_auto_transcription_file_server/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_auto_transcription_file_server sudo npm ci
|
||||
docker-compose exec -T dictation_auto_transcription_file_server npm run test
|
||||
|
||||
# Job 9 : Convert Audio File Build & Push
|
||||
- job: convert_audio_file_service_build
|
||||
dependsOn: convert_audio_file_service_test
|
||||
condition: succeeded('convert_audio_file_service_test')
|
||||
displayName: Build and Push Convert Audio File Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_auto_transcription_file_server
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: Build Convert Audio File Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerAutoTranscription.dockerfile
|
||||
imageName: odmscloud/staging/auto_transcription:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: Push Convert Audio File Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/auto_transcription:$(Build.SourceVersion)
|
||||
|
||||
# Job 10 : Backend Deploy
|
||||
- job: backend_deploy
|
||||
dependsOn: convert_audio_file_service_build
|
||||
condition: succeeded('convert_audio_file_service_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'app-odms-dictation-stg'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'stg-application-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||
|
||||
# Job 11 : Frontend Deploy
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: staging
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob download \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||
- task: AzureStaticWebApp@0
|
||||
displayName: 'Static Web App: '
|
||||
inputs:
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
app_location: '/$(Build.SourceVersion)'
|
||||
config_file_location: /dictation_client
|
||||
skip_app_build: true
|
||||
skip_api_build: true
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
|
||||
# Job 12 : Function Deploy
|
||||
- job: function_deploy
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
displayName: Function Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureFunctionAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'func-odms-dictation-stg'
|
||||
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||
|
||||
# Job 13 : Convert Audio File Deploy
|
||||
- job: convert_audio_file_service_deploy
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: Convert Audio File Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'app-odms-convert-audio-stg'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'stg-application-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/auto_transcription:$(Build.SourceVersion)'
|
||||
|
||||
# Job 14 : Smoke Test
|
||||
- job: smoke_test
|
||||
dependsOn: convert_audio_file_service_deploy
|
||||
condition: succeeded('convert_audio_file_service_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
|
||||
# Job 15 : Backend Slot Swap
|
||||
- job: backend_swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Backend Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-dictation-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 16 : Convert Audio File Slot Swap
|
||||
- job: convert_audio_file_swap_slot
|
||||
dependsOn: backend_swap_slot
|
||||
condition: succeeded('backend_swap_slot')
|
||||
displayName: 'Swap Convert Audio File Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-convert-audio-stg'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-convert-audio-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 17 : DB migration
|
||||
- job: migration
|
||||
dependsOn: convert_audio_file_swap_slot
|
||||
condition: succeeded('convert_audio_file_swap_slot')
|
||||
displayName: DB migration
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: 'omds-service-connection-stg'
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name-ph2)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||
sql-migrate --version
|
||||
cat ./dictation_server/db/dbconfig.yml
|
||||
# migration実行
|
||||
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||
@ -8,6 +8,7 @@ trigger:
|
||||
include:
|
||||
- stage-*
|
||||
|
||||
# Job 1 : Initialize
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
@ -27,10 +28,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがmainブランチに存在するか確認'
|
||||
# Job 2 : Backend Test
|
||||
- job: backend_test
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: UnitTest
|
||||
displayName: Unit Test Backend
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
@ -38,23 +40,25 @@ jobs:
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
displayName: Bash Script (Backend Unit Tests)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_server/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_server sudo npm ci
|
||||
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||
docker-compose exec -T dictation_server sudo npm run test
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_server sudo npm ci
|
||||
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||
docker-compose exec -T dictation_server sudo npm run test
|
||||
|
||||
# Job 3 : Backend Build & Push
|
||||
- job: backend_build
|
||||
dependsOn: backend_test
|
||||
condition: succeeded('backend_test')
|
||||
displayName: Build And Push Backend Image
|
||||
displayName: Build and Push Backend Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
@ -68,7 +72,7 @@ jobs:
|
||||
workingDir: dictation_server
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
displayName: Build Backend Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
@ -77,12 +81,14 @@ jobs:
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
displayName: Push Backend Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
|
||||
# Job 4 : Frontend Staging Build
|
||||
- job: frontend_build_staging
|
||||
dependsOn: backend_build
|
||||
condition: succeeded('backend_build')
|
||||
@ -128,6 +134,8 @@ jobs:
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
|
||||
# Job 5 : Frontend Production Build
|
||||
- job: frontend_build_production
|
||||
dependsOn: frontend_build_staging
|
||||
condition: succeeded('frontend_build_staging')
|
||||
@ -173,10 +181,12 @@ jobs:
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
|
||||
# Job 6 : Function Unit Test
|
||||
- job: function_test
|
||||
dependsOn: frontend_build_production
|
||||
condition: succeeded('frontend_build_production')
|
||||
displayName: UnitTest
|
||||
displayName: Unit Test Function
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
@ -196,6 +206,8 @@ jobs:
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_function sudo npm ci
|
||||
docker-compose exec -T dictation_function sudo npm run test
|
||||
|
||||
# Job 7 : Function Build & Push
|
||||
- job: function_build
|
||||
dependsOn: function_test
|
||||
condition: succeeded('function_test')
|
||||
@ -228,9 +240,70 @@ jobs:
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||
- job: backend_deploy
|
||||
|
||||
# Job 8 : Convert Audio File Test
|
||||
- job: convert_audio_file_service_test
|
||||
dependsOn: function_build
|
||||
condition: succeeded('function_build')
|
||||
displayName: Unit Test Convert Audio File
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Convert Audio File Unit Tests)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_auto_transcription_file_server/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_auto_transcription_file_server sudo npm ci
|
||||
docker-compose exec -T dictation_auto_transcription_file_server npm run test
|
||||
|
||||
# Job 9 : Convert Audio File Build & Push
|
||||
- job: convert_audio_file_service_build
|
||||
dependsOn: convert_audio_file_service_test
|
||||
condition: succeeded('convert_audio_file_service_test')
|
||||
displayName: Build and Push Convert Audio File Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_auto_transcription_file_server
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: Build Convert Audio File Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerAutoTranscription.dockerfile
|
||||
imageName: odmscloud/staging/auto_transcription:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: Push Convert Audio File Image
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/auto_transcription:$(Build.SourceVersion)
|
||||
|
||||
# Job 10 : Backend Deploy
|
||||
- job: backend_deploy
|
||||
dependsOn: convert_audio_file_service_build
|
||||
condition: succeeded('convert_audio_file_service_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
@ -246,6 +319,8 @@ jobs:
|
||||
resourceGroupName: 'stg-application-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||
|
||||
# Job 11 : Frontend Deploy
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
@ -287,6 +362,8 @@ jobs:
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
|
||||
# Job 12 : Function Deploy
|
||||
- job: function_deploy
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
@ -302,9 +379,31 @@ jobs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'func-odms-dictation-stg'
|
||||
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||
- job: smoke_test
|
||||
|
||||
# Job 13 : Convert Audio File Deploy
|
||||
- job: convert_audio_file_service_deploy
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: Convert Audio File Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'app-odms-convert-audio-stg'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'stg-application-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/auto_transcription:$(Build.SourceVersion)'
|
||||
|
||||
# Job 14 : Smoke Test
|
||||
- job: smoke_test
|
||||
dependsOn: convert_audio_file_service_deploy
|
||||
condition: succeeded('convert_audio_file_service_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
@ -313,10 +412,12 @@ jobs:
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
|
||||
# Job 15 : Backend Slot Swap
|
||||
- job: backend_swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Staging and Production'
|
||||
displayName: 'Swap Backend Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
@ -332,9 +433,32 @@ jobs:
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 16 : Convert Audio File Slot Swap
|
||||
- job: convert_audio_file_swap_slot
|
||||
dependsOn: backend_swap_slot
|
||||
condition: succeeded('backend_swap_slot')
|
||||
displayName: 'Swap Convert Audio File Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-convert-audio-stg'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-convert-audio-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
|
||||
# Job 17 : DB migration
|
||||
- job: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('swap_slot')
|
||||
dependsOn: convert_audio_file_swap_slot
|
||||
condition: succeeded('convert_audio_file_swap_slot')
|
||||
displayName: DB migration
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
@ -351,7 +475,7 @@ jobs:
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:18.13.0-buster
|
||||
FROM node:22.14-bookworm
|
||||
|
||||
RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
|
||||
echo "Asia/Tokyo" > /etc/timezone
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV TZ=Asia/Tokyo
|
||||
|
||||
# Options for setup script
|
||||
ARG INSTALL_ZSH="true"
|
||||
ARG UPGRADE_PACKAGES="false"
|
||||
ARG USERNAME=vscode
|
||||
# 1000 はnodeで使われているためずらす
|
||||
ARG USER_UID=1001
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
|
||||
COPY library-scripts/common-debian.sh /tmp/library-scripts/
|
||||
RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
|
||||
&& apt-get install default-jre -y \
|
||||
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
|
||||
|
||||
RUN \
|
||||
apt-get install -y tzdata && \
|
||||
ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||
dpkg-reconfigure -f noninteractive tzdata && \
|
||||
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
|
||||
apt-get install -y nodejs build-essential
|
||||
|
||||
# Update NPM
|
||||
RUN npm install -g npm
|
||||
|
||||
# Install NestJS
|
||||
RUN npm i -g @nestjs/cli
|
||||
|
||||
# 以下 ユーザー権限で実施
|
||||
USER $USERNAME
|
||||
# copy init-script
|
||||
COPY --chown=$USERNAME:$USERNAME init.sh /home/${USERNAME}/
|
||||
RUN chmod +x /home/${USERNAME}/init.sh
|
||||
|
||||
# 変換ツールのパスを通す
|
||||
ENV PATH="/app/dictation_auto_transcription_file_server/bin/:$PATH"
|
||||
|
||||
# 初期化を行う
|
||||
# node imageのデフォルトENTRYPOINTが邪魔するため上書き
|
||||
ENTRYPOINT /home/vscode/init.sh
|
||||
@ -0,0 +1,56 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node
|
||||
{
|
||||
"name": "Dev Dictation Auto Transcription File Server",
|
||||
"dockerComposeFile": [
|
||||
"docker-compose.yml"
|
||||
],
|
||||
"service": "dictation_auto_transcription_file_server",
|
||||
// コンテナを自動停止させない
|
||||
"shutdownAction": "none",
|
||||
"workspaceFolder": "/app/dictation_auto_transcription_file_server",
|
||||
"runArgs": [
|
||||
"--cap-add=SYS_PTRACE",
|
||||
"--security-opt",
|
||||
"seccomp=unconfined"
|
||||
],
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.format.enable": false,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
// formatter
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnType": true,
|
||||
"editor.renderWhitespace": "all",
|
||||
"editor.insertSpaces": false,
|
||||
"editor.renderLineHighlight": "all"
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"salbert.comment-ts",
|
||||
"gruntfuggly.todo-tree",
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-vsliveshare.vsliveshare",
|
||||
"albymor.increment-selection",
|
||||
"eamodio.gitlens",
|
||||
"wmaurer.change-case"
|
||||
],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "yarn install",
|
||||
"postCreateCommand": "sudo chown -R vscode:vscode /app/dictation_auto_transcription_file_server",
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
services:
|
||||
dictation_auto_transcription_file_server:
|
||||
container_name: dictation_auto_transcription_file_server_dev_container
|
||||
env_file: ../.env
|
||||
build: .
|
||||
working_dir: /app/dictation_auto_transcription_file_server
|
||||
# platform: linux/x86_64
|
||||
ports:
|
||||
- '8083:8083'
|
||||
volumes:
|
||||
- ../../:/app
|
||||
- node_modules:/app/dictation_auto_transcription_file_server/node_modules:delegate
|
||||
expose:
|
||||
- '8081'
|
||||
environment:
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
networks:
|
||||
- external
|
||||
volumes:
|
||||
node_modules:
|
||||
networks:
|
||||
external:
|
||||
name: omds_network
|
||||
external: true
|
||||
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Init Script for server Container
|
||||
#
|
||||
|
||||
echo [init.sh] dictation_auto_transcription_file_server initialize.
|
||||
|
||||
# /app の権限がデフォルトでは node ユーザーになっているため、
|
||||
# 権限確認し、vscode ユーザでない場合付け替える
|
||||
ls -ld /app | grep vscode
|
||||
if [ $? -ne 0 ]; then
|
||||
echo [init.sh] change /app owner to vscode.
|
||||
sudo chown -R vscode:vscode /app
|
||||
fi
|
||||
|
||||
cd /app/dictation_auto_transcription_file_server
|
||||
|
||||
echo [init.sh] initialize completed!
|
||||
|
||||
sleep infinity
|
||||
@ -0,0 +1,454 @@
|
||||
#!/usr/bin/env bash
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages]
|
||||
|
||||
set -e
|
||||
|
||||
INSTALL_ZSH=${1:-"true"}
|
||||
USERNAME=${2:-"automatic"}
|
||||
USER_UID=${3:-"automatic"}
|
||||
USER_GID=${4:-"automatic"}
|
||||
UPGRADE_PACKAGES=${5:-"true"}
|
||||
INSTALL_OH_MYS=${6:-"true"}
|
||||
ADD_NON_FREE_PACKAGES=${7:-"false"}
|
||||
SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)"
|
||||
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# If in automatic mode, determine if a user already exists, if not use vscode
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=vscode
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ]; then
|
||||
USERNAME=root
|
||||
USER_UID=0
|
||||
USER_GID=0
|
||||
fi
|
||||
|
||||
# Load markers to see which steps have already run
|
||||
if [ -f "${MARKER_FILE}" ]; then
|
||||
echo "Marker file found:"
|
||||
cat "${MARKER_FILE}"
|
||||
source "${MARKER_FILE}"
|
||||
fi
|
||||
|
||||
# Ensure apt is in non-interactive to avoid prompts
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Function to call apt-get if needed
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
else
|
||||
echo "Skipping apt-get update."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
|
||||
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
|
||||
|
||||
package_list="apt-utils \
|
||||
openssh-client \
|
||||
gnupg2 \
|
||||
dirmngr \
|
||||
iproute2 \
|
||||
procps \
|
||||
lsof \
|
||||
htop \
|
||||
net-tools \
|
||||
psmisc \
|
||||
curl \
|
||||
wget \
|
||||
rsync \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
zip \
|
||||
nano \
|
||||
vim-tiny \
|
||||
less \
|
||||
jq \
|
||||
lsb-release \
|
||||
apt-transport-https \
|
||||
dialog \
|
||||
libc6 \
|
||||
libgcc1 \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
libicu[0-9][0-9] \
|
||||
liblttng-ust[0-9] \
|
||||
libstdc++6 \
|
||||
zlib1g \
|
||||
locales \
|
||||
sudo \
|
||||
ncdu \
|
||||
man-db \
|
||||
strace \
|
||||
manpages \
|
||||
manpages-dev \
|
||||
init-system-helpers"
|
||||
|
||||
# Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian
|
||||
if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then
|
||||
# Bring in variables from /etc/os-release like VERSION_CODENAME
|
||||
. /etc/os-release
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
# Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html
|
||||
sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
package_list="${package_list} manpages-posix manpages-posix-dev"
|
||||
else
|
||||
apt_get_update_if_needed
|
||||
fi
|
||||
|
||||
# Install libssl1.1 if available
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
|
||||
package_list="${package_list} libssl1.1"
|
||||
fi
|
||||
|
||||
# Install appropriate version of libssl1.0.x if available
|
||||
libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
|
||||
if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
|
||||
# Debian 9
|
||||
package_list="${package_list} libssl1.0.2"
|
||||
elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
|
||||
# Ubuntu 18.04, 16.04, earlier
|
||||
package_list="${package_list} libssl1.0.0"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Packages to verify are installed: ${package_list}"
|
||||
apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
|
||||
|
||||
# Install git if not already installed (may be more recent than distro version)
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
PACKAGES_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Get to latest versions of all packages
|
||||
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
|
||||
apt_get_update_if_needed
|
||||
apt-get -y upgrade --no-install-recommends
|
||||
apt-get autoremove -y
|
||||
fi
|
||||
|
||||
# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
|
||||
# Common need for both applications and things like the agnoster ZSH theme.
|
||||
if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||
locale-gen
|
||||
LOCALE_ALREADY_SET="true"
|
||||
fi
|
||||
|
||||
# Create or update a non-root user to match UID/GID.
|
||||
group_name="${USERNAME}"
|
||||
if id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
# User exists, update if needed
|
||||
if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then
|
||||
group_name="$(id -gn $USERNAME)"
|
||||
groupmod --gid $USER_GID ${group_name}
|
||||
usermod --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
|
||||
usermod --uid $USER_UID $USERNAME
|
||||
fi
|
||||
else
|
||||
# Create user
|
||||
if [ "${USER_GID}" = "automatic" ]; then
|
||||
groupadd $USERNAME
|
||||
else
|
||||
groupadd --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "${USER_UID}" = "automatic" ]; then
|
||||
useradd -s /bin/bash --gid $USERNAME -m $USERNAME
|
||||
else
|
||||
useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add sudo support for non-root user
|
||||
if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
|
||||
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
EXISTING_NON_ROOT_USER="${USERNAME}"
|
||||
fi
|
||||
|
||||
# ** Shell customization section **
|
||||
if [ "${USERNAME}" = "root" ]; then
|
||||
user_rc_path="/root"
|
||||
else
|
||||
user_rc_path="/home/${USERNAME}"
|
||||
fi
|
||||
|
||||
# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then
|
||||
cp /etc/skel/.bashrc "${user_rc_path}/.bashrc"
|
||||
fi
|
||||
|
||||
# Restore user .profile defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then
|
||||
cp /etc/skel/.profile "${user_rc_path}/.profile"
|
||||
fi
|
||||
|
||||
# .bashrc/.zshrc snippet
|
||||
rc_snippet="$(cat << 'EOF'
|
||||
|
||||
if [ -z "${USER}" ]; then export USER=$(whoami); fi
|
||||
if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi
|
||||
|
||||
# Display optional first run image specific notice if configured and terminal is interactive
|
||||
if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then
|
||||
if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then
|
||||
cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt"
|
||||
elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then
|
||||
cat "/workspaces/.codespaces/shared/first-run-notice.txt"
|
||||
fi
|
||||
mkdir -p "$HOME/.config/vscode-dev-containers"
|
||||
# Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it
|
||||
((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &)
|
||||
fi
|
||||
|
||||
# Set the default git editor if not already set
|
||||
if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then
|
||||
if [ "${TERM_PROGRAM}" = "vscode" ]; then
|
||||
if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then
|
||||
export GIT_EDITOR="code-insiders --wait"
|
||||
else
|
||||
export GIT_EDITOR="code --wait"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# code shim, it fallbacks to code-insiders if code is not available
|
||||
cat << 'EOF' > /usr/local/bin/code
|
||||
#!/bin/sh
|
||||
|
||||
get_in_path_except_current() {
|
||||
which -a "$1" | grep -A1 "$0" | grep -v "$0"
|
||||
}
|
||||
|
||||
code="$(get_in_path_except_current code)"
|
||||
|
||||
if [ -n "$code" ]; then
|
||||
exec "$code" "$@"
|
||||
elif [ "$(command -v code-insiders)" ]; then
|
||||
exec code-insiders "$@"
|
||||
else
|
||||
echo "code or code-insiders is not installed" >&2
|
||||
exit 127
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/code
|
||||
|
||||
# systemctl shim - tells people to use 'service' if systemd is not running
|
||||
cat << 'EOF' > /usr/local/bin/systemctl
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ -d "/run/systemd/system" ]; then
|
||||
exec /bin/systemctl "$@"
|
||||
else
|
||||
echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all'
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/systemctl
|
||||
|
||||
# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme
|
||||
codespaces_bash="$(cat \
|
||||
<<'EOF'
|
||||
|
||||
# Codespaces bash prompt theme
|
||||
__bash_prompt() {
|
||||
local userpart='`export XIT=$? \
|
||||
&& [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \
|
||||
&& [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`'
|
||||
local gitbranch='`\
|
||||
if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \
|
||||
export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \
|
||||
if [ "${BRANCH}" != "" ]; then \
|
||||
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \
|
||||
&& if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \
|
||||
echo -n " \[\033[1;33m\]✗"; \
|
||||
fi \
|
||||
&& echo -n "\[\033[0;36m\]) "; \
|
||||
fi; \
|
||||
fi`'
|
||||
local lightblue='\[\033[1;34m\]'
|
||||
local removecolor='\[\033[0m\]'
|
||||
PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ "
|
||||
unset -f __bash_prompt
|
||||
}
|
||||
__bash_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
codespaces_zsh="$(cat \
|
||||
<<'EOF'
|
||||
# Codespaces zsh prompt theme
|
||||
__zsh_prompt() {
|
||||
local prompt_username
|
||||
if [ ! -z "${GITHUB_USER}" ]; then
|
||||
prompt_username="@${GITHUB_USER}"
|
||||
else
|
||||
prompt_username="%n"
|
||||
fi
|
||||
PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow
|
||||
PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd
|
||||
PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status
|
||||
PROMPT+='%{$fg[white]%}$ %{$reset_color%}'
|
||||
unset -f __zsh_prompt
|
||||
}
|
||||
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}"
|
||||
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
|
||||
ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})"
|
||||
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})"
|
||||
__zsh_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Add RC snippet and custom bash prompt
|
||||
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/bash.bashrc
|
||||
echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc"
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
echo "${codespaces_bash}" >> "/root/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc"
|
||||
fi
|
||||
chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc"
|
||||
RC_SNIPPET_ALREADY_ADDED="true"
|
||||
fi
|
||||
|
||||
# Optionally install and configure zsh and Oh My Zsh!
|
||||
if [ "${INSTALL_ZSH}" = "true" ]; then
|
||||
if ! type zsh > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get install -y zsh
|
||||
fi
|
||||
if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/zsh/zshrc
|
||||
ZSH_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme.
|
||||
# See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script.
|
||||
oh_my_install_dir="${user_rc_path}/.oh-my-zsh"
|
||||
if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then
|
||||
template_path="${oh_my_install_dir}/templates/zshrc.zsh-template"
|
||||
user_rc_file="${user_rc_path}/.zshrc"
|
||||
umask g-w,o-w
|
||||
mkdir -p ${oh_my_install_dir}
|
||||
git clone --depth=1 \
|
||||
-c core.eol=lf \
|
||||
-c core.autocrlf=false \
|
||||
-c fsck.zeroPaddedFilemode=ignore \
|
||||
-c fetch.fsck.zeroPaddedFilemode=ignore \
|
||||
-c receive.fsck.zeroPaddedFilemode=ignore \
|
||||
"https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1
|
||||
echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file}
|
||||
sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file}
|
||||
|
||||
mkdir -p ${oh_my_install_dir}/custom/themes
|
||||
echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme"
|
||||
# Shrink git while still enabling updates
|
||||
cd "${oh_my_install_dir}"
|
||||
git repack -a -d -f --depth=1 --window=1
|
||||
# Copy to non-root user if one is specified
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root
|
||||
chown -R ${USERNAME}:${group_name} "${user_rc_path}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Persist image metadata info, script if meta.env found in same directory
|
||||
meta_info_script="$(cat << 'EOF'
|
||||
#!/bin/sh
|
||||
. /usr/local/etc/vscode-dev-containers/meta.env
|
||||
|
||||
# Minimal output
|
||||
if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then
|
||||
echo "${VERSION}"
|
||||
exit 0
|
||||
elif [ "$1" = "release" ]; then
|
||||
echo "${GIT_REPOSITORY_RELEASE}"
|
||||
exit 0
|
||||
elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then
|
||||
echo "${CONTENTS_URL}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#Full output
|
||||
echo
|
||||
echo "Development container image information"
|
||||
echo
|
||||
if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi
|
||||
if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi
|
||||
if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi
|
||||
if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi
|
||||
if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi
|
||||
echo
|
||||
EOF
|
||||
)"
|
||||
if [ -f "${SCRIPT_DIR}/meta.env" ]; then
|
||||
mkdir -p /usr/local/etc/vscode-dev-containers/
|
||||
cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env
|
||||
echo "${meta_info_script}" > /usr/local/bin/devcontainer-info
|
||||
chmod +x /usr/local/bin/devcontainer-info
|
||||
fi
|
||||
|
||||
# Write marker file
|
||||
mkdir -p "$(dirname "${MARKER_FILE}")"
|
||||
echo -e "\
|
||||
PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
|
||||
LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
|
||||
EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
|
||||
RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\
|
||||
ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
|
||||
|
||||
echo "Done!"
|
||||
@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
dictation_auto_transcription_file_server:
|
||||
container_name: dictation_auto_transcription_file_server_dev_container
|
||||
env_file: ../.env
|
||||
build: .
|
||||
working_dir: /app/dictation_auto_transcription_file_server
|
||||
ports:
|
||||
- '8083:8083'
|
||||
volumes:
|
||||
- ../../:/app
|
||||
- node_modules:/app/dictation_auto_transcription_file_server/node_modules
|
||||
expose:
|
||||
- '8083'
|
||||
environment:
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
volumes:
|
||||
node_modules:
|
||||
0
dictation_auto_transcription_file_server/.env
Normal file
0
dictation_auto_transcription_file_server/.env
Normal file
16
dictation_auto_transcription_file_server/.env.local.example
Normal file
16
dictation_auto_transcription_file_server/.env.local.example
Normal file
@ -0,0 +1,16 @@
|
||||
STAGE=local
|
||||
NO_COLOR=TRUE
|
||||
CORS=TRUE
|
||||
PORT=8083
|
||||
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5IZZNgDew9eGmuFTezwdHYLSaJvUPPIKYoiOeVLD1paWNI51\n7Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3yCTR6wcWR3PfFJrl9vh5SOo79koZ\noJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbWFJXnDe0DVXYXpJLb4LAlF2XAyYX0\nSYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qSfiL9zWk9dvHoKzSnfSDzDFoFcEoV\nchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//mBNNaDHv83Yuw3mGShT73iJ0JQdk\nTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GOOQIDAQABAoIBADrwp7u097+dK/tw\nWD61n3DIGAqg/lmFt8X4IH8MKLSE/FKr16CS1bqwOEuIM3ZdUtDeXd9Xs7IsyEPE\n5ZwuXK7DSF0M4+Mj8Ip49Q0Aww9aUoLQU9HGfgN/r4599GTrt31clZXA/6Mlighq\ncOZgCcEfdItz8OMu5SQuOIW4CKkCuaWnPOP26UqZocaXNZfpZH0iFLATMMH/TT8x\nay9ToHTQYE17ijdQ/EOLSwoeDV1CU1CIE3P4YfLJjvpKptly5dTevriHEzBi70Jx\n/KEPUn9Jj2gZafrUxRVhmMbm1zkeYxL3gsqRuTzRjEeeILuZhSJyCkQZyUNARxsg\nQY4DZfECgYEA+YLKUtmYTx60FS6DJ4s31TAsXY8kwhq/lB9E3GBZKDd0DPayXEeK\n4UWRQDTT6MI6fedW69FOZJ5sFLp8HQpcssb4Weq9PCpDhNTx8MCbdH3Um5QR3vfW\naKq/1XM8MDUnx5XcNYd87Aw3azvJAvOPr69as8IPnj6sKaRR9uQjbYUCgYEA6nfV\n5j0qmn0EJXZJblk4mvvjLLoWSs17j9YlrZJlJxXMDFRYtgnelv73xMxOMvcGoxn5\nifs7dpaM2x5EmA6jVU5sYaB/beZGEPWqPYGyjIwXPvUGAAv8Gbnvpp+xlSco/Dum\nIq0w+43ry5/xWh6CjfrvKV0J2bDOiJwPEdu/8iUCgYEAnBBSvL+dpN9vhFAzeOh7\nY71eAqcmNsLEUcG9MJqTKbSFwhYMOewF0iHRWHeylEPokhfBJn8kqYrtz4lVWFTC\n5o/Nh3BsLNXCpbMMIapXkeWiti1HgE9ErPMgSkJpwz18RDpYIqM8X+jEQS6D7HSr\nyxfDg+w+GJza0rEVE3hfMIECgYBw+KZ2VfhmEWBjEHhXE+QjQMR3s320MwebCUqE\nNCpKx8TWF/naVC0MwfLtvqbbBY0MHyLN6d//xpA9r3rLbRojqzKrY2KiuDYAS+3n\nzssRzxoQOozWju+8EYu30/ADdqfXyIHG6X3VZs87AGiQzGyJLmP3oR1y5y7MQa09\nJI16hQKBgHK5uwJhGa281Oo5/FwQ3uYLymbNwSGrsOJXiEu2XwJEXwVi2ELOKh4/\n03pBk3Kva3fIwEK+vCzDNnxShIQqBE76/2I1K1whOfoUehhYvKHGaXl2j70Zz9Ks\nrkGW1cx7p+yDqATDrwHBHTHFh5bUTTn8dN40n0e0W/llurpbBkJM\n-----END RSA PRIVATE KEY-----\n"
|
||||
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
STORAGE_ACCOUNT_NAME_US=saodmsusdev
|
||||
STORAGE_ACCOUNT_NAME_AU=saodmsaudev
|
||||
STORAGE_ACCOUNT_NAME_EU=saodmseudev
|
||||
STORAGE_ACCOUNT_KEY_US=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX
|
||||
STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
|
||||
AUDIO_FILE_ZIP_PASSWORD=***********
|
||||
17
dictation_auto_transcription_file_server/.env.test
Normal file
17
dictation_auto_transcription_file_server/.env.test
Normal file
@ -0,0 +1,17 @@
|
||||
STAGE=production
|
||||
NO_COLOR=TRUE
|
||||
CORS=TRUE
|
||||
PORT=8083
|
||||
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5IZZNgDew9eGmuFTezwdHYLSaJvUPPIKYoiOeVLD1paWNI51\n7Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3yCTR6wcWR3PfFJrl9vh5SOo79koZ\noJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbWFJXnDe0DVXYXpJLb4LAlF2XAyYX0\nSYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qSfiL9zWk9dvHoKzSnfSDzDFoFcEoV\nchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//mBNNaDHv83Yuw3mGShT73iJ0JQdk\nTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GOOQIDAQABAoIBADrwp7u097+dK/tw\nWD61n3DIGAqg/lmFt8X4IH8MKLSE/FKr16CS1bqwOEuIM3ZdUtDeXd9Xs7IsyEPE\n5ZwuXK7DSF0M4+Mj8Ip49Q0Aww9aUoLQU9HGfgN/r4599GTrt31clZXA/6Mlighq\ncOZgCcEfdItz8OMu5SQuOIW4CKkCuaWnPOP26UqZocaXNZfpZH0iFLATMMH/TT8x\nay9ToHTQYE17ijdQ/EOLSwoeDV1CU1CIE3P4YfLJjvpKptly5dTevriHEzBi70Jx\n/KEPUn9Jj2gZafrUxRVhmMbm1zkeYxL3gsqRuTzRjEeeILuZhSJyCkQZyUNARxsg\nQY4DZfECgYEA+YLKUtmYTx60FS6DJ4s31TAsXY8kwhq/lB9E3GBZKDd0DPayXEeK\n4UWRQDTT6MI6fedW69FOZJ5sFLp8HQpcssb4Weq9PCpDhNTx8MCbdH3Um5QR3vfW\naKq/1XM8MDUnx5XcNYd87Aw3azvJAvOPr69as8IPnj6sKaRR9uQjbYUCgYEA6nfV\n5j0qmn0EJXZJblk4mvvjLLoWSs17j9YlrZJlJxXMDFRYtgnelv73xMxOMvcGoxn5\nifs7dpaM2x5EmA6jVU5sYaB/beZGEPWqPYGyjIwXPvUGAAv8Gbnvpp+xlSco/Dum\nIq0w+43ry5/xWh6CjfrvKV0J2bDOiJwPEdu/8iUCgYEAnBBSvL+dpN9vhFAzeOh7\nY71eAqcmNsLEUcG9MJqTKbSFwhYMOewF0iHRWHeylEPokhfBJn8kqYrtz4lVWFTC\n5o/Nh3BsLNXCpbMMIapXkeWiti1HgE9ErPMgSkJpwz18RDpYIqM8X+jEQS6D7HSr\nyxfDg+w+GJza0rEVE3hfMIECgYBw+KZ2VfhmEWBjEHhXE+QjQMR3s320MwebCUqE\nNCpKx8TWF/naVC0MwfLtvqbbBY0MHyLN6d//xpA9r3rLbRojqzKrY2KiuDYAS+3n\nzssRzxoQOozWju+8EYu30/ADdqfXyIHG6X3VZs87AGiQzGyJLmP3oR1y5y7MQa09\nJI16hQKBgHK5uwJhGa281Oo5/FwQ3uYLymbNwSGrsOJXiEu2XwJEXwVi2ELOKh4/\n03pBk3Kva3fIwEK+vCzDNnxShIQqBE76/2I1K1whOfoUehhYvKHGaXl2j70Zz9Ks\nrkGW1cx7p+yDqATDrwHBHTHFh5bUTTn8dN40n0e0W/llurpbBkJM\n-----END RSA PRIVATE KEY-----\n"
|
||||
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
STORAGE_ACCOUNT_NAME_US=saxxxxusxxx
|
||||
STORAGE_ACCOUNT_NAME_AU=saxxxxauxxx
|
||||
STORAGE_ACCOUNT_NAME_EU=saxxxxeuxxx
|
||||
STORAGE_ACCOUNT_KEY_US=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==
|
||||
STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==
|
||||
STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==
|
||||
STORAGE_ACCOUNT_ENDPOINT_US=https://xxxxxxxxxxxx.blob.core.windows.net/
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU=https://xxxxxxxxxxxx.blob.core.windows.net/
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU=https://xxxxxxxxxxxx.blob.core.windows.net/
|
||||
# ↓src/features/convert-audio-file/test/testfile/zipのパスワード
|
||||
AUDIO_FILE_ZIP_PASSWORD=password
|
||||
25
dictation_auto_transcription_file_server/.eslintrc.js
Normal file
25
dictation_auto_transcription_file_server/.eslintrc.js
Normal file
@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir : __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
13
dictation_auto_transcription_file_server/.gitignore
vendored
Normal file
13
dictation_auto_transcription_file_server/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/dist
|
||||
/node_modules
|
||||
/dump.rdb
|
||||
/build
|
||||
/openapi/build
|
||||
/.test
|
||||
|
||||
# credentials
|
||||
credentials
|
||||
.env.local
|
||||
|
||||
work_folder/
|
||||
!work_folder/.gitkeep
|
||||
4
dictation_auto_transcription_file_server/.prettierrc
Normal file
4
dictation_auto_transcription_file_server/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
21
dictation_auto_transcription_file_server/.vscode/launch.json
vendored
Normal file
21
dictation_auto_transcription_file_server/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "start:debug"],
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "UnitTest",
|
||||
"port": 9229,
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
}
|
||||
]
|
||||
}
|
||||
24
dictation_auto_transcription_file_server/.vscode/settings.json
vendored
Normal file
24
dictation_auto_transcription_file_server/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"eslint.format.enable": false,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnType": true,
|
||||
"editor.renderWhitespace": "all",
|
||||
"editor.insertSpaces": false,
|
||||
"editor.renderLineHighlight": "all",
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"typescript.preferences.importModuleSpecifier": "relative"
|
||||
|
||||
}
|
||||
BIN
dictation_auto_transcription_file_server/bin/dec2wav.out
Executable file
BIN
dictation_auto_transcription_file_server/bin/dec2wav.out
Executable file
Binary file not shown.
9
dictation_auto_transcription_file_server/nest-cli.json
Normal file
9
dictation_auto_transcription_file_server/nest-cli.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"assets": ["templates/**/*.html", "templates/**/*.txt"],
|
||||
"watchAssets": true
|
||||
}
|
||||
}
|
||||
12084
dictation_auto_transcription_file_server/package-lock.json
generated
Normal file
12084
dictation_auto_transcription_file_server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
111
dictation_auto_transcription_file_server/package.json
Normal file
111
dictation_auto_transcription_file_server/package.json
Normal file
@ -0,0 +1,111 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"apigen": "ts-node src/api/generate.ts && prettier --write \"src/api/odms/*.json\"",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"src/api/odms/*.json\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"tc": "tsc --noEmit",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest -w 1",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"og": "openapi-generator-cli",
|
||||
"openapi-format": "cat \"src/api/odms/openapi.json\" | jq -c . > \"src/api/odms/openapi.json\" && prettier --write \"src/api/odms/*.json\"",
|
||||
"tokengen": "ts-node src/common/test/tokengen.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/identity": "^3.1.3",
|
||||
"@azure/keyvault-secrets": "^4.6.0",
|
||||
"@azure/notification-hubs": "^1.0.3",
|
||||
"@azure/storage-blob": "^12.14.0",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.5",
|
||||
"@nestjs/axios": "^0.1.0",
|
||||
"@nestjs/common": "^9.3.12",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.3.12",
|
||||
"@nestjs/platform-express": "^9.3.12",
|
||||
"@nestjs/serve-static": "^3.0.1",
|
||||
"@openapitools/openapi-generator-cli": "^0.0.6",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"axios": "^1.3.4",
|
||||
"cache-manager": "^5.2.4",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"connect-redis": "^6.1.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express-session": "^1.17.3",
|
||||
"helmet": "^6.0.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jwk-to-pem": "^2.0.5",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-cli": "^4.0.4",
|
||||
"@nestjs/cli": "^9.3.0",
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/swagger": "^6.3.0",
|
||||
"@nestjs/testing": "^9.3.12",
|
||||
"@types/cache-manager": "^4.0.4",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express-session": "^1.17.5",
|
||||
"@types/jest": "27.5.0",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/jwk-to-pem": "^2.0.1",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
"base64url": "^3.0.1",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "28.0.3",
|
||||
"license-checker": "^25.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"sqlite3": "^5.1.6",
|
||||
"supertest": "^6.1.3",
|
||||
"swagger-ui-express": "^4.5.0",
|
||||
"ts-jest": "28.0.1",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "4.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"testTimeout": 120000,
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
25
dictation_auto_transcription_file_server/src/api/generate.ts
Normal file
25
dictation_auto_transcription_file_server/src/api/generate.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { AppModule } from '../app.module';
|
||||
import { promises as fs } from 'fs';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
async function bootstrap(): Promise<void> {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
preview: true,
|
||||
});
|
||||
|
||||
const options = new DocumentBuilder()
|
||||
.setTitle('ODMS GenarateAutoTranscriptionFile OpenAPI')
|
||||
.setVersion('1.0.0')
|
||||
.addBearerAuth({
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
})
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
await fs.writeFile(
|
||||
'src/api/odms/openapi.json',
|
||||
JSON.stringify(document, null, 0),
|
||||
);
|
||||
}
|
||||
bootstrap();
|
||||
@ -0,0 +1,143 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/health": {
|
||||
"get": {
|
||||
"operationId": "checkHealth",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
"responses": { "200": { "description": "" } }
|
||||
}
|
||||
},
|
||||
"/convert-audio-file": {
|
||||
"post": {
|
||||
"operationId": "generateAutoTranscriptionFile",
|
||||
"summary": "",
|
||||
"description": "自動文字起こし用ファイルを生成する",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "x-api-version",
|
||||
"in": "header",
|
||||
"description": "APIバージョン",
|
||||
"schema": { "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenerateAutoTranscriptionFileRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenerateAutoTranscriptionFileResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "不正なパラメータ",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "指定したIDの音声ファイルが存在しない場合",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["convert-audio-file"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "ODMS GenarateAutoTranscriptionFile OpenAPI",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
"servers": [],
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearer": { "scheme": "bearer", "bearerFormat": "JWT", "type": "http" }
|
||||
},
|
||||
"schemas": {
|
||||
"GenerateAutoTranscriptionFileRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"taskId": { "type": "number", "description": "タスクID" },
|
||||
"country": {
|
||||
"type": "string",
|
||||
"description": "アカウントが所属している国情報"
|
||||
},
|
||||
"accountId": { "type": "number", "description": "アカウントID" },
|
||||
"audioFileName": {
|
||||
"type": "string",
|
||||
"description": "音声ファイル名"
|
||||
},
|
||||
"encryptionPassword": {
|
||||
"type": "string",
|
||||
"description": "復号化パスワード"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"taskId",
|
||||
"country",
|
||||
"accountId",
|
||||
"audioFileName",
|
||||
"encryptionPassword"
|
||||
]
|
||||
},
|
||||
"GenerateAutoTranscriptionFileResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"voiceFileName": {
|
||||
"type": "string",
|
||||
"description": "自動文字起こし用音声ファイル(wav形式)"
|
||||
}
|
||||
},
|
||||
"required": ["voiceFileName"]
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": { "type": "string" },
|
||||
"code": { "type": "string" }
|
||||
},
|
||||
"required": ["message", "code"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
dictation_auto_transcription_file_server/src/app.module.ts
Normal file
27
dictation_auto_transcription_file_server/src/app.module.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { MiddlewareConsumer, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { LoggerMiddleware } from './common/loggerMiddleware';
|
||||
import { VersionHeaderMiddleware } from './common/version-header.middleware';
|
||||
import { validate } from './common/validators/env.validator';
|
||||
import { ConvertAudioFileModule } from './features/convert-audio-file/convert-audio-file.module';
|
||||
import { BlobstorageModule } from './gateways/blobstorage/blobstorage.module';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
isGlobal: true,
|
||||
validate,
|
||||
}),
|
||||
ConvertAudioFileModule,
|
||||
BlobstorageModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(LoggerMiddleware).forRoutes('');
|
||||
consumer.apply(VersionHeaderMiddleware).forRoutes('');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
E03XXXX : 自動文字起こし用音声ファイル変換ツールのエラーコード
|
||||
*/
|
||||
export const ErrorCodes = [
|
||||
'E009999', // 汎用エラー
|
||||
'E000101', // トークン形式不正エラー
|
||||
'E000107', // トークン不足エラー
|
||||
'E000401', // IPアドレス未設定エラー
|
||||
'E000501', // リクエストID未設定エラー
|
||||
'E010701', // Blobファイル不在エラー
|
||||
'E031000', // 入力ファイルのオープンに失敗
|
||||
'E031001', // 入力ファイルが不正なフォーマット
|
||||
'E031002', // 入力ファイルが非サポートの拡張子
|
||||
'E031003', // 入力ファイルの読み出しに失敗
|
||||
'E031004', // 入力ファイルが他社のdssファイル
|
||||
'E032000', // 出力ファイルのオープンに失敗
|
||||
'E032001', // 出力ファイルのwavヘッダー書き出しに失敗
|
||||
'E032002', // 出力データの書き出し中に失敗
|
||||
'E033000', // ds2ファイルの暗号化パスワードの指定がない
|
||||
'E033001', // ds2ファイルの暗号化パスワードが異なる
|
||||
'E034000', // mp3の初期化APIが失敗
|
||||
'E034001', // mp3のオブジェクト生成に失敗
|
||||
'E034002', // mp3のフォーマット設定に失敗
|
||||
'E034003', // mp3のフォーマット取得に失敗
|
||||
'E034004', // mp3のブロック出力に失敗
|
||||
'E034005', // mp3のデータ読み出しに失敗
|
||||
'E034006', // mp3ファイルの終了に失敗
|
||||
'E039999', // 未定義のエラー
|
||||
] as const;
|
||||
@ -0,0 +1,10 @@
|
||||
import { errors } from './message';
|
||||
import { ErrorCodeType, ErrorResponse } from './types/types';
|
||||
|
||||
export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => {
|
||||
const msg = errors[errorcode];
|
||||
return {
|
||||
code: errorcode,
|
||||
message: msg,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { Errors } from './types/types';
|
||||
|
||||
// エラーコードとメッセージ対応表
|
||||
export const errors: Errors = {
|
||||
E009999: 'Internal Server Error.',
|
||||
E000101: 'Token invalid format Error.',
|
||||
E000107: 'Token is not exist Error.',
|
||||
E000401: 'IP address not found Error.',
|
||||
E000501: 'Request ID not found Error.',
|
||||
E010701: 'File not found in Blob Storage Error.',
|
||||
E031000: 'Audio convert: Failed to open input file.',
|
||||
E031001: 'Audio convert: Invalid input file format.',
|
||||
E031002: 'Audio convert: Unsupported file extension.',
|
||||
E031003: 'Audio convert: Unable to read input file.',
|
||||
E031004: 'Audio convert: Input file is a third-party DSS format.',
|
||||
E032000: 'Audio convert: Unable to open output file.',
|
||||
E032001: 'Audio convert: Failed to write WAV header.',
|
||||
E032002: 'Audio convert: Error while writing output data.',
|
||||
E033000: 'Audio convert: DS2 encryption password is not specified.',
|
||||
E033001: 'Audio convert: Incorrect DS2 encryption password.',
|
||||
E034000: 'Audio convert: MP3 initialization API failed.',
|
||||
E034001: 'Audio convert: Failed to generate MP3 object.',
|
||||
E034002: 'Audio convert: Failed to configure MP3 format.',
|
||||
E034003: 'Audio convert: Failed to retrieve MP3 format.',
|
||||
E034004: 'Audio convert: Failed during MP3 block output.',
|
||||
E034005: 'Audio convert: Failed to read MP3 data.',
|
||||
E034006: 'Audio convert: Failed to finalize MP3 file.',
|
||||
E039999: 'Audio convert: Undefined error code.',
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ErrorCodes } from '../code';
|
||||
|
||||
export class ErrorResponse {
|
||||
@ApiProperty()
|
||||
message: string;
|
||||
@ApiProperty()
|
||||
code: string;
|
||||
}
|
||||
|
||||
export type ErrorCodeType = (typeof ErrorCodes)[number];
|
||||
|
||||
export type Errors = {
|
||||
[P in ErrorCodeType]: string;
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* フォルダを再帰的に削除します。
|
||||
* @param dirPath 削除したいディレクトリパス
|
||||
*/
|
||||
export const rmDirRecursive = async (dirPath: string) => {
|
||||
if (!existsSync(dirPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = await fs.readdir(dirPath);
|
||||
for (const item of items) {
|
||||
const deleteTarget = path.join(dirPath, item);
|
||||
// 削除対象がフォルダの場合、階層を掘って再帰的に削除しに行く
|
||||
if ((await fs.lstat(deleteTarget)).isDirectory()) {
|
||||
await rmDirRecursive(deleteTarget);
|
||||
} else {
|
||||
await fs.unlink(deleteTarget);
|
||||
}
|
||||
}
|
||||
await fs.rmdir(dirPath);
|
||||
};
|
||||
|
||||
/**
|
||||
* 処理用のワーキングディレクトリを作成する
|
||||
* @param baseDir 基底ディレクトリ
|
||||
* @param suffix 接尾辞
|
||||
*/
|
||||
export const makeWorkingDirectory = async (
|
||||
baseDir: string,
|
||||
suffix: string,
|
||||
): Promise<string> => {
|
||||
const workDirPath = path.join(baseDir, suffix);
|
||||
await fs.mkdir(workDirPath);
|
||||
|
||||
return workDirPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* ファイル名を小文字に変換する
|
||||
* @param fileName 変換前のファイル名
|
||||
* @returns ファイル名を小文字に変換して返す
|
||||
*/
|
||||
export const toLowerCaseFileName = async (fileName: string) => {
|
||||
const lowerCaseFileName = fileName.toLocaleLowerCase();
|
||||
await fs.rename(fileName, lowerCaseFileName);
|
||||
|
||||
return lowerCaseFileName;
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './fileSystem';
|
||||
export * from './zip';
|
||||
@ -0,0 +1,66 @@
|
||||
import * as fs from 'fs';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* パスワード保護付きZIPファイルを解凍します。
|
||||
* @param zipFilePath ZIPファイルが保存されているパス
|
||||
* @param outputFilePathBaseDir ファイル解凍先の基点ディレクトリ
|
||||
* @param zipFilePassword 解凍パスワード
|
||||
*/
|
||||
export const extractPasswordZipFile = async (
|
||||
zipFilePath: string,
|
||||
outputFilePathBaseDir: string,
|
||||
zipFilePassword: string,
|
||||
): Promise<string[]> => {
|
||||
try {
|
||||
await new Promise((resolve, reject: (stderrData: string) => void) => {
|
||||
// ZIP解凍処理
|
||||
const unzipFile = childProcess.spawn('unzip', [
|
||||
'-P',
|
||||
zipFilePassword,
|
||||
zipFilePath,
|
||||
'-d',
|
||||
outputFilePathBaseDir,
|
||||
]);
|
||||
|
||||
let stdoutData = '';
|
||||
let stderrData = '';
|
||||
|
||||
// 標準出力を受け取る
|
||||
unzipFile.stdout.on('data', (data) => {
|
||||
stdoutData += data.toString();
|
||||
});
|
||||
|
||||
// 標準エラー出力を受け取る
|
||||
unzipFile.stderr.on('data', (data) => {
|
||||
stderrData += data.toString();
|
||||
});
|
||||
|
||||
// プロセスが終了したときの処理
|
||||
// unzipコマンドによって出力されたステータスコードによって条件分岐する
|
||||
unzipFile.on('close', (code) => {
|
||||
// 正常にZIPの解凍が実行された場合
|
||||
if (code === 0) {
|
||||
resolve(stdoutData);
|
||||
} else {
|
||||
// ZIPの解凍がに失敗した場合
|
||||
reject(stderrData);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
// ZIPファイルを解凍したフォルダに配置されているファイルのファイル名を取得
|
||||
const filenames = fs.readdirSync(outputFilePathBaseDir);
|
||||
|
||||
return (
|
||||
filenames
|
||||
// 音声ファイル変換するファイル名のみを取得するため、ZIPファイルは取り除く
|
||||
// ZIPの拡張子が大文字の場合があるため、小文字変換してから取り除く
|
||||
.filter((filename) => !filename.toLowerCase().endsWith('.zip'))
|
||||
// ZIPファイルから取得した音声ファイルのファイルのパスを作成する。
|
||||
.map((filename) => path.join(outputFilePathBaseDir, filename))
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SystemAccessGuard } from './accessguards';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
controllers: [],
|
||||
providers: [SystemAccessGuard],
|
||||
})
|
||||
export class SystemAccessGuardsModule {}
|
||||
@ -0,0 +1,43 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { isVerifyError, verify } from '../../jwt';
|
||||
import { Request } from 'express';
|
||||
import { retrieveAuthorizationToken } from '../../../common/http/helper';
|
||||
import { makeErrorResponse } from '../../../common/error/makeErrorResponse';
|
||||
import { SystemAccessToken } from '../../token/types';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getPublicKey } from '../../jwt/jwt';
|
||||
/**
|
||||
* システム間通信用のトークンを検証するガード
|
||||
**/
|
||||
@Injectable()
|
||||
export class SystemAccessGuard implements CanActivate {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
const req = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
if (!token) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const payload = verify<SystemAccessToken>(token, pubkey);
|
||||
if (isVerifyError(payload)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
/**
|
||||
* Authorizationヘッダに格納された文字列(jwt)を取得します
|
||||
* @param {Request}
|
||||
* @return {string | undefined}
|
||||
*/
|
||||
export const retrieveAuthorizationToken = (
|
||||
req: Request,
|
||||
): string | undefined => {
|
||||
const header = req.header('Authorization');
|
||||
|
||||
if (typeof header === 'string') {
|
||||
if (header.startsWith('Bearer ')) {
|
||||
return header.substring('Bearer '.length, header.length);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
import { isVerifyError, sign, verify, decode } from './jwt';
|
||||
|
||||
export { isVerifyError, sign, verify, decode };
|
||||
145
dictation_auto_transcription_file_server/src/common/jwt/jwt.ts
Normal file
145
dictation_auto_transcription_file_server/src/common/jwt/jwt.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
// XXX: decodeがうまく使えないことがあるので応急対応 バージョン9以降だとなる?
|
||||
import { decode as jwtDecode } from 'jsonwebtoken';
|
||||
|
||||
export type VerifyError = {
|
||||
reason: 'ExpiredError' | 'InvalidToken' | 'InvalidTimeStamp' | 'Unknown';
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const isVerifyError = (arg: unknown): arg is VerifyError => {
|
||||
const value = arg as VerifyError;
|
||||
if (value.message === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.reason === undefined) {
|
||||
return false;
|
||||
}
|
||||
switch (value.reason) {
|
||||
case 'ExpiredError':
|
||||
case 'InvalidTimeStamp':
|
||||
case 'InvalidToken':
|
||||
case 'Unknown':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Payloadと秘密鍵を使用して署名されたJWTを生成します
|
||||
* @param {T} payload payloadの型
|
||||
* @param {number} expirationSeconds トークンの有効期限(秒)
|
||||
* @param {string} privateKey 署名に使用する秘密鍵
|
||||
* @return {string} 署名済みトークン
|
||||
* @throws {Error} 秘密鍵の形式が間違っている等の理由が格納されたErrorオブジェクト
|
||||
*/
|
||||
export const sign = <T extends object>(
|
||||
payload: T,
|
||||
expirationSeconds: number,
|
||||
privateKey: string,
|
||||
): string => {
|
||||
try {
|
||||
const token = jwt.sign(payload, privateKey, {
|
||||
expiresIn: expirationSeconds,
|
||||
algorithm: 'RS256',
|
||||
});
|
||||
return token;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* tokenと公開鍵を使用して検証済みJWTのpayloadを取得します
|
||||
* @param {string} token JWT
|
||||
* @param {string} publicKey 検証に使用する公開鍵
|
||||
* @return {T | VerifyError} Payload または 検証エラーの内容を表すオブジェクト
|
||||
*/
|
||||
export const verify = <T extends object>(
|
||||
token: string,
|
||||
publicKey: string,
|
||||
): T | VerifyError => {
|
||||
try {
|
||||
const payload = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
}) as T;
|
||||
return payload;
|
||||
} catch (e) {
|
||||
if (e instanceof jwt.TokenExpiredError) {
|
||||
return {
|
||||
reason: 'ExpiredError',
|
||||
message: e.message,
|
||||
};
|
||||
} else if (e instanceof jwt.NotBeforeError) {
|
||||
return {
|
||||
reason: 'InvalidTimeStamp',
|
||||
message: e.message,
|
||||
};
|
||||
} else if (e instanceof jwt.JsonWebTokenError) {
|
||||
return {
|
||||
reason: 'InvalidToken',
|
||||
message: e.message,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
reason: 'Unknown',
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* tokenから未検証のJWTのpayloadを取得します
|
||||
* @param {string} token JWT
|
||||
* @return {T | VerifyError} Payload または デコードエラーの内容を表すオブジェクト
|
||||
*/
|
||||
export const decode = <T extends object>(token: string): T | VerifyError => {
|
||||
try {
|
||||
const payload = jwtDecode(token, {
|
||||
json: true,
|
||||
}) as T;
|
||||
return payload;
|
||||
} catch (e) {
|
||||
if (e instanceof jwt.TokenExpiredError) {
|
||||
return {
|
||||
reason: 'ExpiredError',
|
||||
message: e.message,
|
||||
};
|
||||
} else if (e instanceof jwt.NotBeforeError) {
|
||||
return {
|
||||
reason: 'InvalidTimeStamp',
|
||||
message: e.message,
|
||||
};
|
||||
} else if (e instanceof jwt.JsonWebTokenError) {
|
||||
return {
|
||||
reason: 'InvalidToken',
|
||||
message: e.message,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
reason: 'Unknown',
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getPrivateKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.getOrThrow<string>('JWT_PRIVATE_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
|
||||
export const getPublicKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.getOrThrow<string>('JWT_PUBLIC_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { Request } from 'express';
|
||||
import { Context } from './types';
|
||||
|
||||
export const makeContext = (
|
||||
externalId: string,
|
||||
requestId: string,
|
||||
delegationId?: string,
|
||||
): Context => {
|
||||
return new Context(externalId, requestId, delegationId);
|
||||
};
|
||||
|
||||
// リクエストヘッダーからrequestIdを取得する
|
||||
export const retrieveRequestId = (req: Request): string | undefined => {
|
||||
return req.header('x-request-id');
|
||||
};
|
||||
|
||||
/**
|
||||
* リクエストのIPアドレスを取得します
|
||||
* @param {Request} req
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export const retrieveIp = (req: Request): string | undefined => {
|
||||
// ローカル環境では直近の送信元IPを取得する
|
||||
if (process.env.STAGE === 'local') {
|
||||
return req.ip;
|
||||
}
|
||||
const ip = req.header('x-forwarded-for');
|
||||
if (typeof ip === 'string') {
|
||||
return ip;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
import { Context } from './types';
|
||||
import { makeContext, retrieveRequestId, retrieveIp } from './context';
|
||||
|
||||
export { Context, makeContext, retrieveRequestId, retrieveIp };
|
||||
@ -0,0 +1,34 @@
|
||||
export class Context {
|
||||
/**
|
||||
* APIの操作ユーザーを追跡するためのID
|
||||
*/
|
||||
trackingId: string;
|
||||
/**
|
||||
* APIの操作ユーザーのIPアドレス
|
||||
*/
|
||||
ip: string;
|
||||
/**
|
||||
* ユーザーの操作を一意に識別するためのID
|
||||
*/
|
||||
requestId: string;
|
||||
/**
|
||||
* APIの代行操作ユーザーを追跡するためのID
|
||||
*/
|
||||
delegationId?: string | undefined;
|
||||
|
||||
constructor(externalId: string, requestId: string, delegationId?: string) {
|
||||
this.trackingId = externalId;
|
||||
this.delegationId = delegationId;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
/**
|
||||
* ログにユーザーを特定する情報を出力する
|
||||
*/
|
||||
getTrackingId(): string {
|
||||
if (this.delegationId) {
|
||||
return `${this.requestId}_${this.trackingId} by ${this.delegationId}`;
|
||||
} else {
|
||||
return `${this.requestId}_${this.trackingId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
private readonly logger = new Logger(LoggerMiddleware.name);
|
||||
|
||||
use(req: Request, res: Response, next: () => void): void {
|
||||
// ここで一意のリクエストIDを生成して、リクエストヘッダーに設定する
|
||||
const requestId = uuidv4();
|
||||
req.headers['x-request-id'] = requestId;
|
||||
|
||||
this.logger.log(this.createReqMsg(req));
|
||||
|
||||
res.on('close', () => {
|
||||
this.logger.log(this.createResMsg(res));
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
private createReqMsg(req: Request): string {
|
||||
const message = `[${req.header('x-request-id')}] Request [url=${
|
||||
req.url
|
||||
}, method=${req.method}]`;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private createResMsg(res: Response): string {
|
||||
const message = `[${res.req.header('x-request-id')}] Response [statusCode=${
|
||||
res.statusCode
|
||||
}, message=${res.statusMessage}]`;
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ConvertAudioFileService } from '../../features/convert-audio-file/convert-audio-file.service';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
import { CacheModule } from '@nestjs/common';
|
||||
|
||||
export const makeTestingModule = async (): Promise<
|
||||
TestingModule | undefined
|
||||
> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.test', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
BlobstorageModule,
|
||||
CacheModule.register({ isGlobal: true, ttl: 86400 }),
|
||||
],
|
||||
providers: [ConvertAudioFileService],
|
||||
}).compile();
|
||||
|
||||
return module;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import { Context } from '../log';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
|
||||
// ### ユニットテスト用コード以外では絶対に使用してはいけないダーティな手段を使用しているが、他の箇所では使用しないこと ###
|
||||
|
||||
/**
|
||||
* blobStorageServiceのモックを作成して、TServiceが依存するサービス(blobStorageService)の参照を上書きする
|
||||
* ※ serviceに指定するオブジェクトは`blobstorageService: blobStorageService`メンバ変数を持つ必要がある
|
||||
* @param service 上書きしたいTService
|
||||
* @param overrides blobStorageServiceの各種メソッドのモックが返す値(省略した場合は既定のダミーの値)
|
||||
*/
|
||||
export const overrideBlobstorageService = <TService>(
|
||||
service: TService,
|
||||
overrides: {
|
||||
containerExists?: (
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
) => Promise<boolean>;
|
||||
fileExists?: (
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
fileName: string,
|
||||
) => Promise<boolean>;
|
||||
downloadFile?: (
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
fileName: string,
|
||||
downloadFilePath: string,
|
||||
) => Promise<void>;
|
||||
uploadFile?: (
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
taskId: number,
|
||||
fileName: string,
|
||||
localFilePath: string,
|
||||
) => Promise<void>;
|
||||
},
|
||||
): void => {
|
||||
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
|
||||
const obj = (service as any).blobStorageService as BlobstorageService;
|
||||
if (overrides.containerExists) {
|
||||
Object.defineProperty(obj, obj.containerExists.name, {
|
||||
value: overrides.containerExists,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (overrides.fileExists) {
|
||||
Object.defineProperty(obj, obj.fileExists.name, {
|
||||
value: overrides.fileExists,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (overrides.downloadFile) {
|
||||
Object.defineProperty(obj, obj.downloadFile.name, {
|
||||
value: overrides.downloadFile,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (overrides.uploadFile) {
|
||||
Object.defineProperty(obj, obj.uploadFile.name, {
|
||||
value: overrides.uploadFile,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,37 @@
|
||||
import { sign } from '../jwt/jwt';
|
||||
import { SystemAccessToken } from '../token/types';
|
||||
import { AppModule } from '../../app.module';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
|
||||
/**
|
||||
* テスト用: API認証トークンを発行する
|
||||
*/
|
||||
async function bootstrap(): Promise<void> {
|
||||
await NestFactory.create(AppModule, {
|
||||
preview: true,
|
||||
});
|
||||
// システムトークンを発行
|
||||
const systemToken = await generateSystemToken();
|
||||
console.log(`{ system_token: ${systemToken} }`);
|
||||
}
|
||||
/**
|
||||
* System用のアクセストークンを生成します
|
||||
* @param context
|
||||
* @returns system token
|
||||
*/
|
||||
async function generateSystemToken(): Promise<string> {
|
||||
// 要求されたトークンの寿命を決定
|
||||
// デフォルト2時間
|
||||
const tokenLifetime = Number(process.env.ACCESS_TOKEN_LIFETIME_WEB || 7200);
|
||||
const privateKey = process.env.JWT_PRIVATE_KEY?.replace(/\\n/g, '\n') ?? '';
|
||||
const token = sign<SystemAccessToken>(
|
||||
{
|
||||
externalId: 'test',
|
||||
},
|
||||
tokenLifetime,
|
||||
privateKey,
|
||||
);
|
||||
return token;
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
@ -0,0 +1 @@
|
||||
export * from './types';
|
||||
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* システムの内部で発行し、外部に公開しないトークン
|
||||
* システム間通信用(例: ODMS Cloud API → 音声ファイル変換API)に使用する
|
||||
*/
|
||||
export type SystemAccessToken = {
|
||||
/**
|
||||
* トークンの発行者名(ログ記録用)
|
||||
*/
|
||||
externalId: string;
|
||||
|
||||
/**
|
||||
* 付加情報を 文字情報として格納できる
|
||||
*/
|
||||
context?: string;
|
||||
};
|
||||
@ -0,0 +1,95 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
validateSync,
|
||||
} from 'class-validator';
|
||||
|
||||
/**
|
||||
* 環境変数の型定義
|
||||
*/
|
||||
export class EnvValidator {
|
||||
// .env
|
||||
|
||||
// .env.local
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
STAGE: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
NO_COLOR: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
CORS: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
PORT: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
JWT_PRIVATE_KEY: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
JWT_PUBLIC_KEY: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_NAME_US: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_NAME_AU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_NAME_EU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_KEY_US: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_KEY_AU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_KEY_EU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_ENDPOINT_US: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_ENDPOINT_AU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
STORAGE_ACCOUNT_ENDPOINT_EU: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
AUDIO_FILE_ZIP_PASSWORD: string;
|
||||
}
|
||||
|
||||
export function validate(config: Record<string, unknown>) {
|
||||
const validatedConfig = plainToClass(EnvValidator, config, {
|
||||
enableImplicitConversion: true,
|
||||
});
|
||||
|
||||
const errors = validateSync(validatedConfig, {
|
||||
skipMissingProperties: false,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors.toString());
|
||||
}
|
||||
return validatedConfig;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
/**
|
||||
* VersionHeaderMiddleware
|
||||
* リクエストヘッダのAPIバージョン情報を管理するミドルウェア
|
||||
*/
|
||||
@Injectable()
|
||||
export class VersionHeaderMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction): void {
|
||||
// リクエストヘッダにAPIバージョンが含まれていない場合、デフォルトのAPIバージョンを利用するようにする。
|
||||
if (!Object.keys(req.headers).includes('x-api-version')) {
|
||||
// 環境変数にDEFAULT_API_VERSIONを追加する
|
||||
req.headers['x-api-version'] = process.env.DEFAULT_API_VERSION || '1';
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 音声ファイルをEast USに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_US = ['CA', 'KY', 'US'];
|
||||
|
||||
/**
|
||||
* 音声ファイルをAustralia Eastに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_AU = ['AU', 'NZ'];
|
||||
|
||||
/**
|
||||
* 音声ファイルをNorth Europeに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_EU = [
|
||||
'AT',
|
||||
'BE',
|
||||
'BG',
|
||||
'HR',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DK',
|
||||
'EE',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'GR',
|
||||
'HU',
|
||||
'IS',
|
||||
'IE',
|
||||
'IT',
|
||||
'LV',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'MT',
|
||||
'NL',
|
||||
'NO',
|
||||
'PL',
|
||||
'PT',
|
||||
'RO',
|
||||
'RS',
|
||||
'SK',
|
||||
'SI',
|
||||
'ZA',
|
||||
'ES',
|
||||
'SE',
|
||||
'CH',
|
||||
'TR',
|
||||
'GB',
|
||||
];
|
||||
/**
|
||||
* ユニットテスト実行をしている場合のNODE_ENVの値
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const NODE_ENV_TEST = 'test';
|
||||
@ -0,0 +1,23 @@
|
||||
import * as errors from '../errors/types';
|
||||
|
||||
export const AUDIO_FILE_CONVERT_ERROR_MAP: {
|
||||
[key: number]: typeof errors.AudioFileConvertToolError;
|
||||
} = {
|
||||
1000: errors.InputAudioFileOpenError,
|
||||
1001: errors.InputAudioFileFormatError,
|
||||
1002: errors.InputAudioFileExtError,
|
||||
1003: errors.InputAudioFileReadError,
|
||||
1004: errors.InputAudioFileInvalidDssFormatError,
|
||||
2000: errors.OutputAudioFileOpenError,
|
||||
2001: errors.OutputAudioFileWriteHeaderError,
|
||||
2002: errors.OutputAudioFileWriteError,
|
||||
3000: errors.DS2AudioFileEncryptionPasswordNotSpecifiedError,
|
||||
3001: errors.DS2AudioFileEncryptionPasswordInvalidError,
|
||||
4000: errors.MP3AudioFileInitializeError,
|
||||
4001: errors.MP3AudioFileObjectGenerateError,
|
||||
4002: errors.MP3AudioFileFormatConfigurationError,
|
||||
4003: errors.MP3AudioFileGetFormatError,
|
||||
4004: errors.MP3AudioFileBlockOutputError,
|
||||
4005: errors.MP3AudioFileReadError,
|
||||
4006: errors.MP3AudioFileCloseError,
|
||||
};
|
||||
@ -0,0 +1,132 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
Version,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiResponse,
|
||||
ApiOperation,
|
||||
ApiTags,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { ErrorResponse } from '../../common/error/types/types';
|
||||
import { Request } from 'express';
|
||||
import { ConvertAudioFileService } from './convert-audio-file.service';
|
||||
import {
|
||||
GenerateAutoTranscriptionFileRequest,
|
||||
GenerateAutoTranscriptionFileResponse,
|
||||
} from './types/types';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { SystemAccessGuard } from '../../common/guards/system/accessguards';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { SystemAccessToken } from '../../common/token';
|
||||
|
||||
@ApiTags('convert-audio-file')
|
||||
@Controller('convert-audio-file')
|
||||
export class ConvertAudioFileController {
|
||||
private readonly logger = new Logger(ConvertAudioFileController.name);
|
||||
constructor(private readonly taskService: ConvertAudioFileService) {}
|
||||
|
||||
@Post()
|
||||
@Version(['1', '2'])
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GenerateAutoTranscriptionFileResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '不正なパラメータ',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.NOT_FOUND,
|
||||
description: '指定したIDの音声ファイルが存在しない場合',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'generateAutoTranscriptionFile',
|
||||
description: '自動文字起こし用ファイルを生成する',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@ApiHeader({
|
||||
name: 'x-api-version',
|
||||
description: 'APIバージョン',
|
||||
})
|
||||
@UseGuards(SystemAccessGuard)
|
||||
async generateAutoTranscriptionFile(
|
||||
@Req() req: Request,
|
||||
@Body() body: GenerateAutoTranscriptionFileRequest,
|
||||
): Promise<GenerateAutoTranscriptionFileResponse> {
|
||||
// SystemAccessGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
|
||||
const { accountId, country, taskId, audioFileName, encryptionPassword } =
|
||||
body;
|
||||
const systemAccessToken = retrieveAuthorizationToken(req);
|
||||
if (!systemAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const decodedAccessToken = jwt.decode(systemAccessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { externalId } = decodedAccessToken as SystemAccessToken;
|
||||
|
||||
const context = makeContext(externalId, requestId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
const { voiceFileName } =
|
||||
await this.taskService.generateAutoTranscriptionFile(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
taskId,
|
||||
audioFileName,
|
||||
encryptionPassword,
|
||||
);
|
||||
|
||||
return { voiceFileName };
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ConvertAudioFileService } from './convert-audio-file.service';
|
||||
import { ConvertAudioFileController } from './convert-audio-file.controller';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
|
||||
@Module({
|
||||
imports: [BlobstorageModule, ConfigModule],
|
||||
providers: [ConvertAudioFileService],
|
||||
controllers: [ConvertAudioFileController],
|
||||
})
|
||||
export class ConvertAudioFileModule {}
|
||||
@ -0,0 +1,455 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import { existsSync } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { makeBlobstorageServiceMockValue } from './test/convert-audio-file.service.mock';
|
||||
import { makeTestingModuleWithBlob } from './test/utility';
|
||||
import { ConvertAudioFileService } from './convert-audio-file.service';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { rmDirRecursive } from '../../common/files';
|
||||
|
||||
const testFilePath = `${__dirname}/test/testfile/zip`;
|
||||
const workFolderPath = `${__dirname}/test/work_folder`;
|
||||
jest.mock('../../common/files/fileSystem', () => {
|
||||
const originalModule = jest.requireActual('../../common/files/fileSystem');
|
||||
return {
|
||||
...Object.assign({}, originalModule),
|
||||
makeWorkingDirectory: () => workFolderPath,
|
||||
};
|
||||
});
|
||||
|
||||
describe('generateAutoTranscriptionFile', () => {
|
||||
beforeEach(async () => {
|
||||
if (!existsSync(workFolderPath)) {
|
||||
await fs.mkdir(workFolderPath);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rmDirRecursive(workFolderPath);
|
||||
});
|
||||
|
||||
it('自動文字起こし用ファイルを生成できること(DS2暗号化なし)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
const { voiceFileName } = await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
|
||||
expect(voiceFileName).toBe(`TEST_NO_ENCRYPTION.ds2.wav`);
|
||||
});
|
||||
it('自動文字起こし用ファイルを生成できること(DS2暗号化あり)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_ENCRYPTION.DS2.zip'),
|
||||
path.join(workFolderPath, 'TEST_ENCRYPTION.DS2.zip'),
|
||||
);
|
||||
|
||||
const { voiceFileName } = await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_ENCRYPTION.DS2.zip',
|
||||
'OZ0030',
|
||||
);
|
||||
|
||||
expect(voiceFileName).toBe(`TEST_ENCRYPTION.DS2.wav`);
|
||||
});
|
||||
it('blobストレージにコンテナが存在しない場合はエラーになること', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
// コンテナが存在しないこととする
|
||||
blobStorageServiceMockValue.containerExists = false;
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('blobストレージに音声ファイルが存在しない場合はエラーになること', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
// ファイルが存在しないこととする
|
||||
blobStorageServiceMockValue.fileExists = false;
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('blobストレージに音声ファイルが存在しない場合はエラーになること', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
// ファイルが存在しないこととする
|
||||
blobStorageServiceMockValue.fileExists = false;
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('blobストレージからダウンロードに失敗した場合エラーになること', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
// ダウンロードに失敗したことにする
|
||||
blobStorageServiceMockValue.downloadFile = new Error('download faild.');
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('blobストレージへアップロードに失敗した場合エラーになること', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
// アップロードに失敗したことにする
|
||||
blobStorageServiceMockValue.uploadFile = new Error('upload faild.');
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ENCRYPTION.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('zipの解凍に失敗した時、エラーになること(zipファイルがない)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// zipファイルをテストフォルダからコピーしないことで、zipがないことをテスト
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ENCRYPTION.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('zipの解凍に失敗した時、エラーになること(パスワード不正)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
// パスワードが異なるzipファイル
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_UNMATCH_PASSWORD.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_UNMATCH_PASSWORD.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_UNMATCH_PASSWORD.ds2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('zipの解凍に失敗した時、エラーになること(zipファイルではない)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NO_ZIPFILE.DS2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NO_ZIPFILE.DS2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NO_ZIPFILE.DS2.zip',
|
||||
'password',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('音声ファイルの変換に失敗した時、エラーになること(パスワード不正)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_ENCRYPTION.DS2.zip'),
|
||||
path.join(workFolderPath, 'TEST_ENCRYPTION.DS2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_ENCRYPTION.DS2.zip',
|
||||
// 誤った復号化パスワードを指定
|
||||
'invalid',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E033001'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
it('音声ファイルの変換に失敗した時、エラー(フォーマット不正)', async () => {
|
||||
// BlobServiceをモック化
|
||||
const blobStorageServiceMockValue = makeBlobstorageServiceMockValue();
|
||||
const module = await makeTestingModuleWithBlob(blobStorageServiceMockValue);
|
||||
if (!module) throw new Error('module not defined.');
|
||||
const service = module.get<ConvertAudioFileService>(
|
||||
ConvertAudioFileService,
|
||||
);
|
||||
const context = makeContext('externalId', 'requestId');
|
||||
|
||||
// テスト用のファイルをワークディレクトリにコピー
|
||||
await fs.copyFile(
|
||||
path.join(testFilePath, 'TEST_NOT_DS2.ds2.zip'),
|
||||
path.join(workFolderPath, 'TEST_NOT_DS2.ds2.zip'),
|
||||
);
|
||||
|
||||
try {
|
||||
await service.generateAutoTranscriptionFile(
|
||||
context,
|
||||
1,
|
||||
'US',
|
||||
1,
|
||||
'TEST_NOT_DS2.ds2.zip',
|
||||
// 誤った復号化パスワードを指定
|
||||
'invalid',
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E031001'));
|
||||
return;
|
||||
} else {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
throw new Error('Error not thrown');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,247 @@
|
||||
import * as childProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
rmDirRecursive,
|
||||
makeWorkingDirectory,
|
||||
extractPasswordZipFile,
|
||||
toLowerCaseFileName,
|
||||
} from '../../common/files';
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { Context } from '../../common/log';
|
||||
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';
|
||||
import { GenerateAutoTranscriptionFileResponse } from './types/types';
|
||||
import * as errors from './errors';
|
||||
import { AUDIO_FILE_CONVERT_ERROR_MAP } from './constants';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ErrorCodeType } from '../../common/error/types/types';
|
||||
import { ErrorCodes } from '../../common/error/code';
|
||||
|
||||
@Injectable()
|
||||
export class ConvertAudioFileService {
|
||||
private readonly logger = new Logger(ConvertAudioFileService.name);
|
||||
private readonly zipFilePassword = this.configService.getOrThrow<string>(
|
||||
'AUDIO_FILE_ZIP_PASSWORD',
|
||||
);
|
||||
constructor(
|
||||
private readonly blobStorageService: BlobstorageService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 自動文字起こし用ファイルを生成する
|
||||
* @param accountId
|
||||
* @param country
|
||||
* @param taskId
|
||||
* @param audioFileName
|
||||
* @param encryptionPassword
|
||||
* @returns voiceFileName
|
||||
*/
|
||||
async generateAutoTranscriptionFile(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
taskId: number,
|
||||
audioFileName: string,
|
||||
encryptionPassword: string,
|
||||
): Promise<GenerateAutoTranscriptionFileResponse> {
|
||||
// 作業用ディレクトリを作成
|
||||
const workDirPath = await makeWorkingDirectory(
|
||||
process.cwd(),
|
||||
// ファイル変換ツールの仕様で、暗号化されていて拡張子が大文字の音声ファイルは読み込めず、変換に失敗する(エラーコード1000が出力される)。
|
||||
// 変換エラーを防ぐため音声ファイル名を小文字に変更してから変換ツールを通すが、
|
||||
// フォルダパスも含めて小文字に変更するため、ファイル読み込みエラーにならないようにあらかじめ小文字でフォルダを作っておく。
|
||||
context.getTrackingId().toLocaleLowerCase(),
|
||||
);
|
||||
try {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}]` +
|
||||
`${this.generateAutoTranscriptionFile.name} | params: { ` +
|
||||
`accountId: ${accountId}, ` +
|
||||
`country: ${country}, ` +
|
||||
`audioFileId: ${taskId}, ` +
|
||||
`audioFileName: ${audioFileName} ` +
|
||||
`};`,
|
||||
);
|
||||
|
||||
// コンテナが存在しないことは本来ありえないため、エラー
|
||||
if (
|
||||
!(await this.blobStorageService.containerExists(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
))
|
||||
) {
|
||||
throw new errors.StorageAccountContainerNotFoundError(
|
||||
`container not found. country: ${country}, accountId: ${accountId}`,
|
||||
);
|
||||
}
|
||||
// 音声ファイルが存在しないことは本来ありえないため、エラー
|
||||
if (
|
||||
!(await this.blobStorageService.fileExists(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
audioFileName,
|
||||
))
|
||||
) {
|
||||
throw new errors.AudioFileNotFoundError(
|
||||
`Audio file is not exists in blob storage. task_id:${taskId}, fileName:${audioFileName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 音声ファイル(zip)をダウンロード
|
||||
// trackingIdでワークフォルダを作って、音声ファイル(zip)をダウンロードする
|
||||
const workFilePath = path.join(workDirPath, audioFileName);
|
||||
await this.blobStorageService.downloadFile(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
audioFileName,
|
||||
workFilePath,
|
||||
);
|
||||
|
||||
let unzipFiles: string[];
|
||||
this.logger.log(`[${context.getTrackingId()}] zip file extract start.`);
|
||||
try {
|
||||
// ダウンロードしたzipファイルを解凍
|
||||
unzipFiles = await extractPasswordZipFile(
|
||||
workFilePath,
|
||||
workDirPath,
|
||||
this.zipFilePassword,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new errors.AudioFileUnzippingError(
|
||||
`Audio file unzipping error. reason: ${e}`,
|
||||
);
|
||||
}
|
||||
this.logger.log(`[${context.getTrackingId()}] zip file extract end.`);
|
||||
|
||||
// zipファイル内にXMLが含まれているため、取り除いて解凍する
|
||||
const targetFiles = unzipFiles.filter(
|
||||
(filePath) => !filePath.endsWith('.xml'),
|
||||
);
|
||||
|
||||
// 解凍後のフォルダには音声ファイルのみが残っている前提。見つからなければエラー
|
||||
const audioFilePath = targetFiles.pop();
|
||||
if (targetFiles.length !== 0 || !audioFilePath) {
|
||||
throw new Error(
|
||||
`audio file not found in zipfile. audioFileName: ${audioFileName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ファイル変換ツールの仕様で、暗号化されていて拡張子が大文字の音声ファイルは読み込めず、変換に失敗する(エラーコード1000が出力される)ため、
|
||||
// 変換エラーを防ぐため音声ファイル名を小文字に変更してから変換ツールを通す。
|
||||
// 実際にアップロードするファイル名は元の拡張子にする。
|
||||
const audioFilePathLower = await toLowerCaseFileName(audioFilePath);
|
||||
|
||||
// 音声ファイルを変換する
|
||||
this.logger.log(`[${context.getTrackingId()}] audio file convert start.`);
|
||||
try {
|
||||
await new Promise(
|
||||
async (resolve, reject: (resultCode: number) => void) => {
|
||||
const child = childProcess.spawn('dec2wav.out', [
|
||||
audioFilePathLower,
|
||||
`${audioFilePath}.wav`,
|
||||
encryptionPassword,
|
||||
]);
|
||||
|
||||
let stdoutData = '';
|
||||
for await (const chunk of child.stdout) {
|
||||
stdoutData += chunk;
|
||||
}
|
||||
// 改行文字が含まれているので取り除く
|
||||
const resultCode = Number(stdoutData.replace('\n', ''));
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] wav convert tool result code: ${resultCode}`,
|
||||
);
|
||||
// 成功コードではない場合、エラーとする
|
||||
if (resultCode > 0) {
|
||||
reject(Number(resultCode));
|
||||
}
|
||||
resolve(resultCode);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
const errorCode = e as unknown as number;
|
||||
const reason = this.handleAudioConvertToolError(errorCode);
|
||||
throw reason;
|
||||
}
|
||||
this.logger.log(`[${context.getTrackingId()}] audio file convert end.`);
|
||||
// BlobStorageにアップロードする
|
||||
await this.blobStorageService.uploadFile(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
taskId,
|
||||
`auto-transcribe/${taskId}/transcribe-request/${
|
||||
`${audioFilePath}.wav`.split('/').slice(-1)[0]
|
||||
}`,
|
||||
`${audioFilePath}.wav`,
|
||||
);
|
||||
|
||||
return {
|
||||
voiceFileName: `${audioFilePath}.wav`.split('/').slice(-1)[0],
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
if (e instanceof errors.AudioFileConvertToolError) {
|
||||
// モジュールエラーコード保持し、エラー返す
|
||||
const underlyingCode = e.toolErrorCode;
|
||||
const errorCode = `E03${underlyingCode}`;
|
||||
|
||||
const mappedErrorCode: ErrorCodeType = ErrorCodes.includes(
|
||||
errorCode as ErrorCodeType,
|
||||
)
|
||||
? (errorCode as ErrorCodeType)
|
||||
: 'E039999';
|
||||
|
||||
throw new HttpException(
|
||||
makeErrorResponse(mappedErrorCode),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case errors.StorageAccountContainerNotFoundError:
|
||||
case errors.AudioFileNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010701'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
// ローカルのファイルは必ず消す
|
||||
try {
|
||||
await rmDirRecursive(workDirPath);
|
||||
} catch (e) {
|
||||
// ファイル削除時のエラーは本来の処理は完了できているので、握りつぶす。
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] work file delete error. reason: ${e})}`,
|
||||
);
|
||||
}
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${
|
||||
this.generateAutoTranscriptionFile.name
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleAudioConvertToolError(errorCode: number) {
|
||||
return new AUDIO_FILE_CONVERT_ERROR_MAP[errorCode](
|
||||
`Audio file convert tool error. error code: ${errorCode}`,
|
||||
errorCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export * from './types';
|
||||
@ -0,0 +1,209 @@
|
||||
/**
|
||||
* SAコンテナが存在しないエラー
|
||||
*/
|
||||
export class StorageAccountContainerNotFoundError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = StorageAccountContainerNotFoundError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 音声ファイル不在エラー
|
||||
*/
|
||||
export class AudioFileNotFoundError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = AudioFileNotFoundError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ZIP解凍失敗エラー
|
||||
*/
|
||||
export class AudioFileUnzippingError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = AudioFileUnzippingError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WAV変換ツール関連エラー
|
||||
*/
|
||||
export class AudioFileConvertToolError extends Error {
|
||||
constructor(message: string, public readonly toolErrorCode: number) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入力ファイルのオープンに失敗
|
||||
*/
|
||||
export class InputAudioFileOpenError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 1000) {
|
||||
super(message, code);
|
||||
this.name = InputAudioFileOpenError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入力ファイルが不正なフォーマット
|
||||
*/
|
||||
export class InputAudioFileFormatError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 1001) {
|
||||
super(message, code);
|
||||
this.name = InputAudioFileFormatError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入力ファイルが非サポートの拡張子
|
||||
*/
|
||||
export class InputAudioFileExtError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 1002) {
|
||||
super(message, code);
|
||||
this.name = InputAudioFileExtError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入力ファイルの読み出しに失敗
|
||||
*/
|
||||
export class InputAudioFileReadError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 1003) {
|
||||
super(message, code);
|
||||
this.name = InputAudioFileReadError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入力ファイルが他社のdssファイル
|
||||
*/
|
||||
export class InputAudioFileInvalidDssFormatError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 1004) {
|
||||
super(message, code);
|
||||
this.name = InputAudioFileInvalidDssFormatError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 出力ファイルのオープンに失敗;
|
||||
*/
|
||||
export class OutputAudioFileOpenError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 2000) {
|
||||
super(message, code);
|
||||
this.name = OutputAudioFileOpenError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 出力ファイルのwavヘッダー書き出しに失敗;
|
||||
*/
|
||||
export class OutputAudioFileWriteHeaderError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 2001) {
|
||||
super(message, code);
|
||||
this.name = OutputAudioFileWriteHeaderError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 出力データの書き出し中に失敗;
|
||||
*/
|
||||
export class OutputAudioFileWriteError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 2002) {
|
||||
super(message, code);
|
||||
this.name = OutputAudioFileWriteError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ds2ファイルの暗号化パスワードの指定がない;
|
||||
*/
|
||||
export class DS2AudioFileEncryptionPasswordNotSpecifiedError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 3000) {
|
||||
super(message, code);
|
||||
this.name = DS2AudioFileEncryptionPasswordNotSpecifiedError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ds2ファイルの暗号化パスワードが異なる;
|
||||
*/
|
||||
export class DS2AudioFileEncryptionPasswordInvalidError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 3001) {
|
||||
super(message, code);
|
||||
this.name = DS2AudioFileEncryptionPasswordInvalidError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3の初期化APIが失敗;
|
||||
*/
|
||||
export class MP3AudioFileInitializeError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4000) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileInitializeError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3のオブジェクト生成に失敗;
|
||||
*/
|
||||
export class MP3AudioFileObjectGenerateError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4001) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileInitializeError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3のフォーマット設定に失敗;
|
||||
*/
|
||||
export class MP3AudioFileFormatConfigurationError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4002) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileFormatConfigurationError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3のフォーマット取得に失敗
|
||||
*/
|
||||
export class MP3AudioFileGetFormatError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4003) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileGetFormatError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3のブロック出力に失敗;
|
||||
*/
|
||||
export class MP3AudioFileBlockOutputError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4004) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileBlockOutputError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3のデータ読み出しに失敗;
|
||||
*/
|
||||
export class MP3AudioFileReadError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4005) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileReadError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mp3ファイルの終了に失敗;
|
||||
*/
|
||||
export class MP3AudioFileCloseError extends AudioFileConvertToolError {
|
||||
constructor(message: string, code = 4006) {
|
||||
super(message, code);
|
||||
this.name = MP3AudioFileCloseError.name;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
export type BlobstorageServiceMockValue = {
|
||||
containerExists: boolean | Error;
|
||||
fileExists: boolean | Error;
|
||||
downloadFile: void | Error;
|
||||
uploadFile: void | Error;
|
||||
};
|
||||
|
||||
export const makeBlobstorageServiceMock = (
|
||||
value: BlobstorageServiceMockValue,
|
||||
) => {
|
||||
const { containerExists, fileExists, downloadFile, uploadFile } = value;
|
||||
|
||||
return {
|
||||
containerExists:
|
||||
containerExists instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(containerExists)
|
||||
: jest.fn<Promise<boolean>, []>().mockResolvedValue(containerExists),
|
||||
fileExists:
|
||||
fileExists instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(fileExists)
|
||||
: jest.fn<Promise<boolean>, []>().mockResolvedValue(fileExists),
|
||||
downloadFile:
|
||||
downloadFile instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(downloadFile)
|
||||
: jest.fn<Promise<void>, []>().mockResolvedValue(downloadFile),
|
||||
uploadFile:
|
||||
uploadFile instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(uploadFile)
|
||||
: jest.fn<Promise<void>, []>().mockResolvedValue(uploadFile),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeBlobstorageServiceMockValue =
|
||||
(): BlobstorageServiceMockValue => {
|
||||
return {
|
||||
containerExists: true,
|
||||
fileExists: true,
|
||||
uploadFile: undefined,
|
||||
downloadFile: undefined,
|
||||
};
|
||||
};
|
||||
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>Test file</test>
|
||||
</root>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>NOT DS2</test>
|
||||
</root>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>NOT DS2</test>
|
||||
</root>
|
||||
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>TEST XML</test>
|
||||
</root>
|
||||
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>password is 'p@ssword'</test>
|
||||
</root>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<test>Not zipfile.</test>
|
||||
</root>
|
||||
Binary file not shown.
@ -0,0 +1,33 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { BlobstorageModule } from '../../../gateways/blobstorage/blobstorage.module';
|
||||
import { BlobstorageService } from '../../../gateways/blobstorage/blobstorage.service';
|
||||
import { ConvertAudioFileService } from '../convert-audio-file.service';
|
||||
import {
|
||||
BlobstorageServiceMockValue,
|
||||
makeBlobstorageServiceMock,
|
||||
} from './convert-audio-file.service.mock';
|
||||
|
||||
export const makeTestingModuleWithBlob = async (
|
||||
blobStorageService: BlobstorageServiceMockValue,
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.test', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
BlobstorageModule,
|
||||
],
|
||||
providers: [ConvertAudioFileService],
|
||||
})
|
||||
.overrideProvider(BlobstorageService)
|
||||
.useValue(makeBlobstorageServiceMock(blobStorageService))
|
||||
.compile();
|
||||
|
||||
return module;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsString, Min } from 'class-validator';
|
||||
|
||||
export class GenerateAutoTranscriptionFileResponse {
|
||||
@ApiProperty({
|
||||
description: '自動文字起こし用音声ファイル(wav形式)',
|
||||
})
|
||||
@Type(() => String)
|
||||
@IsString()
|
||||
voiceFileName: string;
|
||||
}
|
||||
|
||||
export class GenerateAutoTranscriptionFileRequest {
|
||||
@ApiProperty({
|
||||
description: 'タスクID',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
taskId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'アカウントが所属している国情報',
|
||||
})
|
||||
@Type(() => String)
|
||||
country: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'アカウントID',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
accountId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '音声ファイル名',
|
||||
})
|
||||
@Type(() => String)
|
||||
audioFileName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '復号化パスワード',
|
||||
})
|
||||
@Type(() => String)
|
||||
encryptionPassword: string;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BlobstorageService } from './blobstorage.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
exports: [BlobstorageService],
|
||||
imports: [ConfigModule],
|
||||
providers: [BlobstorageService],
|
||||
})
|
||||
export class BlobstorageModule {}
|
||||
@ -0,0 +1,219 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
BlobServiceClient,
|
||||
StorageSharedKeyCredential,
|
||||
ContainerClient,
|
||||
} from '@azure/storage-blob';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
BLOB_STORAGE_REGION_AU,
|
||||
BLOB_STORAGE_REGION_EU,
|
||||
BLOB_STORAGE_REGION_US,
|
||||
} from '../../constants';
|
||||
import { Context } from '../../common/log';
|
||||
|
||||
@Injectable()
|
||||
export class BlobstorageService {
|
||||
private readonly logger = new Logger(BlobstorageService.name);
|
||||
private readonly blobServiceClientUS: BlobServiceClient;
|
||||
private readonly blobServiceClientEU: BlobServiceClient;
|
||||
private readonly blobServiceClientAU: BlobServiceClient;
|
||||
private readonly sharedKeyCredentialUS: StorageSharedKeyCredential;
|
||||
private readonly sharedKeyCredentialAU: StorageSharedKeyCredential;
|
||||
private readonly sharedKeyCredentialEU: StorageSharedKeyCredential;
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.sharedKeyCredentialUS = new StorageSharedKeyCredential(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_NAME_US'),
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_KEY_US'),
|
||||
);
|
||||
this.sharedKeyCredentialAU = new StorageSharedKeyCredential(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_NAME_AU'),
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_KEY_AU'),
|
||||
);
|
||||
this.sharedKeyCredentialEU = new StorageSharedKeyCredential(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_NAME_EU'),
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_KEY_EU'),
|
||||
);
|
||||
this.blobServiceClientUS = new BlobServiceClient(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_ENDPOINT_US'),
|
||||
this.sharedKeyCredentialUS,
|
||||
);
|
||||
this.blobServiceClientAU = new BlobServiceClient(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_ENDPOINT_AU'),
|
||||
this.sharedKeyCredentialAU,
|
||||
);
|
||||
this.blobServiceClientEU = new BlobServiceClient(
|
||||
this.configService.getOrThrow<string>('STORAGE_ACCOUNT_ENDPOINT_EU'),
|
||||
this.sharedKeyCredentialEU,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Containers exists
|
||||
* @param country
|
||||
* @param accountId
|
||||
* @returns exists
|
||||
*/
|
||||
async containerExists(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
): Promise<boolean> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.containerExists.name
|
||||
} | params: { ` + `accountId: ${accountId} };`,
|
||||
);
|
||||
// 国に応じたリージョンでコンテナ名を指定してClientを取得
|
||||
const containerClient = this.getContainerClient(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
);
|
||||
const exists = await containerClient.exists();
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.containerExists.name}`,
|
||||
);
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Files exists
|
||||
* @param context
|
||||
* @param accountId
|
||||
* @param country
|
||||
* @param fileName
|
||||
* @returns exists
|
||||
*/
|
||||
async fileExists(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
fileName: string,
|
||||
): Promise<boolean> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.fileExists.name} | params: { ` +
|
||||
`accountId: ${accountId},` +
|
||||
`country: ${country},` +
|
||||
`fileName: ${fileName} };`,
|
||||
);
|
||||
|
||||
const containerClient = this.getContainerClient(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
);
|
||||
const blob = containerClient.getBlobClient(`${fileName}`);
|
||||
const exists = await blob.exists();
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.fileExists.name}`,
|
||||
);
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download File in countainer
|
||||
* @param context
|
||||
* @param accountId
|
||||
* @param country
|
||||
* @param fileName
|
||||
* @param downloadFilePath
|
||||
*/
|
||||
async downloadFile(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
fileName: string,
|
||||
downloadFilePath: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.downloadFile.name
|
||||
} | params: { ` +
|
||||
`accountId: ${accountId},` +
|
||||
`country: ${country},` +
|
||||
`fileName: ${fileName} };`,
|
||||
);
|
||||
|
||||
const containerClient = this.getContainerClient(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
);
|
||||
const blob = containerClient.getBlobClient(`${fileName}`);
|
||||
await blob.downloadToFile(downloadFilePath);
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.downloadFile.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload File to countainer
|
||||
* @param context
|
||||
* @param accountId
|
||||
* @param country
|
||||
* @param taskId
|
||||
* @param fileName
|
||||
* @param localFilePath
|
||||
* @returns
|
||||
*/
|
||||
async uploadFile(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
taskId: number,
|
||||
fileName: string,
|
||||
localFilePath: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.uploadFile.name} | params: { ` +
|
||||
`accountId: ${accountId},` +
|
||||
`country: ${country},` +
|
||||
`taskId: ${taskId},` +
|
||||
`fileName: ${fileName} };` +
|
||||
`localFilePath: ${localFilePath} };`,
|
||||
);
|
||||
|
||||
const containerClient = this.getContainerClient(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
);
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(fileName);
|
||||
await blockBlobClient.uploadFile(localFilePath);
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.uploadFile.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets container client
|
||||
* @param companyName
|
||||
* @returns container client
|
||||
*/
|
||||
private getContainerClient(
|
||||
context: Context,
|
||||
accountId: number,
|
||||
country: string,
|
||||
): ContainerClient {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.getContainerClient.name
|
||||
} | params: { ` + `accountId: ${accountId} };`,
|
||||
);
|
||||
|
||||
const containerName = `account-${accountId}`;
|
||||
if (BLOB_STORAGE_REGION_US.includes(country)) {
|
||||
return this.blobServiceClientUS.getContainerClient(containerName);
|
||||
} else if (BLOB_STORAGE_REGION_AU.includes(country)) {
|
||||
return this.blobServiceClientAU.getContainerClient(containerName);
|
||||
} else if (BLOB_STORAGE_REGION_EU.includes(country)) {
|
||||
return this.blobServiceClientEU.getContainerClient(containerName);
|
||||
} else {
|
||||
throw new Error('invalid country');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiOperation } from '@nestjs/swagger';
|
||||
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
@Get()
|
||||
@ApiOperation({ operationId: 'checkHealth' })
|
||||
checkHealth(): string {
|
||||
return 'ODMS Auto Transcription File App Service Health OK';
|
||||
}
|
||||
}
|
||||
62
dictation_auto_transcription_file_server/src/main.ts
Normal file
62
dictation_auto_transcription_file_server/src/main.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { AppModule } from './app.module';
|
||||
import { ValidationPipe, VersioningType } from '@nestjs/common';
|
||||
import helmet from 'helmet';
|
||||
const helmetDirectives = helmet.contentSecurityPolicy.getDefaultDirectives();
|
||||
|
||||
helmetDirectives['connect-src'] =
|
||||
process.env.STAGE === 'local'
|
||||
? [
|
||||
"'self'",
|
||||
process.env.ADB2C_ORIGIN ?? '',
|
||||
process.env.STORAGE_ACCOUNT_ENDPOINT_US ?? '',
|
||||
process.env.STORAGE_ACCOUNT_ENDPOINT_AU ?? '',
|
||||
process.env.STORAGE_ACCOUNT_ENDPOINT_EU ?? '',
|
||||
]
|
||||
: ["'self'"];
|
||||
|
||||
helmetDirectives['navigate-to'] = ["'self'"];
|
||||
helmetDirectives['style-src'] = ["'self'", 'https:'];
|
||||
helmetDirectives['report-uri'] = ["'self'"];
|
||||
async function bootstrap() {
|
||||
console.log(`BUILD_VERSION: ${process.env.BUILD_VERSION}`);
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: helmetDirectives,
|
||||
},
|
||||
}),
|
||||
cookieParser(),
|
||||
);
|
||||
|
||||
// バリデーター(+型の自動変換機能)を適用
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({ transform: true, forbidUnknownValues: false }),
|
||||
);
|
||||
|
||||
if (process.env.STAGE === 'local') {
|
||||
const options = new DocumentBuilder()
|
||||
.setTitle('ODMSOpenAPI')
|
||||
.setVersion('1.0.0')
|
||||
.addBearerAuth({
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
})
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
}
|
||||
|
||||
app.enableVersioning({
|
||||
type: VersioningType.HEADER,
|
||||
header: 'x-api-version',
|
||||
});
|
||||
|
||||
await app.listen(process.env.PORT || 80);
|
||||
}
|
||||
bootstrap();
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
22
dictation_auto_transcription_file_server/tsconfig.json
Normal file
22
dictation_auto_transcription_file_server/tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:18.17.1-buster
|
||||
FROM node:22.14-bookworm
|
||||
|
||||
RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
|
||||
echo "Asia/Tokyo" > /etc/timezone
|
||||
|
||||
@ -3,31 +3,54 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
|
||||
#-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag]
|
||||
|
||||
INSTALL_ZSH=${1:-"true"}
|
||||
USERNAME=${2:-"vscode"}
|
||||
USER_UID=${3:-1000}
|
||||
USER_GID=${4:-1000}
|
||||
UPGRADE_PACKAGES=${5:-"true"}
|
||||
#
|
||||
# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md
|
||||
# Maintainer: The VS Code and Codespaces Teams
|
||||
#
|
||||
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages]
|
||||
|
||||
set -e
|
||||
|
||||
INSTALL_ZSH=${1:-"true"}
|
||||
USERNAME=${2:-"automatic"}
|
||||
USER_UID=${3:-"automatic"}
|
||||
USER_GID=${4:-"automatic"}
|
||||
UPGRADE_PACKAGES=${5:-"true"}
|
||||
INSTALL_OH_MYS=${6:-"true"}
|
||||
ADD_NON_FREE_PACKAGES=${7:-"false"}
|
||||
SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)"
|
||||
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Treat a user name of "none" as root
|
||||
if [ "${USERNAME}" = "none" ] || [ "${USERNAME}" = "root" ]; then
|
||||
# Ensure that login shells get the correct path if the user updated the PATH using ENV.
|
||||
rm -f /etc/profile.d/00-restore-env.sh
|
||||
echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh
|
||||
chmod +x /etc/profile.d/00-restore-env.sh
|
||||
|
||||
# If in automatic mode, determine if a user already exists, if not use vscode
|
||||
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
|
||||
USERNAME=""
|
||||
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
|
||||
for CURRENT_USER in ${POSSIBLE_USERS[@]}; do
|
||||
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
|
||||
USERNAME=${CURRENT_USER}
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "${USERNAME}" = "" ]; then
|
||||
USERNAME=vscode
|
||||
fi
|
||||
elif [ "${USERNAME}" = "none" ]; then
|
||||
USERNAME=root
|
||||
USER_UID=0
|
||||
USER_GID=0
|
||||
fi
|
||||
|
||||
# Load markers to see which steps have already run
|
||||
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
|
||||
if [ -f "${MARKER_FILE}" ]; then
|
||||
echo "Marker file found:"
|
||||
cat "${MARKER_FILE}"
|
||||
@ -38,7 +61,7 @@ fi
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Function to call apt-get if needed
|
||||
apt-get-update-if-needed()
|
||||
apt_get_update_if_needed()
|
||||
{
|
||||
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
|
||||
echo "Running apt-get update..."
|
||||
@ -50,132 +73,373 @@ apt-get-update-if-needed()
|
||||
|
||||
# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
|
||||
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
|
||||
apt-get-update-if-needed
|
||||
|
||||
PACKAGE_LIST="apt-utils \
|
||||
git \
|
||||
package_list="apt-utils \
|
||||
openssh-client \
|
||||
less \
|
||||
gnupg2 \
|
||||
dirmngr \
|
||||
iproute2 \
|
||||
procps \
|
||||
lsof \
|
||||
htop \
|
||||
net-tools \
|
||||
psmisc \
|
||||
curl \
|
||||
wget \
|
||||
rsync \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
zip \
|
||||
nano \
|
||||
vim-tiny \
|
||||
less \
|
||||
jq \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
apt-transport-https \
|
||||
dialog \
|
||||
gnupg2 \
|
||||
libc6 \
|
||||
libgcc1 \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
libicu[0-9][0-9] \
|
||||
liblttng-ust0 \
|
||||
liblttng-ust[0-9] \
|
||||
libstdc++6 \
|
||||
zlib1g \
|
||||
locales \
|
||||
sudo"
|
||||
sudo \
|
||||
ncdu \
|
||||
man-db \
|
||||
strace \
|
||||
manpages \
|
||||
manpages-dev \
|
||||
init-system-helpers"
|
||||
|
||||
# Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian
|
||||
if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then
|
||||
# Bring in variables from /etc/os-release like VERSION_CODENAME
|
||||
. /etc/os-release
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list
|
||||
# Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html
|
||||
sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
|
||||
sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list
|
||||
echo "Running apt-get update..."
|
||||
apt-get update
|
||||
package_list="${package_list} manpages-posix manpages-posix-dev"
|
||||
else
|
||||
apt_get_update_if_needed
|
||||
fi
|
||||
|
||||
# Install libssl1.1 if available
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
|
||||
PACKAGE_LIST="${PACKAGE_LIST} libssl1.1"
|
||||
package_list="${package_list} libssl1.1"
|
||||
fi
|
||||
|
||||
|
||||
# Install appropriate version of libssl1.0.x if available
|
||||
LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
|
||||
if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
|
||||
libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
|
||||
if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
|
||||
if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
|
||||
# Debian 9
|
||||
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2"
|
||||
package_list="${package_list} libssl1.0.2"
|
||||
elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
|
||||
# Ubuntu 18.04, 16.04, earlier
|
||||
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0"
|
||||
package_list="${package_list} libssl1.0.0"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Packages to verify are installed: ${PACKAGE_LIST}"
|
||||
apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
|
||||
|
||||
echo "Packages to verify are installed: ${package_list}"
|
||||
apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
|
||||
|
||||
# Install git if not already installed (may be more recent than distro version)
|
||||
if ! type git > /dev/null 2>&1; then
|
||||
apt-get -y install --no-install-recommends git
|
||||
fi
|
||||
|
||||
PACKAGES_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Get to latest versions of all packages
|
||||
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
|
||||
apt-get-update-if-needed
|
||||
apt_get_update_if_needed
|
||||
apt-get -y upgrade --no-install-recommends
|
||||
apt-get autoremove -y
|
||||
fi
|
||||
|
||||
# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
|
||||
# Common need for both applications and things like the agnoster ZSH theme.
|
||||
if [ "${LOCALE_ALREADY_SET}" != "true" ]; then
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||
if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then
|
||||
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
|
||||
locale-gen
|
||||
LOCALE_ALREADY_SET="true"
|
||||
fi
|
||||
|
||||
# Create or update a non-root user to match UID/GID - see https://aka.ms/vscode-remote/containers/non-root-user.
|
||||
if id -u $USERNAME > /dev/null 2>&1; then
|
||||
# Create or update a non-root user to match UID/GID.
|
||||
group_name="${USERNAME}"
|
||||
if id -u ${USERNAME} > /dev/null 2>&1; then
|
||||
# User exists, update if needed
|
||||
if [ "$USER_GID" != "$(id -G $USERNAME)" ]; then
|
||||
groupmod --gid $USER_GID $USERNAME
|
||||
if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then
|
||||
group_name="$(id -gn $USERNAME)"
|
||||
groupmod --gid $USER_GID ${group_name}
|
||||
usermod --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
|
||||
if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
|
||||
usermod --uid $USER_UID $USERNAME
|
||||
fi
|
||||
else
|
||||
# Create user
|
||||
groupadd --gid $USER_GID $USERNAME
|
||||
useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
|
||||
if [ "${USER_GID}" = "automatic" ]; then
|
||||
groupadd $USERNAME
|
||||
else
|
||||
groupadd --gid $USER_GID $USERNAME
|
||||
fi
|
||||
if [ "${USER_UID}" = "automatic" ]; then
|
||||
useradd -s /bin/bash --gid $USERNAME -m $USERNAME
|
||||
else
|
||||
useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add add sudo support for non-root user
|
||||
# Add sudo support for non-root user
|
||||
if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
|
||||
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
|
||||
chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
EXISTING_NON_ROOT_USER="${USERNAME}"
|
||||
fi
|
||||
|
||||
# .bashrc/.zshrc snippet
|
||||
RC_SNIPPET="$(cat << EOF
|
||||
export USER=\$(whoami)
|
||||
|
||||
export PATH=\$PATH:\$HOME/.local/bin
|
||||
|
||||
if [[ \$(which code-insiders 2>&1) && ! \$(which code 2>&1) ]]; then
|
||||
alias code=code-insiders
|
||||
# ** Shell customization section **
|
||||
if [ "${USERNAME}" = "root" ]; then
|
||||
user_rc_path="/root"
|
||||
else
|
||||
user_rc_path="/home/${USERNAME}"
|
||||
fi
|
||||
|
||||
# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then
|
||||
cp /etc/skel/.bashrc "${user_rc_path}/.bashrc"
|
||||
fi
|
||||
|
||||
# Restore user .profile defaults from skeleton file if it doesn't exist or is empty
|
||||
if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then
|
||||
cp /etc/skel/.profile "${user_rc_path}/.profile"
|
||||
fi
|
||||
|
||||
# .bashrc/.zshrc snippet
|
||||
rc_snippet="$(cat << 'EOF'
|
||||
|
||||
if [ -z "${USER}" ]; then export USER=$(whoami); fi
|
||||
if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi
|
||||
|
||||
# Display optional first run image specific notice if configured and terminal is interactive
|
||||
if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then
|
||||
if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then
|
||||
cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt"
|
||||
elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then
|
||||
cat "/workspaces/.codespaces/shared/first-run-notice.txt"
|
||||
fi
|
||||
mkdir -p "$HOME/.config/vscode-dev-containers"
|
||||
# Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it
|
||||
((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &)
|
||||
fi
|
||||
|
||||
# Set the default git editor if not already set
|
||||
if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then
|
||||
if [ "${TERM_PROGRAM}" = "vscode" ]; then
|
||||
if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then
|
||||
export GIT_EDITOR="code-insiders --wait"
|
||||
else
|
||||
export GIT_EDITOR="code --wait"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Ensure ~/.local/bin is in the PATH for root and non-root users for bash. (zsh is later)
|
||||
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
|
||||
echo "${RC_SNIPPET}" | tee -a /root/.bashrc >> /etc/skel/.bashrc
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
echo "${RC_SNIPPET}" >> /home/$USERNAME/.bashrc
|
||||
chown $USER_UID:$USER_GID /home/$USERNAME/.bashrc
|
||||
# code shim, it fallbacks to code-insiders if code is not available
|
||||
cat << 'EOF' > /usr/local/bin/code
|
||||
#!/bin/sh
|
||||
|
||||
get_in_path_except_current() {
|
||||
which -a "$1" | grep -A1 "$0" | grep -v "$0"
|
||||
}
|
||||
|
||||
code="$(get_in_path_except_current code)"
|
||||
|
||||
if [ -n "$code" ]; then
|
||||
exec "$code" "$@"
|
||||
elif [ "$(command -v code-insiders)" ]; then
|
||||
exec code-insiders "$@"
|
||||
else
|
||||
echo "code or code-insiders is not installed" >&2
|
||||
exit 127
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/code
|
||||
|
||||
# systemctl shim - tells people to use 'service' if systemd is not running
|
||||
cat << 'EOF' > /usr/local/bin/systemctl
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ -d "/run/systemd/system" ]; then
|
||||
exec /bin/systemctl "$@"
|
||||
else
|
||||
echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all'
|
||||
fi
|
||||
EOF
|
||||
chmod +x /usr/local/bin/systemctl
|
||||
|
||||
# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme
|
||||
codespaces_bash="$(cat \
|
||||
<<'EOF'
|
||||
|
||||
# Codespaces bash prompt theme
|
||||
__bash_prompt() {
|
||||
local userpart='`export XIT=$? \
|
||||
&& [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \
|
||||
&& [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`'
|
||||
local gitbranch='`\
|
||||
if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \
|
||||
export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \
|
||||
if [ "${BRANCH}" != "" ]; then \
|
||||
echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \
|
||||
&& if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \
|
||||
echo -n " \[\033[1;33m\]✗"; \
|
||||
fi \
|
||||
&& echo -n "\[\033[0;36m\]) "; \
|
||||
fi; \
|
||||
fi`'
|
||||
local lightblue='\[\033[1;34m\]'
|
||||
local removecolor='\[\033[0m\]'
|
||||
PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ "
|
||||
unset -f __bash_prompt
|
||||
}
|
||||
__bash_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
codespaces_zsh="$(cat \
|
||||
<<'EOF'
|
||||
# Codespaces zsh prompt theme
|
||||
__zsh_prompt() {
|
||||
local prompt_username
|
||||
if [ ! -z "${GITHUB_USER}" ]; then
|
||||
prompt_username="@${GITHUB_USER}"
|
||||
else
|
||||
prompt_username="%n"
|
||||
fi
|
||||
PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow
|
||||
PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd
|
||||
PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status
|
||||
PROMPT+='%{$fg[white]%}$ %{$reset_color%}'
|
||||
unset -f __zsh_prompt
|
||||
}
|
||||
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}"
|
||||
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
|
||||
ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})"
|
||||
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})"
|
||||
__zsh_prompt
|
||||
|
||||
EOF
|
||||
)"
|
||||
|
||||
# Add RC snippet and custom bash prompt
|
||||
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/bash.bashrc
|
||||
echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc"
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
echo "${codespaces_bash}" >> "/root/.bashrc"
|
||||
echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc"
|
||||
fi
|
||||
chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc"
|
||||
RC_SNIPPET_ALREADY_ADDED="true"
|
||||
fi
|
||||
|
||||
# Optionally install and configure zsh
|
||||
if [ "${INSTALL_ZSH}" = "true" ] && [ ! -d "/root/.oh-my-zsh" ] && [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
|
||||
apt-get-update-if-needed
|
||||
apt-get install -y zsh
|
||||
curl -fsSLo- https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash 2>&1
|
||||
echo -e "${RC_SNIPPET}\nDEFAULT_USER=\$USER\nprompt_context(){}" >> /root/.zshrc
|
||||
cp -fR /root/.oh-my-zsh /etc/skel
|
||||
cp -f /root/.zshrc /etc/skel
|
||||
sed -i -e "s/\/root\/.oh-my-zsh/\/home\/\$(whoami)\/.oh-my-zsh/g" /etc/skel/.zshrc
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
cp -fR /etc/skel/.oh-my-zsh /etc/skel/.zshrc /home/$USERNAME
|
||||
chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc
|
||||
# Optionally install and configure zsh and Oh My Zsh!
|
||||
if [ "${INSTALL_ZSH}" = "true" ]; then
|
||||
if ! type zsh > /dev/null 2>&1; then
|
||||
apt_get_update_if_needed
|
||||
apt-get install -y zsh
|
||||
fi
|
||||
ZSH_ALREADY_INSTALLED="true"
|
||||
if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
|
||||
echo "${rc_snippet}" >> /etc/zsh/zshrc
|
||||
ZSH_ALREADY_INSTALLED="true"
|
||||
fi
|
||||
|
||||
# Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme.
|
||||
# See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script.
|
||||
oh_my_install_dir="${user_rc_path}/.oh-my-zsh"
|
||||
if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then
|
||||
template_path="${oh_my_install_dir}/templates/zshrc.zsh-template"
|
||||
user_rc_file="${user_rc_path}/.zshrc"
|
||||
umask g-w,o-w
|
||||
mkdir -p ${oh_my_install_dir}
|
||||
git clone --depth=1 \
|
||||
-c core.eol=lf \
|
||||
-c core.autocrlf=false \
|
||||
-c fsck.zeroPaddedFilemode=ignore \
|
||||
-c fetch.fsck.zeroPaddedFilemode=ignore \
|
||||
-c receive.fsck.zeroPaddedFilemode=ignore \
|
||||
"https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1
|
||||
echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file}
|
||||
sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file}
|
||||
|
||||
mkdir -p ${oh_my_install_dir}/custom/themes
|
||||
echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme"
|
||||
# Shrink git while still enabling updates
|
||||
cd "${oh_my_install_dir}"
|
||||
git repack -a -d -f --depth=1 --window=1
|
||||
# Copy to non-root user if one is specified
|
||||
if [ "${USERNAME}" != "root" ]; then
|
||||
cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root
|
||||
chown -R ${USERNAME}:${group_name} "${user_rc_path}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Persist image metadata info, script if meta.env found in same directory
|
||||
meta_info_script="$(cat << 'EOF'
|
||||
#!/bin/sh
|
||||
. /usr/local/etc/vscode-dev-containers/meta.env
|
||||
|
||||
# Minimal output
|
||||
if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then
|
||||
echo "${VERSION}"
|
||||
exit 0
|
||||
elif [ "$1" = "release" ]; then
|
||||
echo "${GIT_REPOSITORY_RELEASE}"
|
||||
exit 0
|
||||
elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then
|
||||
echo "${CONTENTS_URL}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#Full output
|
||||
echo
|
||||
echo "Development container image information"
|
||||
echo
|
||||
if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi
|
||||
if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi
|
||||
if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi
|
||||
if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi
|
||||
if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi
|
||||
if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi
|
||||
echo
|
||||
EOF
|
||||
)"
|
||||
if [ -f "${SCRIPT_DIR}/meta.env" ]; then
|
||||
mkdir -p /usr/local/etc/vscode-dev-containers/
|
||||
cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env
|
||||
echo "${meta_info_script}" > /usr/local/bin/devcontainer-info
|
||||
chmod +x /usr/local/bin/devcontainer-info
|
||||
fi
|
||||
|
||||
# Write marker file
|
||||
|
||||
8
dictation_client/.vscode/settings.json
vendored
8
dictation_client/.vscode/settings.json
vendored
@ -34,5 +34,11 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnType": true,
|
||||
"prettier.disableLanguages": ["markdown"]
|
||||
"prettier.disableLanguages": ["markdown"],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +140,12 @@ export interface AllocatableLicenseInfo {
|
||||
* @memberof AllocatableLicenseInfo
|
||||
*/
|
||||
'expiryDate'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AllocatableLicenseInfo
|
||||
*/
|
||||
'licenseType': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -333,6 +339,18 @@ export interface AudioUploadFinishedResponse {
|
||||
* @memberof AudioUploadFinishedResponse
|
||||
*/
|
||||
'jobNumber': string;
|
||||
/**
|
||||
* タスクID
|
||||
* @type {number}
|
||||
* @memberof AudioUploadFinishedResponse
|
||||
*/
|
||||
'taskId'?: number;
|
||||
/**
|
||||
* 自動文字起こし可否フラグ
|
||||
* @type {boolean}
|
||||
* @memberof AudioUploadFinishedResponse
|
||||
*/
|
||||
'isAutoTranscription'?: boolean;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -366,6 +384,37 @@ export interface Author {
|
||||
*/
|
||||
'authorId': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface AutoTranscribeCompleteRequest
|
||||
*/
|
||||
export interface AutoTranscribeCompleteRequest {
|
||||
/**
|
||||
* タスクID
|
||||
* @type {number}
|
||||
* @memberof AutoTranscribeCompleteRequest
|
||||
*/
|
||||
'taskId': number;
|
||||
/**
|
||||
* 自動文字起こし結果
|
||||
* @type {string}
|
||||
* @memberof AutoTranscribeCompleteRequest
|
||||
*/
|
||||
'transcribeResultStatus': string;
|
||||
/**
|
||||
* 文字起こし結果ファイル名
|
||||
* @type {string}
|
||||
* @memberof AutoTranscribeCompleteRequest
|
||||
*/
|
||||
'transcribeResultName'?: string;
|
||||
/**
|
||||
* タイムラインテキスト
|
||||
* @type {string}
|
||||
* @memberof AutoTranscribeCompleteRequest
|
||||
*/
|
||||
'timelineTextName'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -424,6 +473,32 @@ export interface ConfirmRequest {
|
||||
*/
|
||||
'token': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ConvertAudioFileRequest
|
||||
*/
|
||||
export interface ConvertAudioFileRequest {
|
||||
/**
|
||||
* タスクID
|
||||
* @type {number}
|
||||
* @memberof ConvertAudioFileRequest
|
||||
*/
|
||||
'taskId': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ConvertAudioFileResponse
|
||||
*/
|
||||
export interface ConvertAudioFileResponse {
|
||||
/**
|
||||
* 自動文字起こし用音声ファイル(wav形式)
|
||||
* @type {string}
|
||||
* @memberof ConvertAudioFileResponse
|
||||
*/
|
||||
'voiceFileName': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -509,6 +584,12 @@ export interface CreateOrdersRequest {
|
||||
* @memberof CreateOrdersRequest
|
||||
*/
|
||||
'orderCount': number;
|
||||
/**
|
||||
* ライセンス種別: NORMAL / SPEECH_RECOGNITION / SPEECH_RECOGNITION_UPGRADE
|
||||
* @type {string}
|
||||
* @memberof CreateOrdersRequest
|
||||
*/
|
||||
'licenseType': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -578,6 +659,12 @@ export interface CreateWorkflowsRequest {
|
||||
* @memberof CreateWorkflowsRequest
|
||||
*/
|
||||
'worktypeId'?: number;
|
||||
/**
|
||||
* 自動文字起こし可否フラグ
|
||||
* @type {boolean}
|
||||
* @memberof CreateWorkflowsRequest
|
||||
*/
|
||||
'isAutoTranscription': boolean;
|
||||
/**
|
||||
* テンプレートの内部ID
|
||||
* @type {number}
|
||||
@ -796,6 +883,25 @@ export interface GetAllocatableLicensesResponse {
|
||||
*/
|
||||
'allocatableLicenses': Array<AllocatableLicenseInfo>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetAuthorLicenseResponse
|
||||
*/
|
||||
export interface GetAuthorLicenseResponse {
|
||||
/**
|
||||
* ライセンス種別: NONE / NORMAL / TRIAL / CARD / SPEECH_RECOGNITION
|
||||
* @type {string}
|
||||
* @memberof GetAuthorLicenseResponse
|
||||
*/
|
||||
'licenseType': string;
|
||||
/**
|
||||
* ライセンス有効期限
|
||||
* @type {object}
|
||||
* @memberof GetAuthorLicenseResponse
|
||||
*/
|
||||
'expiryDate': object;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -869,52 +975,46 @@ export interface GetLicenseSummaryRequest {
|
||||
export interface GetLicenseSummaryResponse {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'totalLicense': number;
|
||||
'totalLicense': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'allocatedLicense': number;
|
||||
'allocatedLicense': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'reusableLicense': number;
|
||||
'reusableLicense': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'freeLicense': number;
|
||||
'freeLicense': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'expiringWithin14daysLicense': number;
|
||||
'expiringWithin14daysLicense': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'issueRequesting': number;
|
||||
'issueRequesting': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @type {PartnerLicenseInfoAggregates}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'numberOfRequesting': number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof GetLicenseSummaryResponse
|
||||
*/
|
||||
'shortage': number;
|
||||
'shortage': PartnerLicenseInfoAggregates;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
@ -1054,6 +1154,12 @@ export interface GetPartnerLicensesRequest {
|
||||
* @memberof GetPartnerLicensesRequest
|
||||
*/
|
||||
'accountId': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof GetPartnerLicensesRequest
|
||||
*/
|
||||
'licenseType': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -1725,6 +1831,37 @@ export interface PartnerLicenseInfo {
|
||||
*/
|
||||
'issueRequesting': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface PartnerLicenseInfoAggregates
|
||||
*/
|
||||
export interface PartnerLicenseInfoAggregates {
|
||||
/**
|
||||
* 通常ライセンス(カードライセンス含む)
|
||||
* @type {number}
|
||||
* @memberof PartnerLicenseInfoAggregates
|
||||
*/
|
||||
'normal': number;
|
||||
/**
|
||||
* トライアルライセンス
|
||||
* @type {number}
|
||||
* @memberof PartnerLicenseInfoAggregates
|
||||
*/
|
||||
'trial': number;
|
||||
/**
|
||||
* 自動文字起こしライセンス
|
||||
* @type {number}
|
||||
* @memberof PartnerLicenseInfoAggregates
|
||||
*/
|
||||
'speechRecognition': number;
|
||||
/**
|
||||
* 自動文字起こしアップグレードライセンス
|
||||
* @type {number}
|
||||
* @memberof PartnerLicenseInfoAggregates
|
||||
*/
|
||||
'speechRecognitionUpgrade': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1982,6 +2119,37 @@ export interface RegisterRequest {
|
||||
*/
|
||||
'handler': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface RequestAutoTranscriptionRequest
|
||||
*/
|
||||
export interface RequestAutoTranscriptionRequest {
|
||||
/**
|
||||
* タスクID
|
||||
* @type {number}
|
||||
* @memberof RequestAutoTranscriptionRequest
|
||||
*/
|
||||
'taskId': number;
|
||||
/**
|
||||
* 自動文字起こし用音声ファイル(wav形式)
|
||||
* @type {string}
|
||||
* @memberof RequestAutoTranscriptionRequest
|
||||
*/
|
||||
'voiceFileName': string;
|
||||
/**
|
||||
* 音声コーデック
|
||||
* @type {string}
|
||||
* @memberof RequestAutoTranscriptionRequest
|
||||
*/
|
||||
'voiceCodec': string;
|
||||
/**
|
||||
* 文字起こし言語
|
||||
* @type {string}
|
||||
* @memberof RequestAutoTranscriptionRequest
|
||||
*/
|
||||
'language': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -2256,6 +2424,12 @@ export interface Task {
|
||||
* @memberof Task
|
||||
*/
|
||||
'transcriptionFinishedDate'?: string;
|
||||
/**
|
||||
* 自動文字起こしステータス NotApplicable / InProgress / Success / Failure / Abort
|
||||
* @type {string}
|
||||
* @memberof Task
|
||||
*/
|
||||
'autoTranscriptionStatus': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -2622,6 +2796,12 @@ export interface UpdateWorkflowRequest {
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'worktypeId'?: number;
|
||||
/**
|
||||
* 自動文字起こし可否フラグ
|
||||
* @type {boolean}
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'isAutoTranscription': boolean;
|
||||
/**
|
||||
* テンプレートの内部ID
|
||||
* @type {number}
|
||||
@ -2744,6 +2924,12 @@ export interface User {
|
||||
* @memberof User
|
||||
*/
|
||||
'licenseStatus': string;
|
||||
/**
|
||||
* TRIAL/NORMAL/CARD/SPEECH_RECOGNITION/SPEECH_RECOGNITION_UPGRADE
|
||||
* @type {string}
|
||||
* @memberof User
|
||||
*/
|
||||
'licenseType'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -2763,6 +2949,12 @@ export interface Workflow {
|
||||
* @memberof Workflow
|
||||
*/
|
||||
'author': Author;
|
||||
/**
|
||||
* 自動文字起こし可否フラグ
|
||||
* @type {boolean}
|
||||
* @memberof Workflow
|
||||
*/
|
||||
'isAutoTranscription': boolean;
|
||||
/**
|
||||
*
|
||||
* @type {WorkflowWorktype}
|
||||
@ -6117,13 +6309,56 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
|
||||
};
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
*
|
||||
* @param {string} authorId Author Id
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
uploadFinished: async (audioUploadFinishedRequest: AudioUploadFinishedRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
filesControllerGetAuthorLicense: async (authorId: string, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'authorId' is not null or undefined
|
||||
assertParamExists('filesControllerGetAuthorLicense', 'authorId', authorId)
|
||||
const localVarPath = `/files/author-license/{authorId}`
|
||||
.replace(`{${"authorId"}}`, encodeURIComponent(String(authorId)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
uploadFinished: async (audioUploadFinishedRequest: AudioUploadFinishedRequest, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'audioUploadFinishedRequest' is not null or undefined
|
||||
assertParamExists('uploadFinished', 'audioUploadFinishedRequest', audioUploadFinishedRequest)
|
||||
const localVarPath = `/files/audio/upload-finished`;
|
||||
@ -6142,6 +6377,10 @@ export const FilesApiAxiosParamCreator = function (configuration?: Configuration
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
@ -6314,14 +6553,28 @@ export const FilesApiFp = function(configuration?: Configuration) {
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
*
|
||||
* @param {string} authorId Author Id
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadFinishedResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFinished(audioUploadFinishedRequest, options);
|
||||
async filesControllerGetAuthorLicense(authorId: string, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetAuthorLicenseResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.filesControllerGetAuthorLicense(authorId, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['FilesApi.filesControllerGetAuthorLicense']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadFinishedResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFinished(audioUploadFinishedRequest, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['FilesApi.uploadFinished']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
@ -6404,14 +6657,25 @@ export const FilesApiFactory = function (configuration?: Configuration, basePath
|
||||
return localVarFp.fileRename(fileRenameRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
*
|
||||
* @param {string} authorId Author Id
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: any): AxiosPromise<AudioUploadFinishedResponse> {
|
||||
return localVarFp.uploadFinished(audioUploadFinishedRequest, options).then((request) => request(axios, basePath));
|
||||
filesControllerGetAuthorLicense(authorId: string, xApiVersion?: string, options?: any): AxiosPromise<GetAuthorLicenseResponse> {
|
||||
return localVarFp.filesControllerGetAuthorLicense(authorId, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, xApiVersion?: string, options?: any): AxiosPromise<AudioUploadFinishedResponse> {
|
||||
return localVarFp.uploadFinished(audioUploadFinishedRequest, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* ログイン中ユーザー用のBlob Storage上の音声ファイルのアップロード先アクセスURLを取得します
|
||||
@ -6488,15 +6752,28 @@ export class FilesApi extends BaseAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
*
|
||||
* @param {string} authorId Author Id
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof FilesApi
|
||||
*/
|
||||
public uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, options?: AxiosRequestConfig) {
|
||||
return FilesApiFp(this.configuration).uploadFinished(audioUploadFinishedRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
public filesControllerGetAuthorLicense(authorId: string, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return FilesApiFp(this.configuration).filesControllerGetAuthorLicense(authorId, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* アップロードが完了した音声ファイルの情報を登録し、文字起こしタスクを生成します
|
||||
* @summary
|
||||
* @param {AudioUploadFinishedRequest} audioUploadFinishedRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof FilesApi
|
||||
*/
|
||||
public uploadFinished(audioUploadFinishedRequest: AudioUploadFinishedRequest, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return FilesApiFp(this.configuration).uploadFinished(audioUploadFinishedRequest, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -6665,10 +6942,13 @@ export const LicensesApiAxiosParamCreator = function (configuration?: Configurat
|
||||
/**
|
||||
* 割り当て可能なライセンスを取得します
|
||||
* @summary
|
||||
* @param {number} userId ユーザID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAllocatableLicenses: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
getAllocatableLicenses: async (userId: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'userId' is not null or undefined
|
||||
assertParamExists('getAllocatableLicenses', 'userId', userId)
|
||||
const localVarPath = `/licenses/allocatable`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@ -6685,6 +6965,10 @@ export const LicensesApiAxiosParamCreator = function (configuration?: Configurat
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (userId !== undefined) {
|
||||
localVarQueryParameter['userId'] = userId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
@ -6828,11 +7112,12 @@ export const LicensesApiFp = function(configuration?: Configuration) {
|
||||
/**
|
||||
* 割り当て可能なライセンスを取得します
|
||||
* @summary
|
||||
* @param {number} userId ユーザID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getAllocatableLicenses(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetAllocatableLicensesResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllocatableLicenses(options);
|
||||
async getAllocatableLicenses(userId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetAllocatableLicensesResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAllocatableLicenses(userId, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['LicensesApi.getAllocatableLicenses']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
@ -6906,11 +7191,12 @@ export const LicensesApiFactory = function (configuration?: Configuration, baseP
|
||||
/**
|
||||
* 割り当て可能なライセンスを取得します
|
||||
* @summary
|
||||
* @param {number} userId ユーザID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAllocatableLicenses(options?: any): AxiosPromise<GetAllocatableLicensesResponse> {
|
||||
return localVarFp.getAllocatableLicenses(options).then((request) => request(axios, basePath));
|
||||
getAllocatableLicenses(userId: number, options?: any): AxiosPromise<GetAllocatableLicensesResponse> {
|
||||
return localVarFp.getAllocatableLicenses(userId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@ -6981,12 +7267,13 @@ export class LicensesApi extends BaseAPI {
|
||||
/**
|
||||
* 割り当て可能なライセンスを取得します
|
||||
* @summary
|
||||
* @param {number} userId ユーザID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof LicensesApi
|
||||
*/
|
||||
public getAllocatableLicenses(options?: AxiosRequestConfig) {
|
||||
return LicensesApiFp(this.configuration).getAllocatableLicenses(options).then((request) => request(this.axios, this.basePath));
|
||||
public getAllocatableLicenses(userId: number, options?: AxiosRequestConfig) {
|
||||
return LicensesApiFp(this.configuration).getAllocatableLicenses(userId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -7136,6 +7423,96 @@ export class NotificationApi extends BaseAPI {
|
||||
*/
|
||||
export const TasksApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
* 自動文字起こし完了API(外部連携API)
|
||||
* @summary
|
||||
* @param {AutoTranscribeCompleteRequest} autoTranscribeCompleteRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
autoTranscribeComplete: async (autoTranscribeCompleteRequest: AutoTranscribeCompleteRequest, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'autoTranscribeCompleteRequest' is not null or undefined
|
||||
assertParamExists('autoTranscribeComplete', 'autoTranscribeCompleteRequest', autoTranscribeCompleteRequest)
|
||||
const localVarPath = `/tasks/auto-transcribe-complete`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(autoTranscribeCompleteRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 音声テキストSolに自動文字起こし要求を行う
|
||||
* @summary
|
||||
* @param {RequestAutoTranscriptionRequest} requestAutoTranscriptionRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
autoTranscribeRequest: async (requestAutoTranscriptionRequest: RequestAutoTranscriptionRequest, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'requestAutoTranscriptionRequest' is not null or undefined
|
||||
assertParamExists('autoTranscribeRequest', 'requestAutoTranscriptionRequest', requestAutoTranscriptionRequest)
|
||||
const localVarPath = `/tasks/auto-transcribe`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(requestAutoTranscriptionRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクをバックアップします(ステータスをBackupにします)
|
||||
* @summary
|
||||
@ -7203,6 +7580,49 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 指定した音声ファイルに紐づく自動文字起こしタスクの自動文字起こしをキャンセルします
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
cancelAutoTranscribe: async (audioFileId: number, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'audioFileId' is not null or undefined
|
||||
assertParamExists('cancelAutoTranscribe', 'audioFileId', audioFileId)
|
||||
const localVarPath = `/tasks/{audioFileId}/auto-transcribe/cancel`
|
||||
.replace(`{${"audioFileId"}}`, encodeURIComponent(String(audioFileId)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
@ -7332,6 +7752,51 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 音声ファイルを自動文字起こし可能なファイル形式(WAV形式)に変換し、音声テキストソリューション側へ連携するためのBlobStorageに変換後のファイルを格納
|
||||
* @summary
|
||||
* @param {ConvertAudioFileRequest} convertAudioFileRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
convertAudioFile: async (convertAudioFileRequest: ConvertAudioFileRequest, xApiVersion?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'convertAudioFileRequest' is not null or undefined
|
||||
assertParamExists('convertAudioFile', 'convertAudioFileRequest', convertAudioFileRequest)
|
||||
const localVarPath = `/tasks/convert-audio-file`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (xApiVersion != null) {
|
||||
localVarHeaderParameter['x-api-version'] = String(xApiVersion);
|
||||
}
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(convertAudioFileRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクを削除します。
|
||||
* @summary
|
||||
@ -7566,6 +8031,34 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
|
||||
export const TasksApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = TasksApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
* 自動文字起こし完了API(外部連携API)
|
||||
* @summary
|
||||
* @param {AutoTranscribeCompleteRequest} autoTranscribeCompleteRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async autoTranscribeComplete(autoTranscribeCompleteRequest: AutoTranscribeCompleteRequest, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.autoTranscribeComplete(autoTranscribeCompleteRequest, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['TasksApi.autoTranscribeComplete']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 音声テキストSolに自動文字起こし要求を行う
|
||||
* @summary
|
||||
* @param {RequestAutoTranscriptionRequest} requestAutoTranscriptionRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async autoTranscribeRequest(requestAutoTranscriptionRequest: RequestAutoTranscriptionRequest, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.autoTranscribeRequest(requestAutoTranscriptionRequest, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['TasksApi.autoTranscribeRequest']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクをバックアップします(ステータスをBackupにします)
|
||||
* @summary
|
||||
@ -7592,6 +8085,20 @@ export const TasksApiFp = function(configuration?: Configuration) {
|
||||
const operationBasePath = operationServerMap['TasksApi.cancel']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 指定した音声ファイルに紐づく自動文字起こしタスクの自動文字起こしをキャンセルします
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async cancelAutoTranscribe(audioFileId: number, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.cancelAutoTranscribe(audioFileId, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['TasksApi.cancelAutoTranscribe']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクのチェックアウト候補を変更します。
|
||||
* @summary
|
||||
@ -7632,6 +8139,20 @@ export const TasksApiFp = function(configuration?: Configuration) {
|
||||
const operationBasePath = operationServerMap['TasksApi.checkout']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 音声ファイルを自動文字起こし可能なファイル形式(WAV形式)に変換し、音声テキストソリューション側へ連携するためのBlobStorageに変換後のファイルを格納
|
||||
* @summary
|
||||
* @param {ConvertAudioFileRequest} convertAudioFileRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async convertAudioFile(convertAudioFileRequest: ConvertAudioFileRequest, xApiVersion?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConvertAudioFileResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.convertAudioFile(convertAudioFileRequest, xApiVersion, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['TasksApi.convertAudioFile']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクを削除します。
|
||||
* @summary
|
||||
@ -7713,6 +8234,28 @@ export const TasksApiFp = function(configuration?: Configuration) {
|
||||
export const TasksApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = TasksApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
* 自動文字起こし完了API(外部連携API)
|
||||
* @summary
|
||||
* @param {AutoTranscribeCompleteRequest} autoTranscribeCompleteRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
autoTranscribeComplete(autoTranscribeCompleteRequest: AutoTranscribeCompleteRequest, xApiVersion?: string, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.autoTranscribeComplete(autoTranscribeCompleteRequest, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 音声テキストSolに自動文字起こし要求を行う
|
||||
* @summary
|
||||
* @param {RequestAutoTranscriptionRequest} requestAutoTranscriptionRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
autoTranscribeRequest(requestAutoTranscriptionRequest: RequestAutoTranscriptionRequest, xApiVersion?: string, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.autoTranscribeRequest(requestAutoTranscriptionRequest, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクをバックアップします(ステータスをBackupにします)
|
||||
* @summary
|
||||
@ -7733,6 +8276,17 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
|
||||
cancel(audioFileId: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.cancel(audioFileId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 指定した音声ファイルに紐づく自動文字起こしタスクの自動文字起こしをキャンセルします
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
cancelAutoTranscribe(audioFileId: number, xApiVersion?: string, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.cancelAutoTranscribe(audioFileId, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクのチェックアウト候補を変更します。
|
||||
* @summary
|
||||
@ -7764,6 +8318,17 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
|
||||
checkout(audioFileId: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.checkout(audioFileId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 音声ファイルを自動文字起こし可能なファイル形式(WAV形式)に変換し、音声テキストソリューション側へ連携するためのBlobStorageに変換後のファイルを格納
|
||||
* @summary
|
||||
* @param {ConvertAudioFileRequest} convertAudioFileRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
convertAudioFile(convertAudioFileRequest: ConvertAudioFileRequest, xApiVersion?: string, options?: any): AxiosPromise<ConvertAudioFileResponse> {
|
||||
return localVarFp.convertAudioFile(convertAudioFileRequest, xApiVersion, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクを削除します。
|
||||
* @summary
|
||||
@ -7830,6 +8395,32 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class TasksApi extends BaseAPI {
|
||||
/**
|
||||
* 自動文字起こし完了API(外部連携API)
|
||||
* @summary
|
||||
* @param {AutoTranscribeCompleteRequest} autoTranscribeCompleteRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TasksApi
|
||||
*/
|
||||
public autoTranscribeComplete(autoTranscribeCompleteRequest: AutoTranscribeCompleteRequest, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return TasksApiFp(this.configuration).autoTranscribeComplete(autoTranscribeCompleteRequest, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 音声テキストSolに自動文字起こし要求を行う
|
||||
* @summary
|
||||
* @param {RequestAutoTranscriptionRequest} requestAutoTranscriptionRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TasksApi
|
||||
*/
|
||||
public autoTranscribeRequest(requestAutoTranscriptionRequest: RequestAutoTranscriptionRequest, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return TasksApiFp(this.configuration).autoTranscribeRequest(requestAutoTranscriptionRequest, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した文字起こしタスクをバックアップします(ステータスをBackupにします)
|
||||
* @summary
|
||||
@ -7854,6 +8445,19 @@ export class TasksApi extends BaseAPI {
|
||||
return TasksApiFp(this.configuration).cancel(audioFileId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した音声ファイルに紐づく自動文字起こしタスクの自動文字起こしをキャンセルします
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TasksApi
|
||||
*/
|
||||
public cancelAutoTranscribe(audioFileId: number, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return TasksApiFp(this.configuration).cancelAutoTranscribe(audioFileId, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した文字起こしタスクのチェックアウト候補を変更します。
|
||||
* @summary
|
||||
@ -7891,6 +8495,19 @@ export class TasksApi extends BaseAPI {
|
||||
return TasksApiFp(this.configuration).checkout(audioFileId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 音声ファイルを自動文字起こし可能なファイル形式(WAV形式)に変換し、音声テキストソリューション側へ連携するためのBlobStorageに変換後のファイルを格納
|
||||
* @summary
|
||||
* @param {ConvertAudioFileRequest} convertAudioFileRequest
|
||||
* @param {string} [xApiVersion] APIバージョン
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TasksApi
|
||||
*/
|
||||
public convertAudioFile(convertAudioFileRequest: ConvertAudioFileRequest, xApiVersion?: string, options?: AxiosRequestConfig) {
|
||||
return TasksApiFp(this.configuration).convertAudioFile(convertAudioFileRequest, xApiVersion, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した文字起こしタスクを削除します。
|
||||
* @summary
|
||||
|
||||
@ -6,6 +6,14 @@ export const STATUS = {
|
||||
BACKUP: "Backup",
|
||||
} as const;
|
||||
|
||||
export const AUTO_TRANSCRIPTION_STATUS = {
|
||||
NOTAPPLICABLE: "NotApplicable",
|
||||
INPROGRESS: "InProgress",
|
||||
SUCCESS: "Success",
|
||||
FAILURE: "Failure",
|
||||
ABORT: "Abort",
|
||||
} as const;
|
||||
|
||||
export type StatusType = (typeof STATUS)[keyof typeof STATUS];
|
||||
|
||||
export const LIMIT_TASK_NUM = 100;
|
||||
@ -64,6 +72,7 @@ export interface DisplayInfoType {
|
||||
UploadDate: boolean;
|
||||
TranscriptionStartedDate: boolean;
|
||||
TranscriptionFinishedDate: boolean;
|
||||
AutoTranscriptionStatus: boolean;
|
||||
Transcriptionist: boolean;
|
||||
Comment: boolean;
|
||||
OptionItem1: boolean;
|
||||
@ -93,6 +102,7 @@ export const INIT_DISPLAY_INFO: DisplayInfoType = {
|
||||
UploadDate: false,
|
||||
TranscriptionStartedDate: true,
|
||||
TranscriptionFinishedDate: true,
|
||||
AutoTranscriptionStatus: true,
|
||||
Transcriptionist: true,
|
||||
Comment: true,
|
||||
OptionItem1: false,
|
||||
@ -115,3 +125,19 @@ export const PRIORITY = {
|
||||
NORMAL: "Normal",
|
||||
HIGH: "High",
|
||||
} as const;
|
||||
|
||||
// 言語JSONのキーマッピング
|
||||
export const AUTO_TRANSCRIPTION_STATUS_LANGUAGE_MAP: Record<
|
||||
string,
|
||||
| "dictationPage.label.autoTranscriptionStatusNotApplicable"
|
||||
| "dictationPage.label.autoTranscriptionStatusInProgress"
|
||||
| "dictationPage.label.autoTranscriptionStatusSuccess"
|
||||
| "dictationPage.label.autoTranscriptionStatusFailure"
|
||||
| "dictationPage.label.autoTranscriptionStatusAbort"
|
||||
> = {
|
||||
NotApplicable: "dictationPage.label.autoTranscriptionStatusNotApplicable",
|
||||
InProgress: "dictationPage.label.autoTranscriptionStatusInProgress",
|
||||
Success: "dictationPage.label.autoTranscriptionStatusSuccess",
|
||||
Failure: "dictationPage.label.autoTranscriptionStatusFailure",
|
||||
Abort: "dictationPage.label.autoTranscriptionStatusAbort",
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
cancelAsync,
|
||||
deleteTaskAsync,
|
||||
renameFileAsync,
|
||||
cancelSpeechRecognitionAsync,
|
||||
} from "./operations";
|
||||
import {
|
||||
SORTABLE_COLUMN,
|
||||
@ -239,7 +240,6 @@ export const dictationSlice = createSlice({
|
||||
builder.addCase(deleteTaskAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
|
||||
builder.addCase(renameFileAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
@ -249,6 +249,15 @@ export const dictationSlice = createSlice({
|
||||
builder.addCase(renameFileAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(cancelSpeechRecognitionAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(cancelSpeechRecognitionAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(cancelSpeechRecognitionAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -1014,3 +1014,69 @@ export const renameFileAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const cancelSpeechRecognitionAsync = createAsyncThunk<
|
||||
{
|
||||
/** empty */
|
||||
},
|
||||
{
|
||||
audioFileId: number;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("dictations/cancelSpeechRecognitionAsync", async (args, thunkApi) => {
|
||||
const { audioFileId } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const accessToken = getAccessToken(state.auth);
|
||||
const config = new Configuration(configuration);
|
||||
const tasksApi = new TasksApi(config);
|
||||
try {
|
||||
await tasksApi.cancelAutoTranscribe(
|
||||
audioFileId,
|
||||
// APIバージョン2を固定で指定
|
||||
"2",
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
// 自動文字起こしステータスが[InProgress]以外、またはタスクが存在しない場合
|
||||
if (error.code === "E010601" || error.code === "E010603") {
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID(
|
||||
"dictationPage.message.cancelSpeechRecognitionError"
|
||||
),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export const LICENSE_TYPE = {
|
||||
TRIAL: "TRIAL",
|
||||
NORMAL: "NORMAL",
|
||||
CARD: "CARD",
|
||||
SPEECH_RECOGNITION: "SPEECH_RECOGNITION",
|
||||
SPEECH_RECOGNITION_UPGRADE: "SPEECH_RECOGNITION_UPGRADE",
|
||||
} as const;
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./state";
|
||||
export * from "./constants";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
export * from "./licenseSlice";
|
||||
|
||||
@ -6,6 +6,7 @@ const initialState: LicenseOrdersState = {
|
||||
apps: {
|
||||
poNumber: "",
|
||||
newOrder: 0,
|
||||
licenseType: "",
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
@ -21,6 +22,13 @@ export const licenseSlice = createSlice({
|
||||
const { newOrder } = action.payload;
|
||||
state.apps.newOrder = newOrder;
|
||||
},
|
||||
changeLicenseType: (
|
||||
state,
|
||||
action: PayloadAction<{ licenseType: string }>
|
||||
) => {
|
||||
const { licenseType } = action.payload;
|
||||
state.apps.licenseType = licenseType;
|
||||
},
|
||||
cleanupApps: (state) => {
|
||||
state.apps = initialState.apps;
|
||||
},
|
||||
@ -38,7 +46,11 @@ export const licenseSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { changePoNumber, changeNewOrder, cleanupApps } =
|
||||
licenseSlice.actions;
|
||||
export const {
|
||||
changePoNumber,
|
||||
changeNewOrder,
|
||||
changeLicenseType,
|
||||
cleanupApps,
|
||||
} = licenseSlice.actions;
|
||||
|
||||
export default licenseSlice.reducer;
|
||||
|
||||
@ -15,6 +15,7 @@ export const orderLicenseAsync = createAsyncThunk<
|
||||
// パラメータ
|
||||
poNumber: string;
|
||||
orderCount: number;
|
||||
licenseType: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
@ -23,7 +24,7 @@ export const orderLicenseAsync = createAsyncThunk<
|
||||
};
|
||||
}
|
||||
>("licenses/orderLicenseAsync", async (args, thunkApi) => {
|
||||
const { poNumber, orderCount } = args;
|
||||
const { poNumber, orderCount, licenseType } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
@ -38,6 +39,7 @@ export const orderLicenseAsync = createAsyncThunk<
|
||||
{
|
||||
poNumber,
|
||||
orderCount,
|
||||
licenseType,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { RootState } from "app/store";
|
||||
import { LICENSE_TYPE } from "./constants";
|
||||
|
||||
export const selectInputValidationErrors = (state: RootState) => {
|
||||
const { poNumber, newOrder } = state.license.apps;
|
||||
const { poNumber, newOrder, licenseType } = state.license.apps;
|
||||
|
||||
// 必須項目のチェック
|
||||
const hasErrorEmptyPoNumber = poNumber === "";
|
||||
@ -9,11 +10,14 @@ export const selectInputValidationErrors = (state: RootState) => {
|
||||
|
||||
const hasErrorIncorrectPoNumber = checkErrorIncorrectPoNumber(poNumber);
|
||||
const hasErrorIncorrectNewOrder = checkErrorIncorrectNewOrder(newOrder);
|
||||
const hasErrorIncorrectLicenseType =
|
||||
checkErrorIncorrectLicenseType(licenseType);
|
||||
|
||||
return {
|
||||
hasErrorEmptyPoNumber,
|
||||
hasErrorIncorrectPoNumber,
|
||||
hasErrorIncorrectNewOrder,
|
||||
hasErrorIncorrectLicenseType,
|
||||
};
|
||||
};
|
||||
export const checkErrorIncorrectPoNumber = (poNumber: string): boolean => {
|
||||
@ -23,6 +27,7 @@ export const checkErrorIncorrectPoNumber = (poNumber: string): boolean => {
|
||||
|
||||
return !charaType;
|
||||
};
|
||||
|
||||
export const checkErrorIncorrectNewOrder = (newOrder: number): boolean => {
|
||||
// 0以下の場合はエラー
|
||||
if (newOrder <= 0) {
|
||||
@ -32,8 +37,25 @@ export const checkErrorIncorrectNewOrder = (newOrder: number): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const checkErrorIncorrectLicenseType = (
|
||||
licenseType: string
|
||||
): boolean => {
|
||||
const allowLicenseType: string[] = [
|
||||
LICENSE_TYPE.NORMAL,
|
||||
LICENSE_TYPE.SPEECH_RECOGNITION,
|
||||
LICENSE_TYPE.SPEECH_RECOGNITION_UPGRADE,
|
||||
];
|
||||
if (!allowLicenseType.includes(licenseType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const selectPoNumber = (state: RootState) => state.license.apps.poNumber;
|
||||
export const selectNewOrder = (state: RootState) => state.license.apps.newOrder;
|
||||
export const selectLicenseType = (state: RootState) =>
|
||||
state.license.apps.licenseType;
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.license.apps.isLoading;
|
||||
|
||||
@ -5,5 +5,6 @@ export interface LicenseOrdersState {
|
||||
export interface Apps {
|
||||
poNumber: string;
|
||||
newOrder: number;
|
||||
licenseType: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export const LIMIT_ORDER_HISORY_NUM = 50;
|
||||
export const LIMIT_ORDER_HISTORY_NUM = 50;
|
||||
|
||||
export const STATUS = {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@ -12,3 +12,18 @@ export const LICENSE_TYPE = {
|
||||
NORMAL: "NORMAL",
|
||||
TRIAL: "TRIAL",
|
||||
} as const;
|
||||
|
||||
// 言語JSONのキーマッピング
|
||||
export const LICENSE_TYPE_LANGUAGE_MAP: Record<
|
||||
string,
|
||||
| "orderHistoriesPage.label.normal"
|
||||
| "orderHistoriesPage.label.speechRecognition"
|
||||
| "orderHistoriesPage.label.speechRecognitionUpgrade"
|
||||
| "orderHistoriesPage.label.trial"
|
||||
> = {
|
||||
NORMAL: "orderHistoriesPage.label.normal",
|
||||
SPEECH_RECOGNITION: "orderHistoriesPage.label.speechRecognition",
|
||||
SPEECH_RECOGNITION_UPGRADE:
|
||||
"orderHistoriesPage.label.speechRecognitionUpgrade",
|
||||
TRIAL: "orderHistoriesPage.label.trial",
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
cancelOrderAsync,
|
||||
cancelIssueAsync,
|
||||
} from "./operations";
|
||||
import { LIMIT_ORDER_HISORY_NUM } from "./constants";
|
||||
import { LIMIT_ORDER_HISTORY_NUM } from "./constants";
|
||||
|
||||
const initialState: LicenseOrderHistoryState = {
|
||||
domain: {
|
||||
@ -15,7 +15,7 @@ const initialState: LicenseOrderHistoryState = {
|
||||
companyName: "",
|
||||
},
|
||||
apps: {
|
||||
limit: LIMIT_ORDER_HISORY_NUM,
|
||||
limit: LIMIT_ORDER_HISTORY_NUM,
|
||||
offset: 0,
|
||||
isLoading: false,
|
||||
LicenseOrder: undefined,
|
||||
|
||||
@ -9,14 +9,48 @@ import {
|
||||
const initialState: LicenseSummaryState = {
|
||||
domain: {
|
||||
licenseSummaryInfo: {
|
||||
totalLicense: 0,
|
||||
allocatedLicense: 0,
|
||||
reusableLicense: 0,
|
||||
freeLicense: 0,
|
||||
expiringWithin14daysLicense: 0,
|
||||
issueRequesting: 0,
|
||||
numberOfRequesting: 0,
|
||||
shortage: 0,
|
||||
totalLicense: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
allocatedLicense: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
reusableLicense: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
freeLicense: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
expiringWithin14daysLicense: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
issueRequesting: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
shortage: {
|
||||
normal: 0,
|
||||
speechRecognition: 0,
|
||||
speechRecognitionUpgrade: 0,
|
||||
trial: 0,
|
||||
},
|
||||
storageSize: 0,
|
||||
usedSize: 0,
|
||||
isStorageAvailable: false,
|
||||
|
||||
@ -1,22 +1,12 @@
|
||||
import { GetLicenseSummaryResponse } from "../../../api";
|
||||
|
||||
export interface LicenseSummaryState {
|
||||
domain: Domain;
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
licenseSummaryInfo: {
|
||||
totalLicense: number;
|
||||
allocatedLicense: number;
|
||||
reusableLicense: number;
|
||||
freeLicense: number;
|
||||
expiringWithin14daysLicense: number;
|
||||
issueRequesting: number;
|
||||
numberOfRequesting: number;
|
||||
shortage: number;
|
||||
storageSize: number;
|
||||
usedSize: number;
|
||||
isStorageAvailable: boolean;
|
||||
};
|
||||
licenseSummaryInfo: GetLicenseSummaryResponse;
|
||||
accountInfo: {
|
||||
companyName: string;
|
||||
};
|
||||
|
||||
@ -1 +1,8 @@
|
||||
export const ACCOUNTS_VIEW_LIMIT = 10;
|
||||
export const LICENSE_TYPE = {
|
||||
TRIAL: "TRIAL",
|
||||
NORMAL: "NORMAL",
|
||||
CARD: "CARD",
|
||||
SPEECH_RECOGNITION: "SPEECH_RECOGNITION",
|
||||
SPEECH_RECOGNITION_UPGRADE: "SPEECH_RECOGNITION_UPGRADE",
|
||||
} as const;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user