Compare commits
251 Commits
release-20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9a254b63 | ||
|
|
ff5533b647 | ||
|
|
b3845187f6 | ||
|
|
019c818a19 | ||
|
|
1137d826ae | ||
|
|
b529388871 | ||
|
|
aef17893d9 | ||
|
|
4f598b0017 | ||
|
|
b71ec627d7 | ||
|
|
af56f8ccad | ||
|
|
0b451ed62f | ||
|
|
11395279af | ||
|
|
a07cfe51aa | ||
|
|
ad397f6fe7 | ||
|
|
85fdec2e5a | ||
|
|
f1b75a7ff0 | ||
|
|
6690302ac3 | ||
|
|
1320222d79 | ||
|
|
baea8ce5e5 | ||
|
|
d69126a980 | ||
|
|
79a2b9f0a3 | ||
|
|
2a755f2bd3 | ||
|
|
edb8a79f94 | ||
|
|
4a5136deee | ||
|
|
f2eaba7e5f | ||
|
|
3b576c6a47 | ||
|
|
557fc48d05 | ||
|
|
353d5ad462 | ||
|
|
df55be5e19 | ||
|
|
35425c576a | ||
|
|
d4abc0fcfb | ||
|
|
11c0937214 | ||
|
|
12fe7ac9bb | ||
|
|
b997b928b8 | ||
|
|
10f57c6aeb | ||
|
|
254cbdf0d6 | ||
|
|
6b1286ba13 | ||
|
|
aea66f4616 | ||
|
|
c73340ec51 | ||
|
|
07904c35e6 | ||
|
|
474cdd56c6 | ||
|
|
83732fa1b5 | ||
|
|
6b1650a634 | ||
|
|
4f7d65f0e8 | ||
|
|
1f10a3f096 | ||
|
|
909c2a6d55 | ||
|
|
37666a00c7 | ||
|
|
b7925f311b | ||
|
|
97620881d3 | ||
|
|
0e68f26c57 | ||
|
|
9736d23653 | ||
|
|
261a5afbdd | ||
|
|
3a7c1765ee | ||
|
|
c16a8b023c | ||
|
|
4b16e6a004 | ||
|
|
bd8f035c46 | ||
|
|
bdcce37b5a | ||
|
|
7af5c50a27 | ||
|
|
5860da285e | ||
|
|
3ece576476 | ||
|
|
4dac420bed | ||
|
|
ddf338bc72 | ||
|
|
0cca61517c | ||
|
|
fe5e8b8e1c | ||
|
|
dfdc6a33ad | ||
|
|
35e2d626a0 | ||
|
|
228e21ba78 | ||
|
|
68df7cd728 | ||
|
|
ffd6eb4e68 | ||
|
|
e205209b12 | ||
|
|
3ea84a3597 | ||
|
|
b71c4398d2 | ||
|
|
c26ad130ed | ||
|
|
279a9ab037 | ||
|
|
4e00c03ef2 | ||
|
|
c813ddc0ac | ||
|
|
23f8b54011 | ||
|
|
8122f6f4e1 | ||
|
|
e76242bddd | ||
|
|
af0ba78ae9 | ||
|
|
1904b95adf | ||
|
|
d43cece48a | ||
|
|
ba7196cac1 | ||
|
|
7eecb001c6 | ||
|
|
0b01da936d | ||
|
|
b88c0d9b96 | ||
|
|
b7554e30ff | ||
|
|
b03cda3ccc | ||
|
|
7bfd424a64 | ||
|
|
9ee29e91ba | ||
|
|
b24059b538 | ||
|
|
a7b18d8151 | ||
|
|
23862ad3ac | ||
|
|
566da623bf | ||
|
|
69241ed36c | ||
|
|
c469f943f1 | ||
|
|
0a714f8484 | ||
|
|
1d2089b0c4 | ||
|
|
f975ecf551 | ||
|
|
e6d27d7810 | ||
|
|
f209c7359e | ||
|
|
33d4ab3d2f | ||
|
|
07bca1d638 | ||
|
|
09c21eafa7 | ||
|
|
e6d6e477d9 | ||
|
|
915483c109 | ||
|
|
c600d9f818 | ||
|
|
5147f853ae | ||
|
|
0288292058 | ||
|
|
8752448eed | ||
|
|
1d71bef7aa | ||
|
|
114ded790e | ||
|
|
ae638b16be | ||
|
|
133db833ee | ||
|
|
1f0cf50166 | ||
|
|
eb6b413adb | ||
|
|
6e93a5be79 | ||
|
|
ac3d523c0e | ||
|
|
75f0a49fc1 | ||
|
|
cab7a75ec1 | ||
|
|
b8af6fb6b2 | ||
|
|
13e0793219 | ||
|
|
f80912c617 | ||
|
|
66c643677d | ||
|
|
1451d6f584 | ||
|
|
5f4a05044b | ||
|
|
02a4784e58 | ||
|
|
9256566f89 | ||
|
|
9b61443b2f | ||
|
|
5169092892 | ||
|
|
017276c94a | ||
|
|
e96e8ea54a | ||
|
|
2b68a9f054 | ||
|
|
83e297cc9b | ||
|
|
9f5ccabb0c | ||
|
|
415fd2eb58 | ||
|
|
340aa73bde | ||
|
|
43561f237e | ||
|
|
311eb98236 | ||
|
|
8b2613b9f6 | ||
|
|
071bd2b85e | ||
|
|
1ef696efe8 | ||
|
|
c059a2eabd | ||
|
|
84fc89071a | ||
|
|
ccc03da62d | ||
|
|
ff4cd35ed3 | ||
|
|
f386a8f7e0 | ||
|
|
2e6b7c8ab5 | ||
|
|
85edd2296e | ||
|
|
869cbd43e0 | ||
|
|
aca9bcf496 | ||
|
|
146b8a6e40 | ||
|
|
9dc80b4965 | ||
|
|
04c726e964 | ||
|
|
f265d0ff45 | ||
|
|
e448e8d249 | ||
|
|
eda88aa048 | ||
|
|
da40e8f09c | ||
|
|
d6a47932e7 | ||
|
|
7160e0ee2e | ||
|
|
2220e2560f | ||
|
|
31de71f743 | ||
|
|
7ff563f644 | ||
|
|
a47ebaa9df | ||
|
|
88ce6a2c9e | ||
|
|
cad3a99f70 | ||
|
|
0ebd2ab17e | ||
|
|
9ca9b7a144 | ||
|
|
fd3e584fac | ||
|
|
ce6e09a7d0 | ||
|
|
6d56255a5a | ||
|
|
363f12f86f | ||
|
|
0be9c26f09 | ||
|
|
71127a6db9 | ||
|
|
f6d39a4c26 | ||
|
|
34d1dd5629 | ||
|
|
f0d71937e3 | ||
|
|
dd8bddc971 | ||
|
|
ddd4d31f25 | ||
|
|
5305984b1a | ||
|
|
0ab6488f58 | ||
|
|
5a78a6668f | ||
|
|
d0628caa05 | ||
|
|
68d1a1796b | ||
|
|
c95fb1e1f6 | ||
|
|
c1f370faaf | ||
|
|
13d421c2bc | ||
|
|
b524fd5995 | ||
|
|
a1b59de44d | ||
|
|
fbdfeee73c | ||
|
|
f03342bc55 | ||
|
|
a65d6a2774 | ||
|
|
cb68c16eb8 | ||
|
|
c31bb47bb8 | ||
|
|
dc52ec2022 | ||
|
|
bc87bcd5cf | ||
|
|
e3ee9412c9 | ||
|
|
8110b9cccc | ||
|
|
ebbf957419 | ||
|
|
12d168d14c | ||
|
|
439ce7de63 | ||
|
|
5adf7ed12e | ||
|
|
f8183399e2 | ||
|
|
e44cb3b955 | ||
|
|
a9aca6e4ff | ||
|
|
562db3def9 | ||
|
|
ecb28b9328 | ||
|
|
cdef84e269 | ||
|
|
fc7d271a29 | ||
|
|
aef30c8cbe | ||
|
|
c0b99203da | ||
|
|
63191a3b61 | ||
|
|
3877a4670d | ||
|
|
bbbd3e757b | ||
|
|
83efd97bdf | ||
|
|
447b0e280c | ||
|
|
cd277f3f9a | ||
|
|
270122b135 | ||
|
|
32d8c6b896 | ||
|
|
91c27b7684 | ||
|
|
548c7a05e9 | ||
|
|
eaf1b3c8b8 | ||
|
|
19b544540e | ||
|
|
feeec9d1f5 | ||
|
|
4442c7cbbe | ||
|
|
1e8bc39c7f | ||
|
|
ce3deecd1b | ||
|
|
8e159a1c2a | ||
|
|
df74dc358c | ||
|
|
186896da15 | ||
|
|
4be13e002d | ||
|
|
cf30b97a16 | ||
|
|
44759b1aac | ||
|
|
5ab2f02c56 | ||
|
|
f595cae1b8 | ||
|
|
48a2bddfd9 | ||
|
|
fbcafd2014 | ||
|
|
9f7c8c99c0 | ||
|
|
92193d499a | ||
|
|
c32b38b783 | ||
|
|
08a37ff264 | ||
|
|
f00861702a | ||
|
|
8dfbcea0da | ||
|
|
8aa45baee8 | ||
|
|
f2f8728319 | ||
|
|
ded446de93 | ||
|
|
1524ec2473 | ||
|
|
a7bb32ec4a | ||
|
|
d08c6c99af | ||
|
|
8793606070 | ||
|
|
81c299dd99 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
environment_building_tools/logfile.log
|
||||
312
azure-pipelines-staging-ccb.yml
Normal file
312
azure-pipelines-staging-ccb.yml
Normal file
@ -0,0 +1,312 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- release-ccb
|
||||
tags:
|
||||
include:
|
||||
- stage-*
|
||||
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin release-ccb:release-ccb
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) release-ccb; then
|
||||
echo "This commit is in the release-ccb branch."
|
||||
else
|
||||
echo "This commit is not in the release-ccb branch."
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがrelease-ccbブランチに存在するか確認'
|
||||
- job: backend_test
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: UnitTest
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_server/.devcontainer
|
||||
script: |
|
||||
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: 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
|
||||
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
|
||||
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: 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: function_test
|
||||
dependsOn: frontend_build_staging
|
||||
condition: succeeded('frontend_build_staging')
|
||||
displayName: UnitTest
|
||||
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: |
|
||||
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: 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: backend_deploy
|
||||
dependsOn: function_build
|
||||
condition: succeeded('function_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: 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: 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: smoke_test
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap 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: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('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_CCB/$(db-name-ccb)/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_ccb
|
||||
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
@ -0,0 +1,363 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConnectionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- release-ph1-enhance
|
||||
tags:
|
||||
include:
|
||||
- stage-*
|
||||
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin release-ph1-enhance:release-ph1-enhance
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph1-enhance; then
|
||||
echo "This commit is in the release-ph1-enhance branch."
|
||||
else
|
||||
echo "This commit is not in the release-ph1-enhance branch."
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがrelease-ph1-enhanceブランチに存在するか確認'
|
||||
- job: backend_test
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: UnitTest
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
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: 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
|
||||
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
|
||||
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: 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: 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: function_test
|
||||
dependsOn: frontend_build_production
|
||||
condition: succeeded('frontend_build_production')
|
||||
displayName: UnitTest
|
||||
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: 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: backend_deploy
|
||||
dependsOn: function_build
|
||||
condition: succeeded('function_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: 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: 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: smoke_test
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap 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: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('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-ph1-enhance)/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
|
||||
@ -43,11 +43,14 @@ jobs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_server/.devcontainer
|
||||
script: |
|
||||
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: backend_build
|
||||
dependsOn: backend_test
|
||||
condition: succeeded('backend_test')
|
||||
@ -170,9 +173,32 @@ jobs:
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: function_build
|
||||
- job: function_test
|
||||
dependsOn: frontend_build_production
|
||||
condition: succeeded('frontend_build_production')
|
||||
displayName: UnitTest
|
||||
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: function_build
|
||||
dependsOn: function_test
|
||||
condition: succeeded('function_test')
|
||||
displayName: Build And Push Function Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
@ -186,32 +212,6 @@ jobs:
|
||||
command: ci
|
||||
workingDir: dictation_function
|
||||
verbose: false
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: 'omds-service-connection-stg'
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
SecretsFilter: '*'
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
cd dictation_function
|
||||
npm run test
|
||||
env:
|
||||
TENANT_NAME: xxxxxxxxxxxx
|
||||
SIGNIN_FLOW_NAME: xxxxxxxxxxxx
|
||||
ADB2C_TENANT_ID: $(adb2c-tenant-id)
|
||||
ADB2C_CLIENT_ID: $(adb2c-client-id)
|
||||
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
|
||||
ADB2C_ORIGIN: xxxxxx
|
||||
SENDGRID_API_KEY: $(sendgrid-api-key)
|
||||
MAIL_FROM: xxxxxx
|
||||
APP_DOMAIN: xxxxxxxxx
|
||||
REDIS_HOST: xxxxxxxxxxxx
|
||||
REDIS_PORT: 0
|
||||
REDIS_PASSWORD: xxxxxxxxxxxx
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
|
||||
1
data_migration_tools/.gitignore
vendored
Normal file
1
data_migration_tools/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/tool*
|
||||
BIN
data_migration_tools/baseNode.zip
Normal file
BIN
data_migration_tools/baseNode.zip
Normal file
Binary file not shown.
8
data_migration_tools/buildTool.ps1
Normal file
8
data_migration_tools/buildTool.ps1
Normal file
@ -0,0 +1,8 @@
|
||||
# 移行ツールをビルドする
|
||||
# docker ps
|
||||
|
||||
$clientContainerName = "client_devcontainer-client-1"
|
||||
$serverContainerName = "server_devcontainer-server-1"
|
||||
|
||||
docker exec -t $clientContainerName sudo npm run build:local
|
||||
docker exec -t $serverContainerName npm run build:exe
|
||||
2
data_migration_tools/client/codegen.sh
Normal file
2
data_migration_tools/client/codegen.sh
Normal file
@ -0,0 +1,2 @@
|
||||
npx openapi-generator-cli version-manager set 7.1.0
|
||||
npx openapi-generator-cli generate -g typescript-axios -i /app/data_migration_tools/server/src/api/odms/openapi.json -o /app/data_migration_tools/client/src/api/
|
||||
7
data_migration_tools/client/openapitools.json
Normal file
7
data_migration_tools/client/openapitools.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "7.1.0"
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import TopPage from "./pages/topPage";
|
||||
import DeletePage from "./pages/deletePage";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
<Routes>
|
||||
<Route path="/" element={<div />} />
|
||||
<Route path="/" element={<TopPage />} />
|
||||
<Route path="/delete" element={<DeletePage />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
|
||||
4
data_migration_tools/client/src/api/.gitignore
vendored
Normal file
4
data_migration_tools/client/src/api/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
wwwroot/*.js
|
||||
node_modules
|
||||
typings
|
||||
dist
|
||||
1
data_migration_tools/client/src/api/.npmignore
Normal file
1
data_migration_tools/client/src/api/.npmignore
Normal file
@ -0,0 +1 @@
|
||||
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm
|
||||
@ -0,0 +1,23 @@
|
||||
# OpenAPI Generator Ignore
|
||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
||||
@ -0,0 +1,9 @@
|
||||
.gitignore
|
||||
.npmignore
|
||||
.openapi-generator-ignore
|
||||
api.ts
|
||||
base.ts
|
||||
common.ts
|
||||
configuration.ts
|
||||
git_push.sh
|
||||
index.ts
|
||||
@ -0,0 +1 @@
|
||||
7.1.0
|
||||
146
data_migration_tools/client/src/api/api.ts
Normal file
146
data_migration_tools/client/src/api/api.ts
Normal file
@ -0,0 +1,146 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* ODMSOpenAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import type { Configuration } from './configuration';
|
||||
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import globalAxios from 'axios';
|
||||
// Some imports not used depending on template conditions
|
||||
// @ts-ignore
|
||||
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
|
||||
import type { RequestArgs } from './base';
|
||||
// @ts-ignore
|
||||
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface ErrorResponse
|
||||
*/
|
||||
export interface ErrorResponse {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ErrorResponse
|
||||
*/
|
||||
'message': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ErrorResponse
|
||||
*/
|
||||
'code': string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DeleteApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const DeleteApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
* すべてのデータを削除します
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteData: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/delete`;
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const DeleteApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = DeleteApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
* すべてのデータを削除します
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteData(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteData(options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['DeleteApi.deleteData']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const DeleteApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = DeleteApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
* すべてのデータを削除します
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteData(options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteData(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* DeleteApi - object-oriented interface
|
||||
* @export
|
||||
* @class DeleteApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class DeleteApi extends BaseAPI {
|
||||
/**
|
||||
* すべてのデータを削除します
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DeleteApi
|
||||
*/
|
||||
public deleteData(options?: AxiosRequestConfig) {
|
||||
return DeleteApiFp(this.configuration).deleteData(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
86
data_migration_tools/client/src/api/base.ts
Normal file
86
data_migration_tools/client/src/api/base.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* ODMSOpenAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import type { Configuration } from './configuration';
|
||||
// Some imports not used depending on template conditions
|
||||
// @ts-ignore
|
||||
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import globalAxios from 'axios';
|
||||
|
||||
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const COLLECTION_FORMATS = {
|
||||
csv: ",",
|
||||
ssv: " ",
|
||||
tsv: "\t",
|
||||
pipes: "|",
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface RequestArgs
|
||||
*/
|
||||
export interface RequestArgs {
|
||||
url: string;
|
||||
options: AxiosRequestConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @class BaseAPI
|
||||
*/
|
||||
export class BaseAPI {
|
||||
protected configuration: Configuration | undefined;
|
||||
|
||||
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
|
||||
if (configuration) {
|
||||
this.configuration = configuration;
|
||||
this.basePath = configuration.basePath ?? basePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @class RequiredError
|
||||
* @extends {Error}
|
||||
*/
|
||||
export class RequiredError extends Error {
|
||||
constructor(public field: string, msg?: string) {
|
||||
super(msg);
|
||||
this.name = "RequiredError"
|
||||
}
|
||||
}
|
||||
|
||||
interface ServerMap {
|
||||
[key: string]: {
|
||||
url: string,
|
||||
description: string,
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const operationServerMap: ServerMap = {
|
||||
}
|
||||
150
data_migration_tools/client/src/api/common.ts
Normal file
150
data_migration_tools/client/src/api/common.ts
Normal file
@ -0,0 +1,150 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* ODMSOpenAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import type { Configuration } from "./configuration";
|
||||
import type { RequestArgs } from "./base";
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import { RequiredError } from "./base";
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const DUMMY_BASE_URL = 'https://example.com'
|
||||
|
||||
/**
|
||||
*
|
||||
* @throws {RequiredError}
|
||||
* @export
|
||||
*/
|
||||
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
|
||||
if (paramValue === null || paramValue === undefined) {
|
||||
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
|
||||
if (configuration && configuration.apiKey) {
|
||||
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
|
||||
? await configuration.apiKey(keyParamName)
|
||||
: await configuration.apiKey;
|
||||
object[keyParamName] = localVarApiKeyValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
|
||||
if (configuration && (configuration.username || configuration.password)) {
|
||||
object["auth"] = { username: configuration.username, password: configuration.password };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
|
||||
if (configuration && configuration.accessToken) {
|
||||
const accessToken = typeof configuration.accessToken === 'function'
|
||||
? await configuration.accessToken()
|
||||
: await configuration.accessToken;
|
||||
object["Authorization"] = "Bearer " + accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
|
||||
if (configuration && configuration.accessToken) {
|
||||
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
|
||||
? await configuration.accessToken(name, scopes)
|
||||
: await configuration.accessToken;
|
||||
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
|
||||
}
|
||||
}
|
||||
|
||||
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
|
||||
if (parameter == null) return;
|
||||
if (typeof parameter === "object") {
|
||||
if (Array.isArray(parameter)) {
|
||||
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
|
||||
}
|
||||
else {
|
||||
Object.keys(parameter).forEach(currentKey =>
|
||||
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (urlSearchParams.has(key)) {
|
||||
urlSearchParams.append(key, parameter);
|
||||
}
|
||||
else {
|
||||
urlSearchParams.set(key, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const setSearchParams = function (url: URL, ...objects: any[]) {
|
||||
const searchParams = new URLSearchParams(url.search);
|
||||
setFlattenedQueryParams(searchParams, objects);
|
||||
url.search = searchParams.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
|
||||
const nonString = typeof value !== 'string';
|
||||
const needsSerialization = nonString && configuration && configuration.isJsonMime
|
||||
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
|
||||
: nonString;
|
||||
return needsSerialization
|
||||
? JSON.stringify(value !== undefined ? value : {})
|
||||
: (value || "");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const toPathString = function (url: URL) {
|
||||
return url.pathname + url.search + url.hash
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
|
||||
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
|
||||
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url};
|
||||
return axios.request<T, R>(axiosRequestArgs);
|
||||
};
|
||||
}
|
||||
110
data_migration_tools/client/src/api/configuration.ts
Normal file
110
data_migration_tools/client/src/api/configuration.ts
Normal file
@ -0,0 +1,110 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* ODMSOpenAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface ConfigurationParameters {
|
||||
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||
username?: string;
|
||||
password?: string;
|
||||
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||
basePath?: string;
|
||||
serverIndex?: number;
|
||||
baseOptions?: any;
|
||||
formDataCtor?: new () => any;
|
||||
}
|
||||
|
||||
export class Configuration {
|
||||
/**
|
||||
* parameter for apiKey security
|
||||
* @param name security name
|
||||
* @memberof Configuration
|
||||
*/
|
||||
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||
/**
|
||||
* parameter for basic security
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Configuration
|
||||
*/
|
||||
username?: string;
|
||||
/**
|
||||
* parameter for basic security
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Configuration
|
||||
*/
|
||||
password?: string;
|
||||
/**
|
||||
* parameter for oauth2 security
|
||||
* @param name security name
|
||||
* @param scopes oauth2 scope
|
||||
* @memberof Configuration
|
||||
*/
|
||||
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||
/**
|
||||
* override base path
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Configuration
|
||||
*/
|
||||
basePath?: string;
|
||||
/**
|
||||
* override server index
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Configuration
|
||||
*/
|
||||
serverIndex?: number;
|
||||
/**
|
||||
* base options for axios calls
|
||||
*
|
||||
* @type {any}
|
||||
* @memberof Configuration
|
||||
*/
|
||||
baseOptions?: any;
|
||||
/**
|
||||
* The FormData constructor that will be used to create multipart form data
|
||||
* requests. You can inject this here so that execution environments that
|
||||
* do not support the FormData class can still run the generated client.
|
||||
*
|
||||
* @type {new () => FormData}
|
||||
*/
|
||||
formDataCtor?: new () => any;
|
||||
|
||||
constructor(param: ConfigurationParameters = {}) {
|
||||
this.apiKey = param.apiKey;
|
||||
this.username = param.username;
|
||||
this.password = param.password;
|
||||
this.accessToken = param.accessToken;
|
||||
this.basePath = param.basePath;
|
||||
this.serverIndex = param.serverIndex;
|
||||
this.baseOptions = param.baseOptions;
|
||||
this.formDataCtor = param.formDataCtor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given MIME is a JSON MIME.
|
||||
* JSON MIME examples:
|
||||
* application/json
|
||||
* application/json; charset=UTF8
|
||||
* APPLICATION/JSON
|
||||
* application/vnd.company+json
|
||||
* @param mime - MIME (Multipurpose Internet Mail Extensions)
|
||||
* @return True if the given MIME is JSON, false otherwise.
|
||||
*/
|
||||
public isJsonMime(mime: string): boolean {
|
||||
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
|
||||
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
|
||||
}
|
||||
}
|
||||
57
data_migration_tools/client/src/api/git_push.sh
Normal file
57
data_migration_tools/client/src/api/git_push.sh
Normal file
@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
|
||||
#
|
||||
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
|
||||
|
||||
git_user_id=$1
|
||||
git_repo_id=$2
|
||||
release_note=$3
|
||||
git_host=$4
|
||||
|
||||
if [ "$git_host" = "" ]; then
|
||||
git_host="github.com"
|
||||
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
|
||||
fi
|
||||
|
||||
if [ "$git_user_id" = "" ]; then
|
||||
git_user_id="GIT_USER_ID"
|
||||
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
|
||||
fi
|
||||
|
||||
if [ "$git_repo_id" = "" ]; then
|
||||
git_repo_id="GIT_REPO_ID"
|
||||
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
|
||||
fi
|
||||
|
||||
if [ "$release_note" = "" ]; then
|
||||
release_note="Minor update"
|
||||
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
|
||||
fi
|
||||
|
||||
# Initialize the local directory as a Git repository
|
||||
git init
|
||||
|
||||
# Adds the files in the local repository and stages them for commit.
|
||||
git add .
|
||||
|
||||
# Commits the tracked changes and prepares them to be pushed to a remote repository.
|
||||
git commit -m "$release_note"
|
||||
|
||||
# Sets the new remote
|
||||
git_remote=$(git remote)
|
||||
if [ "$git_remote" = "" ]; then # git remote not defined
|
||||
|
||||
if [ "$GIT_TOKEN" = "" ]; then
|
||||
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
|
||||
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
|
||||
else
|
||||
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
git pull origin master
|
||||
|
||||
# Pushes (Forces) the changes in the local repository up to the remote repository
|
||||
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
|
||||
git push origin master 2>&1 | grep -v 'To https'
|
||||
18
data_migration_tools/client/src/api/index.ts
Normal file
18
data_migration_tools/client/src/api/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* ODMSOpenAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export * from "./api";
|
||||
export * from "./configuration";
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
|
||||
import auth from "features/auth/authSlice";
|
||||
import ui from "features/ui/uiSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
auth,
|
||||
ui,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
17
data_migration_tools/client/src/common/errors/code.ts
Normal file
17
data_migration_tools/client/src/common/errors/code.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
*/
|
||||
export const errorCodes = [
|
||||
"E009999", // 汎用エラー
|
||||
] as const;
|
||||
3
data_migration_tools/client/src/common/errors/index.ts
Normal file
3
data_migration_tools/client/src/common/errors/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./code";
|
||||
export * from "./types";
|
||||
export * from "./utils";
|
||||
9
data_migration_tools/client/src/common/errors/types.ts
Normal file
9
data_migration_tools/client/src/common/errors/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { errorCodes } from "./code";
|
||||
|
||||
export type ErrorObject = {
|
||||
message: string;
|
||||
code: ErrorCodeType;
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
export type ErrorCodeType = (typeof errorCodes)[number];
|
||||
101
data_migration_tools/client/src/common/errors/utils.ts
Normal file
101
data_migration_tools/client/src/common/errors/utils.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { isError } from "lodash";
|
||||
import { ErrorResponse } from "../../api";
|
||||
import { errorCodes } from "./code";
|
||||
import { ErrorCodeType, ErrorObject } from "./types";
|
||||
|
||||
export const createErrorObject = (error: unknown): ErrorObject => {
|
||||
// 最低限通常のエラーかを判定
|
||||
// Error以外のものがthrowされた場合
|
||||
// 基本的にないはずだがプログラム上あるので拾う
|
||||
if (!isError(error)) {
|
||||
return {
|
||||
message: "not error type.",
|
||||
code: "E009999",
|
||||
};
|
||||
}
|
||||
|
||||
// Axiosエラー 通信してのエラーであるかを判定
|
||||
if (!isAxiosError(error)) {
|
||||
return {
|
||||
message: "not axios error.",
|
||||
code: "E009999",
|
||||
};
|
||||
}
|
||||
|
||||
const errorResponse = error.response;
|
||||
if (!errorResponse) {
|
||||
return {
|
||||
message: error.message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse,
|
||||
};
|
||||
}
|
||||
|
||||
const { data } = errorResponse;
|
||||
|
||||
// 想定しているエラーレスポンスの型か判定
|
||||
if (!isErrorResponse(data)) {
|
||||
return {
|
||||
message: error.message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
}
|
||||
|
||||
const { message, code } = data;
|
||||
|
||||
// 想定しているエラーコードかを判定
|
||||
if (!isErrorCode(code)) {
|
||||
return {
|
||||
message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
code,
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
};
|
||||
|
||||
const isAxiosError = (e: unknown): e is AxiosError => {
|
||||
const error = e as AxiosError;
|
||||
return error?.isAxiosError ?? false;
|
||||
};
|
||||
|
||||
const isErrorResponse = (error: unknown): error is ErrorResponse => {
|
||||
const errorResponse = error as ErrorResponse;
|
||||
if (
|
||||
errorResponse === undefined ||
|
||||
errorResponse.message === undefined ||
|
||||
errorResponse.code === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const isErrorCode = (errorCode: string): errorCode is ErrorCodeType =>
|
||||
errorCodes.includes(errorCode as ErrorCodeType);
|
||||
|
||||
export const isErrorObject = (
|
||||
data: unknown
|
||||
): data is { error: ErrorObject } => {
|
||||
if (
|
||||
data &&
|
||||
typeof data === "object" &&
|
||||
"error" in data &&
|
||||
typeof (data as { error: ErrorObject }).error === "object" &&
|
||||
typeof (data as { error: ErrorObject }).error.message === "string" &&
|
||||
typeof (data as { error: ErrorObject }).error.code === "string" &&
|
||||
(typeof (data as { error: ErrorObject }).error.statusCode === "number" ||
|
||||
(data as { error: ErrorObject }).error.statusCode === undefined)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
export const getBasePath = () => {
|
||||
if (import.meta.env.VITE_STAGE === "local") {
|
||||
return "http://localhost:8180";
|
||||
return "http://localhost:8280";
|
||||
}
|
||||
return window.location.origin;
|
||||
};
|
||||
|
||||
15
data_migration_tools/client/src/features/auth/authSlice.ts
Normal file
15
data_migration_tools/client/src/features/auth/authSlice.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import type { AuthState } from "./state";
|
||||
import { initialConfig } from "./utils";
|
||||
|
||||
const initialState: AuthState = {
|
||||
configuration: initialConfig(),
|
||||
};
|
||||
|
||||
export const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState,
|
||||
reducers: {},
|
||||
});
|
||||
|
||||
export default authSlice.reducer;
|
||||
3
data_migration_tools/client/src/features/auth/index.ts
Normal file
3
data_migration_tools/client/src/features/auth/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./authSlice";
|
||||
export * from "./state";
|
||||
export * from "./utils";
|
||||
5
data_migration_tools/client/src/features/auth/state.ts
Normal file
5
data_migration_tools/client/src/features/auth/state.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { ConfigurationParameters } from "../../api";
|
||||
|
||||
export interface AuthState {
|
||||
configuration: ConfigurationParameters;
|
||||
}
|
||||
13
data_migration_tools/client/src/features/auth/utils.ts
Normal file
13
data_migration_tools/client/src/features/auth/utils.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ConfigurationParameters } from "../../api";
|
||||
|
||||
// 初期状態のAPI Config
|
||||
export const initialConfig = (): ConfigurationParameters => {
|
||||
const config: ConfigurationParameters = {};
|
||||
if (import.meta.env.VITE_STAGE === "local") {
|
||||
config.basePath = "http://localhost:8280";
|
||||
} else {
|
||||
config.basePath = `${window.location.origin}/dictation/api`;
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { DeleteState } from "./state";
|
||||
import { deleteDataAsync } from "./operations";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
|
||||
const initialState: DeleteState = {};
|
||||
|
||||
export const deleteSlice = createSlice({
|
||||
name: "detete",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(deleteDataAsync.pending, () => {
|
||||
/* Empty Object */
|
||||
});
|
||||
builder.addCase(deleteDataAsync.fulfilled, () => {
|
||||
/* Empty Object */
|
||||
});
|
||||
builder.addCase(deleteDataAsync.rejected, () => {
|
||||
/* Empty Object */
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default deleteSlice.reducer;
|
||||
3
data_migration_tools/client/src/features/delete/index.ts
Normal file
3
data_migration_tools/client/src/features/delete/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./state";
|
||||
export * from "./deleteSlice";
|
||||
export * from "./operations";
|
||||
@ -0,0 +1,47 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { openSnackbar } from "../ui/uiSlice";
|
||||
import type { RootState } from "../../app/store";
|
||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||
import { DeleteApi } from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
export const deleteDataAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("delete/deleteDataAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const deleteApi = new DeleteApi(config);
|
||||
|
||||
try {
|
||||
await deleteApi.deleteData();
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: "削除しました。",
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: "削除に失敗しました。",
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
2
data_migration_tools/client/src/features/delete/state.ts
Normal file
2
data_migration_tools/client/src/features/delete/state.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface DeleteState {}
|
||||
2
data_migration_tools/client/src/features/ui/constants.ts
Normal file
2
data_migration_tools/client/src/features/ui/constants.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// 標準のスナックバー表示時間(ミリ秒)
|
||||
export const DEFAULT_SNACKBAR_DURATION = 3000;
|
||||
5
data_migration_tools/client/src/features/ui/index.ts
Normal file
5
data_migration_tools/client/src/features/ui/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./constants";
|
||||
export * from "./selectors";
|
||||
export * from "./state";
|
||||
export * from "./uiSlice";
|
||||
export * from "./types";
|
||||
15
data_migration_tools/client/src/features/ui/selectors.ts
Normal file
15
data_migration_tools/client/src/features/ui/selectors.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { RootState } from "app/store";
|
||||
import { SnackbarLevel } from "./types";
|
||||
|
||||
export const selectSnackber = (
|
||||
state: RootState
|
||||
): {
|
||||
isOpen: boolean;
|
||||
level: SnackbarLevel;
|
||||
message: string;
|
||||
duration?: number;
|
||||
} => {
|
||||
const { isOpen, level, message, duration } = state.ui;
|
||||
|
||||
return { isOpen, level, message, duration };
|
||||
};
|
||||
8
data_migration_tools/client/src/features/ui/state.ts
Normal file
8
data_migration_tools/client/src/features/ui/state.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { SnackbarLevel } from "./types";
|
||||
|
||||
export interface UIState {
|
||||
isOpen: boolean;
|
||||
level: SnackbarLevel;
|
||||
message: string;
|
||||
duration?: number;
|
||||
}
|
||||
1
data_migration_tools/client/src/features/ui/types.ts
Normal file
1
data_migration_tools/client/src/features/ui/types.ts
Normal file
@ -0,0 +1 @@
|
||||
export type SnackbarLevel = "info" | "error";
|
||||
38
data_migration_tools/client/src/features/ui/uiSlice.ts
Normal file
38
data_migration_tools/client/src/features/ui/uiSlice.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { SnackbarLevel } from "./types";
|
||||
import { UIState } from "./state";
|
||||
import { DEFAULT_SNACKBAR_DURATION } from "./constants";
|
||||
|
||||
const initialState: UIState = {
|
||||
isOpen: false,
|
||||
level: "error",
|
||||
message: "",
|
||||
};
|
||||
|
||||
export const uiSlice = createSlice({
|
||||
name: "ui",
|
||||
initialState,
|
||||
reducers: {
|
||||
openSnackbar: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
level: SnackbarLevel;
|
||||
message: string;
|
||||
duration?: number;
|
||||
}>
|
||||
) => {
|
||||
const { level, message, duration } = action.payload;
|
||||
state.isOpen = true;
|
||||
state.level = level;
|
||||
state.message = message;
|
||||
state.duration =
|
||||
level === "error" ? undefined : duration ?? DEFAULT_SNACKBAR_DURATION;
|
||||
},
|
||||
closeSnackbar: (state) => {
|
||||
state.isOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
export const { openSnackbar, closeSnackbar } = uiSlice.actions;
|
||||
|
||||
export default uiSlice.reducer;
|
||||
32
data_migration_tools/client/src/pages/deletePage/index.tsx
Normal file
32
data_migration_tools/client/src/pages/deletePage/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { AppDispatch } from "app/store";
|
||||
import { deleteDataAsync } from "features/delete";
|
||||
import React, { useCallback } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const DeletePage = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm("本当に削除しますか?")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
dispatch(deleteDataAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>データ削除ツール</p>
|
||||
<button type="button" onClick={onDelete}>
|
||||
削除
|
||||
</button>
|
||||
<br />
|
||||
<Link to="/">return to TopPage</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeletePage;
|
||||
15
data_migration_tools/client/src/pages/topPage/index.tsx
Normal file
15
data_migration_tools/client/src/pages/topPage/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const TopPage = (): JSX.Element => {
|
||||
console.log("DeletePage");
|
||||
return (
|
||||
<div>
|
||||
<Link to="/delete">データ削除ツール</Link>
|
||||
<br />
|
||||
<Link to="/">return to TopPage</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopPage;
|
||||
63
data_migration_tools/package-lock.json
generated
Normal file
63
data_migration_tools/package-lock.json
generated
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "data_migration_tools",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"author": "",
|
||||
"description": "",
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"packages": {},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^20.2.3",
|
||||
"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",
|
||||
"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.3.5"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nest build && cp -r build dist",
|
||||
"build:exe": "nest build && cp -r build dist && sh ./buildTool.sh",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"prebuild": "rimraf dist",
|
||||
"start": "nest start",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"test": "jest",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:watch": "jest --watch"
|
||||
}
|
||||
}
|
||||
@ -14,5 +14,11 @@ services:
|
||||
- "8280"
|
||||
environment:
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
networks:
|
||||
- external
|
||||
networks:
|
||||
external:
|
||||
name: omds_network
|
||||
external: true
|
||||
volumes:
|
||||
data_migration_tools_server_node_modules:
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
DB_HOST=omds-mysql
|
||||
DB_PORT=3306
|
||||
DB_NAME=omds
|
||||
DB_USERNAME=omdsdbuser
|
||||
DB_PASSWORD=omdsdbpass
|
||||
@ -0,0 +1,36 @@
|
||||
STAGE=local
|
||||
NO_COLOR=TRUE
|
||||
CORS=TRUE
|
||||
PORT=8280
|
||||
# 開発環境ではADB2Cが別テナントになる都合上、環境変数を分けている
|
||||
TENANT_NAME=adb2codmsdev
|
||||
SIGNIN_FLOW_NAME=b2c_1_signin_dev
|
||||
ADB2C_TENANT_ID=xxxxxxxx
|
||||
ADB2C_CLIENT_ID=xxxxxxxx
|
||||
ADB2C_CLIENT_SECRET=xxxxxxxx
|
||||
ADB2C_ORIGIN=https://zzzzzzzzzz
|
||||
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"
|
||||
SENDGRID_API_KEY=xxxxxxxxxxxxxxxx
|
||||
MAIL_FROM=xxxxx@xxxxx.xxxx
|
||||
NOTIFICATION_HUB_NAME=ntf-odms-dev
|
||||
NOTIFICATION_HUB_CONNECT_STRING=XXXXXXXXXXXXXXXXXX
|
||||
APP_DOMAIN=http://localhost:8081/
|
||||
STORAGE_TOKEN_EXPIRE_TIME=2
|
||||
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
|
||||
ACCESS_TOKEN_LIFETIME_WEB=7200
|
||||
REFRESH_TOKEN_LIFETIME_WEB=86400
|
||||
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000
|
||||
EMAIL_CONFIRM_LIFETIME=86400
|
||||
REDIS_HOST=redis-cache
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=omdsredispass
|
||||
ADB2C_CACHE_TTL=86400
|
||||
12
data_migration_tools/server/buildTool.sh
Normal file
12
data_migration_tools/server/buildTool.sh
Normal file
@ -0,0 +1,12 @@
|
||||
if [ ! -d "../tool" ]; then
|
||||
mkdir "../tool"
|
||||
echo "フォルダが作成されました。"
|
||||
else
|
||||
rm -f ../tool/ODMS_DataTool.exe
|
||||
fi
|
||||
unzip ../baseNode.zip -d ../tool
|
||||
ncc build dist/main.js -o dist_single -a
|
||||
npx -y postject ../tool/ODMS_DataTool.exe NODE_JS_CODE dist_single/index.js --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite
|
||||
cp -r build ../tool
|
||||
cp .env ../tool
|
||||
cp .env.local.example ../tool
|
||||
2057
data_migration_tools/server/package-lock.json
generated
2057
data_migration_tools/server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,10 +19,13 @@
|
||||
"test": "jest",
|
||||
"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:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"apigen": "ts-node src/api/generate.ts && prettier --write \"src/api/odms/*.json\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/identity": "^4.0.1",
|
||||
"@azure/storage-blob": "^12.14.0",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
"@nestjs/common": "^9.3.9",
|
||||
"@nestjs/config": "^2.3.1",
|
||||
"@nestjs/core": "^9.3.9",
|
||||
@ -30,6 +33,7 @@
|
||||
"@nestjs/serve-static": "^3.0.1",
|
||||
"@nestjs/swagger": "^6.2.1",
|
||||
"@nestjs/testing": "^9.3.9",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"@openapitools/openapi-generator-cli": "^2.5.2",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"axios": "^1.3.4",
|
||||
@ -37,9 +41,12 @@
|
||||
"class-validator": "^0.14.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.0",
|
||||
"swagger-cli": "^4.0.4"
|
||||
"swagger-cli": "^4.0.4",
|
||||
"typeorm": "^0.3.20",
|
||||
"csv": "^6.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
|
||||
25
data_migration_tools/server/src/api/generate.ts
Normal file
25
data_migration_tools/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("ODMSOpenAPI")
|
||||
.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();
|
||||
56
data_migration_tools/server/src/api/odms/openapi.json
Normal file
56
data_migration_tools/server/src/api/odms/openapi.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/delete": {
|
||||
"post": {
|
||||
"operationId": "deleteData",
|
||||
"summary": "",
|
||||
"description": "すべてのデータを削除します",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/DeleteResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["delete"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "ODMSOpenAPI",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
"servers": [],
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearer": { "scheme": "bearer", "bearerFormat": "JWT", "type": "http" }
|
||||
},
|
||||
"schemas": {
|
||||
"DeleteResponse": { "type": "object", "properties": {} },
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": { "type": "string" },
|
||||
"code": { "type": "string" }
|
||||
},
|
||||
"required": ["message", "code"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,35 @@
|
||||
import { MiddlewareConsumer, Module } from "@nestjs/common";
|
||||
import { ServeStaticModule } from "@nestjs/serve-static";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { join } from "path";
|
||||
import { LoggerMiddleware } from "./common/loggerMiddleware";
|
||||
import { AdB2cModule } from "./gateways/adb2c/adb2c.module";
|
||||
import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module";
|
||||
import { RegisterController } from "./features/register/register.controller";
|
||||
import { RegisterService } from "./features/register/register.service";
|
||||
import { RegisterModule } from "./features/register/register.module";
|
||||
import { AccountsRepositoryModule } from "./repositories/accounts/accounts.repository.module";
|
||||
import { UsersRepositoryModule } from "./repositories/users/users.repository.module";
|
||||
import { SortCriteriaRepositoryModule } from "./repositories/sort_criteria/sort_criteria.repository.module";
|
||||
import { LicensesRepositoryModule } from "./repositories/licenses/licenses.repository.module";
|
||||
import { WorktypesRepositoryModule } from "./repositories/worktypes/worktypes.repository.module";
|
||||
import { AccountsController } from "./features/accounts/accounts.controller";
|
||||
import { AccountsService } from "./features/accounts/accounts.service";
|
||||
import { AccountsModule } from "./features/accounts/accounts.module";
|
||||
import { UsersController } from "./features/users/users.controller";
|
||||
import { UsersService } from "./features/users/users.service";
|
||||
import { UsersModule } from "./features/users/users.module";
|
||||
import { DeleteModule } from "./features/delete/delete.module";
|
||||
import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module";
|
||||
import { DeleteController } from "./features/delete/delete.controller";
|
||||
import { DeleteService } from "./features/delete/delete.service";
|
||||
import { TransferModule } from "./features/transfer/transfer.module";
|
||||
import { TransferController } from "./features/transfer/transfer.controller";
|
||||
import { TransferService } from "./features/transfer/transfer.service";
|
||||
import { VerificationController } from "./features/verification/verification.controller";
|
||||
import { VerificationService } from "./features/verification/verification.service";
|
||||
import { VerificationModule } from "./features/verification/verification.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -13,9 +40,51 @@ import { LoggerMiddleware } from "./common/loggerMiddleware";
|
||||
envFilePath: [".env.local", ".env"],
|
||||
isGlobal: true,
|
||||
}),
|
||||
AdB2cModule,
|
||||
AccountsModule,
|
||||
UsersModule,
|
||||
TransferModule,
|
||||
RegisterModule,
|
||||
VerificationModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
LicensesRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
BlobstorageModule,
|
||||
DeleteModule,
|
||||
DeleteRepositoryModule,
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
type: "mysql",
|
||||
host: configService.get("DB_HOST"),
|
||||
port: configService.get("DB_PORT"),
|
||||
username: configService.get("DB_USERNAME"),
|
||||
password: configService.get("DB_PASSWORD"),
|
||||
database: configService.get("DB_NAME"),
|
||||
autoLoadEntities: true, // forFeature()で登録されたEntityを自動的にロード
|
||||
synchronize: false, // trueにすると自動的にmigrationが行われるため注意
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
controllers: [
|
||||
RegisterController,
|
||||
AccountsController,
|
||||
UsersController,
|
||||
DeleteController,
|
||||
TransferController,
|
||||
VerificationController,
|
||||
],
|
||||
providers: [
|
||||
RegisterService,
|
||||
AccountsService,
|
||||
UsersService,
|
||||
DeleteService,
|
||||
TransferService,
|
||||
VerificationService,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { bigintTransformer } from '.';
|
||||
|
||||
describe('bigintTransformer', () => {
|
||||
describe('to', () => {
|
||||
it('number型を整数を表す文字列に変換できる', () => {
|
||||
expect(bigintTransformer.to(0)).toBe('0');
|
||||
expect(bigintTransformer.to(1)).toBe('1');
|
||||
expect(bigintTransformer.to(1234567890)).toBe('1234567890');
|
||||
expect(bigintTransformer.to(9007199254740991)).toBe('9007199254740991');
|
||||
expect(bigintTransformer.to(-1)).toBe('-1');
|
||||
});
|
||||
it('少数点以下がある場合はエラーとなる', () => {
|
||||
expect(() => bigintTransformer.to(1.1)).toThrowError(
|
||||
'1.1 is not integer.',
|
||||
);
|
||||
});
|
||||
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
|
||||
expect(() => bigintTransformer.to(9007199254740992)).toThrowError(
|
||||
'value is greater than 9007199254740991.',
|
||||
);
|
||||
expect(() => bigintTransformer.to(9223372036854775807)).toThrowError(
|
||||
'value is greater than 9007199254740991.',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('from', () => {
|
||||
it('bigint型の文字列をnumber型に変換できる', () => {
|
||||
expect(bigintTransformer.from('0')).toBe(0);
|
||||
expect(bigintTransformer.from('1')).toBe(1);
|
||||
expect(bigintTransformer.from('1234567890')).toBe(1234567890);
|
||||
expect(bigintTransformer.from('-1')).toBe(-1);
|
||||
});
|
||||
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
|
||||
expect(() => bigintTransformer.from('9007199254740992')).toThrowError(
|
||||
'9007199254740992 is greater than 9007199254740991.',
|
||||
);
|
||||
expect(() => bigintTransformer.from('9223372036854775807')).toThrowError(
|
||||
'9223372036854775807 is greater than 9007199254740991.',
|
||||
);
|
||||
});
|
||||
it('number型の場合はそのまま返す', () => {
|
||||
expect(bigintTransformer.from(0)).toBe(0);
|
||||
expect(bigintTransformer.from(1)).toBe(1);
|
||||
expect(bigintTransformer.from(1234567890)).toBe(1234567890);
|
||||
expect(bigintTransformer.from(-1)).toBe(-1);
|
||||
});
|
||||
it('nullの場合はそのまま返す', () => {
|
||||
expect(bigintTransformer.from(null)).toBe(null);
|
||||
});
|
||||
it('number型に変換できない場合はエラーとなる', () => {
|
||||
expect(() => bigintTransformer.from('a')).toThrowError('a is not int.');
|
||||
expect(() => bigintTransformer.from('')).toThrowError(' is not int.');
|
||||
expect(() => bigintTransformer.from(undefined)).toThrowError(
|
||||
'undefined is not string.',
|
||||
);
|
||||
expect(() => bigintTransformer.from({})).toThrowError(
|
||||
'[object Object] is not string.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
57
data_migration_tools/server/src/common/entity/index.ts
Normal file
57
data_migration_tools/server/src/common/entity/index.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
|
||||
// DBのbigint型をnumber型に変換するためのtransformer
|
||||
// DBのBigInt型をそのまま扱うと、JSのNumber型の最大値を超えると誤差が発生するため、本来はNumber型に変換すべきではないが、
|
||||
// 影響範囲を最小限に抑えるため、Number型に変換する。使用するのはAutoIncrementされるIDのみの想定のため、
|
||||
// Number.MAX_SAFE_INTEGERより大きい値は現実的には発生しない想定で変換する。
|
||||
export const bigintTransformer: ValueTransformer = {
|
||||
from: (value: any): number | null => {
|
||||
// valueがnullであればそのまま返す
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
// valueがnumber型かどうかを判定
|
||||
// 利用DBによってはbigint型であってもnumber型で返ってくる場合があるため、number型の場合はそのまま返す(sqliteの場合)
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
// valueが文字列かどうかを判定
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(`${value} is not string.`);
|
||||
}
|
||||
// 数値に変換可能な文字列かどうかを判定
|
||||
if (Number.isNaN(parseInt(value))) {
|
||||
throw new Error(`${value} is not int.`);
|
||||
}
|
||||
|
||||
// 文字列ならbigintに変換
|
||||
// valueが整数でない場合は値が丸められてしまうが、TypeORMのEntityの定義上、整数を表す文字列以外はありえないため、少数点は考慮しない
|
||||
const bigIntValue = BigInt(value);
|
||||
// bigIntValueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
|
||||
if (bigIntValue > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error(`${value} is greater than ${Number.MAX_SAFE_INTEGER}.`);
|
||||
}
|
||||
// number型で表現できる整数であればnumber型に変換して返す
|
||||
return Number(bigIntValue);
|
||||
},
|
||||
to: (value: any): string | null | undefined => {
|
||||
// valueがnullまたはundefinedであればそのまま返す
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
// valueがnumber型かどうかを判定
|
||||
if (typeof value !== 'number') {
|
||||
throw new Error(`${value} is not number.`);
|
||||
}
|
||||
|
||||
// valueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
|
||||
if (value > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error(`value is greater than ${Number.MAX_SAFE_INTEGER}.`);
|
||||
}
|
||||
// valueが整数かどうかを判定
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error(`${value} is not integer.`);
|
||||
}
|
||||
return value.toString();
|
||||
},
|
||||
};
|
||||
70
data_migration_tools/server/src/common/error/code.ts
Normal file
70
data_migration_tools/server/src/common/error/code.ts
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
*/
|
||||
export const ErrorCodes = [
|
||||
'E009999', // 汎用エラー
|
||||
'E000101', // トークン形式不正エラー
|
||||
'E000102', // トークン有効期限切れエラー
|
||||
'E000103', // トークン非アクティブエラー
|
||||
'E000104', // トークン署名エラー
|
||||
'E000105', // トークン発行元エラー
|
||||
'E000106', // トークンアルゴリズムエラー
|
||||
'E000107', // トークン不足エラー
|
||||
'E000108', // トークン権限エラー
|
||||
'E000301', // ADB2Cへのリクエスト上限超過エラー
|
||||
'E000401', // IPアドレス未設定エラー
|
||||
'E000501', // リクエストID未設定エラー
|
||||
'E010001', // パラメータ形式不正エラー
|
||||
'E010201', // 未認証ユーザエラー
|
||||
'E010202', // 認証済ユーザエラー
|
||||
'E010203', // 管理ユーザ権限エラー
|
||||
'E010204', // ユーザ不在エラー
|
||||
'E010205', // DBのRoleが想定外の値エラー
|
||||
'E010206', // DBのTierが想定外の値エラー
|
||||
'E010207', // ユーザーのRole変更不可エラー
|
||||
'E010208', // ユーザーの暗号化パスワード不足エラー
|
||||
'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー
|
||||
'E010301', // メールアドレス登録済みエラー
|
||||
'E010302', // authorId重複エラー
|
||||
'E010401', // PONumber重複エラー
|
||||
'E010501', // アカウント不在エラー
|
||||
'E010502', // アカウント情報変更不可エラー
|
||||
'E010503', // 代行操作不許可エラー
|
||||
'E010504', // アカウントロックエラー
|
||||
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||
'E010602', // タスク変更権限不足エラー
|
||||
'E010603', // タスク不在エラー
|
||||
'E010701', // Blobファイル不在エラー
|
||||
'E010801', // ライセンス不在エラー
|
||||
'E010802', // ライセンス取り込み済みエラー
|
||||
'E010803', // ライセンス発行済みエラー
|
||||
'E010804', // ライセンス不足エラー
|
||||
'E010805', // ライセンス有効期限切れエラー
|
||||
'E010806', // ライセンス割り当て不可エラー
|
||||
'E010807', // ライセンス割り当て解除済みエラー
|
||||
'E010808', // ライセンス注文キャンセル不可エラー
|
||||
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||
'E010812', // ライセンス未割当エラー
|
||||
'E010908', // タイピストグループ不在エラー
|
||||
'E010909', // タイピストグループ名重複エラー
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
'E011004', // ワークタイプ使用中エラー
|
||||
'E012001', // テンプレートファイル不在エラー
|
||||
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
'E013002', // ワークフロー不在エラー
|
||||
] 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,
|
||||
};
|
||||
};
|
||||
59
data_migration_tools/server/src/common/error/message.ts
Normal file
59
data_migration_tools/server/src/common/error/message.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Errors } from './types/types';
|
||||
|
||||
// エラーコードとメッセージ対応表
|
||||
export const errors: Errors = {
|
||||
E009999: 'Internal Server Error.',
|
||||
E000101: 'Token invalid format Error.',
|
||||
E000102: 'Token expired Error.',
|
||||
E000103: 'Token not before Error',
|
||||
E000104: 'Token invalid signature Error.',
|
||||
E000105: 'Token invalid issuer Error.',
|
||||
E000106: 'Token invalid algorithm Error.',
|
||||
E000107: 'Token is not exist Error.',
|
||||
E000108: 'Token authority failed Error.',
|
||||
E000301: 'ADB2C request limit exceeded Error',
|
||||
E000401: 'IP address not found Error.',
|
||||
E000501: 'Request ID not found Error.',
|
||||
E010001: 'Param invalid format Error.',
|
||||
E010201: 'Email not verified user Error.',
|
||||
E010202: 'Email already verified user Error.',
|
||||
E010203: 'Administrator Permissions Error.',
|
||||
E010204: 'User not Found Error.',
|
||||
E010205: 'Role from DB is unexpected value Error.',
|
||||
E010206: 'Tier from DB is unexpected value Error.',
|
||||
E010207: 'User role change not allowed Error.',
|
||||
E010208: 'User encryption password not found Error.',
|
||||
E010209: 'Accepted term not latest Error.',
|
||||
E010301: 'This email user already created Error',
|
||||
E010302: 'This AuthorId already used Error',
|
||||
E010401: 'This PoNumber already used Error',
|
||||
E010501: 'Account not Found Error.',
|
||||
E010502: 'Account information cannot be changed Error.',
|
||||
E010503: 'Delegation not allowed Error.',
|
||||
E010504: 'Account is locked Error.',
|
||||
E010601: 'Task is not Editable Error',
|
||||
E010602: 'No task edit permissions Error',
|
||||
E010603: 'Task not found Error.',
|
||||
E010701: 'File not found in Blob Storage Error.',
|
||||
E010801: 'License not exist Error',
|
||||
E010802: 'License already activated Error',
|
||||
E010803: 'License already issued Error',
|
||||
E010804: 'License shortage Error',
|
||||
E010805: 'License is expired Error',
|
||||
E010806: 'License is unavailable Error',
|
||||
E010807: 'License is already deallocated Error',
|
||||
E010808: 'Order cancel failed Error',
|
||||
E010809: 'Already license order status changed Error',
|
||||
E010810: 'Cancellation period expired error',
|
||||
E010811: 'Already license allocated Error',
|
||||
E010812: 'License not allocated Error',
|
||||
E010908: 'Typist Group not exist Error',
|
||||
E010909: 'Typist Group name already exist Error',
|
||||
E011001: 'This WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
E011004: 'WorkTypeID is in use Error',
|
||||
E012001: 'Template file not found Error',
|
||||
E013001: 'AuthorId and WorktypeId pair already exists Error',
|
||||
E013002: 'Workflow not found Error',
|
||||
};
|
||||
15
data_migration_tools/server/src/common/error/types/types.ts
Normal file
15
data_migration_tools/server/src/common/error/types/types.ts
Normal file
@ -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;
|
||||
};
|
||||
17
data_migration_tools/server/src/common/errors/code.ts
Normal file
17
data_migration_tools/server/src/common/errors/code.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
*/
|
||||
export const ErrorCodes = [
|
||||
"E009999", // 汎用エラー
|
||||
] 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,
|
||||
};
|
||||
};
|
||||
6
data_migration_tools/server/src/common/errors/message.ts
Normal file
6
data_migration_tools/server/src/common/errors/message.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Errors } from "./types/types";
|
||||
|
||||
// エラーコードとメッセージ対応表
|
||||
export const errors: Errors = {
|
||||
E009999: "Internal Server Error.",
|
||||
};
|
||||
15
data_migration_tools/server/src/common/errors/types/types.ts
Normal file
15
data_migration_tools/server/src/common/errors/types/types.ts
Normal file
@ -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;
|
||||
};
|
||||
32
data_migration_tools/server/src/common/log/context.ts
Normal file
32
data_migration_tools/server/src/common/log/context.ts
Normal file
@ -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}
|
||||
* @return {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;
|
||||
};
|
||||
4
data_migration_tools/server/src/common/log/index.ts
Normal file
4
data_migration_tools/server/src/common/log/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Context } from "./types";
|
||||
import { makeContext, retrieveRequestId, retrieveIp } from "./context";
|
||||
|
||||
export { Context, makeContext, retrieveRequestId, retrieveIp };
|
||||
34
data_migration_tools/server/src/common/log/types.ts
Normal file
34
data_migration_tools/server/src/common/log/types.ts
Normal file
@ -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}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
data_migration_tools/server/src/common/password/index.ts
Normal file
3
data_migration_tools/server/src/common/password/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { makePassword } from "./password";
|
||||
|
||||
export { makePassword };
|
||||
41
data_migration_tools/server/src/common/password/password.ts
Normal file
41
data_migration_tools/server/src/common/password/password.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export const makePassword = (): string => {
|
||||
// パスワードの文字数を決定
|
||||
const passLength = 8;
|
||||
|
||||
// パスワードに使用可能な文字を決定(今回はアルファベットの大文字と小文字 + 数字 + symbolsの記号)
|
||||
const lowerCase = "abcdefghijklmnopqrstuvwxyz";
|
||||
const upperCase = lowerCase.toLocaleUpperCase();
|
||||
const numbers = "0123456789";
|
||||
const symbols = "@#$%^&*\\-_+=[]{}|:',.?/`~\"();!";
|
||||
const chars = lowerCase + upperCase + numbers + symbols;
|
||||
|
||||
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ
|
||||
const charaTypePattern =
|
||||
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
|
||||
|
||||
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
|
||||
let valid = false;
|
||||
let autoGeneratedPassword: string = "";
|
||||
|
||||
while (!valid) {
|
||||
autoGeneratedPassword = "";
|
||||
// パスワードをランダムに決定+
|
||||
while (autoGeneratedPassword.length < passLength) {
|
||||
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
||||
const index = Math.floor(Math.random() * chars.length);
|
||||
autoGeneratedPassword += chars[index];
|
||||
}
|
||||
|
||||
// パスワードが上で決定した条件をすべて満たしているかチェック
|
||||
// 条件を満たすまでループ
|
||||
valid =
|
||||
autoGeneratedPassword.length == passLength &&
|
||||
charaTypePattern.test(autoGeneratedPassword);
|
||||
if (!valid) {
|
||||
// autoGeneratedPasswordをログに出す
|
||||
console.log("Password is not valid");
|
||||
console.log(autoGeneratedPassword);
|
||||
}
|
||||
}
|
||||
return autoGeneratedPassword;
|
||||
};
|
||||
143
data_migration_tools/server/src/common/repository/index.ts
Normal file
143
data_migration_tools/server/src/common/repository/index.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import {
|
||||
ObjectLiteral,
|
||||
Repository,
|
||||
EntityTarget,
|
||||
UpdateResult,
|
||||
DeleteResult,
|
||||
UpdateQueryBuilder,
|
||||
Brackets,
|
||||
FindOptionsWhere,
|
||||
} from 'typeorm';
|
||||
import { Context } from '../log';
|
||||
|
||||
/**
|
||||
* VS Code上で型解析エラーが発生するため、typeorm内の型定義と同一の型定義をここに記述する
|
||||
*/
|
||||
type QueryDeepPartialEntity<T> = _QueryDeepPartialEntity<
|
||||
ObjectLiteral extends T ? unknown : T
|
||||
>;
|
||||
type _QueryDeepPartialEntity<T> = {
|
||||
[P in keyof T]?:
|
||||
| (T[P] extends Array<infer U>
|
||||
? Array<_QueryDeepPartialEntity<U>>
|
||||
: T[P] extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<_QueryDeepPartialEntity<U>>
|
||||
: _QueryDeepPartialEntity<T[P]>)
|
||||
| (() => string);
|
||||
};
|
||||
|
||||
interface InsertEntityOptions {
|
||||
id: number;
|
||||
}
|
||||
|
||||
const insertEntity = async <T extends InsertEntityOptions & ObjectLiteral>(
|
||||
entity: EntityTarget<T>,
|
||||
repository: Repository<T>,
|
||||
value: QueryDeepPartialEntity<T>,
|
||||
isCommentOut: boolean,
|
||||
context: Context,
|
||||
): Promise<T> => {
|
||||
let query = repository.createQueryBuilder().insert().into(entity);
|
||||
if (isCommentOut) {
|
||||
query = query.comment(
|
||||
`${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
);
|
||||
}
|
||||
const result = await query.values(value).execute();
|
||||
// result.identifiers[0].idがnumber型でない場合はエラー
|
||||
if (typeof result.identifiers[0].id !== 'number') {
|
||||
throw new Error('Failed to insert entity');
|
||||
}
|
||||
const where: FindOptionsWhere<T> = { id: result.identifiers[0].id } as T;
|
||||
|
||||
// 結果をもとにセレクトする
|
||||
const inserted = await repository.findOne({
|
||||
where,
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (!inserted) {
|
||||
throw new Error('Failed to insert entity');
|
||||
}
|
||||
return inserted;
|
||||
};
|
||||
|
||||
const insertEntities = async <T extends InsertEntityOptions & ObjectLiteral>(
|
||||
entity: EntityTarget<T>,
|
||||
repository: Repository<T>,
|
||||
values: QueryDeepPartialEntity<T>[],
|
||||
isCommentOut: boolean,
|
||||
context: Context,
|
||||
): Promise<T[]> => {
|
||||
let query = repository.createQueryBuilder().insert().into(entity);
|
||||
if (isCommentOut) {
|
||||
query = query.comment(
|
||||
`${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
);
|
||||
}
|
||||
const result = await query.values(values).execute();
|
||||
|
||||
// 挿入するレコードが0で、結果も0であれば、からの配列を返す
|
||||
if (values.length === 0 && result.identifiers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 挿入するレコード数と挿入されたレコード数が一致しない場合はエラー
|
||||
if (result.identifiers.length !== values.length) {
|
||||
throw new Error('Failed to insert entities');
|
||||
}
|
||||
const where: FindOptionsWhere<T>[] = result.identifiers.map((i) => {
|
||||
// idがnumber型でない場合はエラー
|
||||
if (typeof i.id !== 'number') {
|
||||
throw new Error('Failed to insert entities');
|
||||
}
|
||||
return { id: i.id } as T;
|
||||
});
|
||||
|
||||
// 結果をもとにセレクトする
|
||||
const inserted = await repository.find({
|
||||
where,
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
});
|
||||
if (!inserted) {
|
||||
throw new Error('Failed to insert entity');
|
||||
}
|
||||
return inserted;
|
||||
};
|
||||
|
||||
const updateEntity = async <T extends ObjectLiteral>(
|
||||
repository: Repository<T>,
|
||||
criteria:
|
||||
| string
|
||||
| ((qb: UpdateQueryBuilder<T>) => string)
|
||||
| Brackets
|
||||
| ObjectLiteral
|
||||
| ObjectLiteral[],
|
||||
values: QueryDeepPartialEntity<T>,
|
||||
isCommentOut: boolean,
|
||||
context: Context,
|
||||
): Promise<UpdateResult> => {
|
||||
let query = repository.createQueryBuilder().update();
|
||||
if (isCommentOut) {
|
||||
query = query.comment(
|
||||
`${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
);
|
||||
}
|
||||
return await query.set(values).where(criteria).execute();
|
||||
};
|
||||
|
||||
const deleteEntity = async <T extends ObjectLiteral>(
|
||||
repository: Repository<T>,
|
||||
criteria: string | Brackets | ObjectLiteral | ObjectLiteral[],
|
||||
isCommentOut: boolean,
|
||||
context: Context,
|
||||
): Promise<DeleteResult> => {
|
||||
let query = repository.createQueryBuilder().delete();
|
||||
if (isCommentOut) {
|
||||
query = query.comment(
|
||||
`${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
);
|
||||
}
|
||||
return await query.where(criteria).execute();
|
||||
};
|
||||
|
||||
export { insertEntity, insertEntities, updateEntity, deleteEntity };
|
||||
10
data_migration_tools/server/src/common/types/role/index.ts
Normal file
10
data_migration_tools/server/src/common/types/role/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../../constants';
|
||||
|
||||
/**
|
||||
* Token.roleに配置されうる文字列リテラル型
|
||||
*/
|
||||
export type Roles =
|
||||
| (typeof ADMIN_ROLES)[keyof typeof ADMIN_ROLES]
|
||||
| (typeof USER_ROLES)[keyof typeof USER_ROLES];
|
||||
|
||||
export type UserRoles = (typeof USER_ROLES)[keyof typeof USER_ROLES];
|
||||
27
data_migration_tools/server/src/common/types/sort/index.ts
Normal file
27
data_migration_tools/server/src/common/types/sort/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import {
|
||||
TASK_LIST_SORTABLE_ATTRIBUTES,
|
||||
SORT_DIRECTIONS,
|
||||
} from '../../../constants';
|
||||
|
||||
export type TaskListSortableAttribute =
|
||||
(typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number];
|
||||
|
||||
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
|
||||
|
||||
export const isTaskListSortableAttribute = (
|
||||
arg: string,
|
||||
): arg is TaskListSortableAttribute => {
|
||||
const param = arg as TaskListSortableAttribute;
|
||||
if (TASK_LIST_SORTABLE_ATTRIBUTES.includes(param)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isSortDirection = (arg: string): arg is SortDirection => {
|
||||
const param = arg as SortDirection;
|
||||
if (SORT_DIRECTIONS.includes(param)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
11
data_migration_tools/server/src/common/types/sort/util.ts
Normal file
11
data_migration_tools/server/src/common/types/sort/util.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { SortDirection, TaskListSortableAttribute } from '.';
|
||||
|
||||
export const getDirection = (direction: SortDirection): SortDirection => {
|
||||
return direction;
|
||||
};
|
||||
|
||||
export const getTaskListSortableAttribute = (
|
||||
TaskListSortableAttribute: TaskListSortableAttribute,
|
||||
): TaskListSortableAttribute => {
|
||||
return TaskListSortableAttribute;
|
||||
};
|
||||
287
data_migration_tools/server/src/common/types/types.ts
Normal file
287
data_migration_tools/server/src/common/types/types.ts
Normal file
@ -0,0 +1,287 @@
|
||||
export class csvInputFile {
|
||||
type: string;
|
||||
account_id: string;
|
||||
parent_id: string;
|
||||
email: string;
|
||||
company_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
country: string;
|
||||
state: string;
|
||||
start_date: string;
|
||||
expired_date: string;
|
||||
user_email: string;
|
||||
author_id: string;
|
||||
recording_mode: string;
|
||||
wt1: string;
|
||||
wt2: string;
|
||||
wt3: string;
|
||||
wt4: string;
|
||||
wt5: string;
|
||||
wt6: string;
|
||||
wt7: string;
|
||||
wt8: string;
|
||||
wt9: string;
|
||||
wt10: string;
|
||||
wt11: string;
|
||||
wt12: string;
|
||||
wt13: string;
|
||||
wt14: string;
|
||||
wt15: string;
|
||||
wt16: string;
|
||||
wt17: string;
|
||||
wt18: string;
|
||||
wt19: string;
|
||||
wt20: string;
|
||||
}
|
||||
|
||||
export class csvInputFileWithRow extends csvInputFile {
|
||||
row: number;
|
||||
}
|
||||
|
||||
export class AccountsFileType {
|
||||
accountId: number;
|
||||
type: string;
|
||||
companyName: string;
|
||||
country: string;
|
||||
dealerAccountId?: number;
|
||||
adminName: string;
|
||||
adminMail: string;
|
||||
userId: number;
|
||||
role: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export class AccountsFile {
|
||||
accountId: number;
|
||||
type: number;
|
||||
companyName: string;
|
||||
country: string;
|
||||
dealerAccountId?: number;
|
||||
adminName: string;
|
||||
adminMail: string;
|
||||
userId: number;
|
||||
role: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export class UsersFile {
|
||||
accountId: number;
|
||||
userId: number;
|
||||
name: string;
|
||||
role: string;
|
||||
authorId: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class LicensesFile {
|
||||
expiry_date: string;
|
||||
account_id: number;
|
||||
type: string;
|
||||
status: string;
|
||||
allocated_user_id?: number;
|
||||
}
|
||||
|
||||
export class WorktypesFile {
|
||||
account_id: number;
|
||||
custom_worktype_id: string;
|
||||
}
|
||||
|
||||
export class CardLicensesFile {
|
||||
license_id: number;
|
||||
issue_id: number;
|
||||
card_license_key: string;
|
||||
activated_at?: string;
|
||||
created_at?: string;
|
||||
created_by?: string;
|
||||
updated_at?: string;
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export class AccountsMappingFile {
|
||||
accountIdText: string;
|
||||
accountIdNumber: number;
|
||||
}
|
||||
|
||||
export class VerificationResultDetails {
|
||||
input: string;
|
||||
inputRow: number;
|
||||
diffTargetTable: string;
|
||||
columnName: string;
|
||||
fileData: string;
|
||||
databaseData: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
|
||||
export function isAccountsFileArray(obj: any): obj is AccountsFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isAccountsFile(item));
|
||||
}
|
||||
export function isAccountsFile(obj: any): obj is AccountsFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"accountId" in obj &&
|
||||
typeof obj.accountId === "number" &&
|
||||
"type" in obj &&
|
||||
typeof obj.type === "number" &&
|
||||
"companyName" in obj &&
|
||||
typeof obj.companyName === "string" &&
|
||||
"country" in obj &&
|
||||
typeof obj.country === "string" &&
|
||||
("dealerAccountId" in obj
|
||||
? obj.dealerAccountId === null || typeof obj.dealerAccountId === "number"
|
||||
: true) &&
|
||||
"adminName" in obj &&
|
||||
typeof obj.adminName === "string" &&
|
||||
"adminMail" in obj &&
|
||||
typeof obj.adminMail === "string" &&
|
||||
"userId" in obj &&
|
||||
typeof obj.userId === "number" &&
|
||||
("role" in obj
|
||||
? obj.role === null || typeof obj.role === "string"
|
||||
: true) &&
|
||||
("authorId" in obj
|
||||
? obj.authorId === null || typeof obj.authorId === "string"
|
||||
: true)
|
||||
);
|
||||
}
|
||||
|
||||
export function isUsersFileArray(obj: any): obj is UsersFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isUsersFile(item));
|
||||
}
|
||||
export function isUsersFile(obj: any): obj is UsersFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"accountId" in obj &&
|
||||
"userId" in obj &&
|
||||
"name" in obj &&
|
||||
"role" in obj &&
|
||||
"authorId" in obj &&
|
||||
"email" in obj &&
|
||||
typeof obj.accountId === "number" &&
|
||||
typeof obj.userId === "number" &&
|
||||
typeof obj.name === "string" &&
|
||||
typeof obj.role === "string" &&
|
||||
typeof obj.authorId === "string" &&
|
||||
typeof obj.email === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function isLicensesFileArray(obj: any): obj is LicensesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isLicensesFile(item));
|
||||
}
|
||||
export function isLicensesFile(obj: any): obj is LicensesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"expiry_date" in obj &&
|
||||
"account_id" in obj &&
|
||||
"type" in obj &&
|
||||
"status" in obj &&
|
||||
typeof obj.expiry_date === "string" &&
|
||||
typeof obj.account_id === "number" &&
|
||||
typeof obj.type === "string" &&
|
||||
typeof obj.status === "string" &&
|
||||
(obj.allocated_user_id === null ||
|
||||
typeof obj.allocated_user_id === "number")
|
||||
);
|
||||
}
|
||||
|
||||
export function isWorktypesFileArray(obj: any): obj is WorktypesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isWorktypesFile(item));
|
||||
}
|
||||
export function isWorktypesFile(obj: any): obj is WorktypesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"account_id" in obj &&
|
||||
"custom_worktype_id" in obj &&
|
||||
typeof obj.account_id === "number" &&
|
||||
typeof obj.custom_worktype_id === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function isCardLicensesFileArray(obj: any): obj is CardLicensesFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isCardLicensesFile(item));
|
||||
}
|
||||
export function isCardLicensesFile(obj: any): obj is CardLicensesFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"license_id" in obj &&
|
||||
"issue_id" in obj &&
|
||||
"card_license_key" in obj &&
|
||||
typeof obj.license_id === "number" &&
|
||||
typeof obj.issue_id === "number" &&
|
||||
typeof obj.card_license_key === "string" &&
|
||||
(obj.activated_at === null || typeof obj.activated_at === "string") &&
|
||||
(obj.created_at === null || typeof obj.created_at === "string") &&
|
||||
(obj.created_by === null || typeof obj.created_by === "string") &&
|
||||
(obj.updated_at === null || typeof obj.updated_at === "string") &&
|
||||
(obj.updated_by === null || typeof obj.updated_by === "string")
|
||||
);
|
||||
}
|
||||
|
||||
export function isAccountsMappingFileArray(
|
||||
obj: any
|
||||
): obj is AccountsMappingFile[] {
|
||||
return Array.isArray(obj) && obj.every((item) => isAccountsMappingFile(item));
|
||||
}
|
||||
export function isAccountsMappingFile(obj: any): obj is AccountsMappingFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
obj !== null &&
|
||||
"accountIdText" in obj &&
|
||||
"accountIdNumber" in obj &&
|
||||
typeof obj.accountIdText === "string" &&
|
||||
typeof obj.accountIdNumber === "number"
|
||||
);
|
||||
}
|
||||
|
||||
export function isCsvInputFileForValidateArray(obj: any): obj is csvInputFile[] {
|
||||
return (
|
||||
Array.isArray(obj) && obj.every((item) => isCsvInputFileForValidate(item))
|
||||
);
|
||||
}
|
||||
|
||||
export function isCsvInputFileForValidate(obj: any): obj is csvInputFile {
|
||||
return (
|
||||
typeof obj === "object" &&
|
||||
"type" in obj &&
|
||||
"account_id" in obj &&
|
||||
"parent_id" in obj &&
|
||||
"email" in obj &&
|
||||
"company_name" in obj &&
|
||||
"first_name" in obj &&
|
||||
"last_name" in obj &&
|
||||
"country" in obj &&
|
||||
"state" in obj &&
|
||||
"start_date" in obj &&
|
||||
"expired_date" in obj &&
|
||||
"user_email" in obj &&
|
||||
"author_id" in obj &&
|
||||
"recording_mode" in obj &&
|
||||
"wt1" in obj &&
|
||||
"wt2" in obj &&
|
||||
"wt3" in obj &&
|
||||
"wt4" in obj &&
|
||||
"wt5" in obj &&
|
||||
"wt6" in obj &&
|
||||
"wt7" in obj &&
|
||||
"wt8" in obj &&
|
||||
"wt9" in obj &&
|
||||
"wt10" in obj &&
|
||||
"wt11" in obj &&
|
||||
"wt12" in obj &&
|
||||
"wt13" in obj &&
|
||||
"wt14" in obj &&
|
||||
"wt15" in obj &&
|
||||
"wt16" in obj &&
|
||||
"wt17" in obj &&
|
||||
"wt18" in obj &&
|
||||
"wt19" in obj &&
|
||||
"wt20" in obj
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
} from "class-validator";
|
||||
|
||||
@ValidatorConstraint()
|
||||
export class IsAdminPassword implements ValidatorConstraintInterface {
|
||||
validate(value: string): boolean {
|
||||
// 8文字~64文字でなければ早期に不合格
|
||||
const minLength = 8;
|
||||
const maxLength = 64;
|
||||
if (value.length < minLength || value.length > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ
|
||||
const charaTypePattern =
|
||||
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
|
||||
return new RegExp(charaTypePattern).test(value);
|
||||
}
|
||||
defaultMessage(): string {
|
||||
return "Admin password rule not satisfied";
|
||||
}
|
||||
}
|
||||
|
||||
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
|
||||
return (object: any, propertyName: string) => {
|
||||
registerDecorator({
|
||||
name: "IsAdminPasswordvalid",
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [],
|
||||
options: validationOptions,
|
||||
validator: IsAdminPassword,
|
||||
});
|
||||
};
|
||||
};
|
||||
406
data_migration_tools/server/src/constants/index.ts
Normal file
406
data_migration_tools/server/src/constants/index.ts
Normal file
@ -0,0 +1,406 @@
|
||||
/**
|
||||
* 階層
|
||||
* @const {number}
|
||||
*/
|
||||
export const TIERS = {
|
||||
//OMDS東京
|
||||
TIER1: 1,
|
||||
//OMDS現地法人
|
||||
TIER2: 2,
|
||||
//代理店
|
||||
TIER3: 3,
|
||||
//販売店
|
||||
TIER4: 4,
|
||||
//エンドユーザー
|
||||
TIER5: 5,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 音声ファイルを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",
|
||||
];
|
||||
|
||||
/**
|
||||
* 管理ロール
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADMIN_ROLES = {
|
||||
ADMIN: "admin",
|
||||
STANDARD: "standard",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ロール
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_ROLES = {
|
||||
NONE: "none",
|
||||
AUTHOR: "author",
|
||||
TYPIST: "typist",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ロールのソート順
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_ROLE_ORDERS = [
|
||||
USER_ROLES.AUTHOR,
|
||||
USER_ROLES.TYPIST,
|
||||
USER_ROLES.NONE,
|
||||
] as string[];
|
||||
|
||||
/**
|
||||
* ライセンス注文状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ISSUE_STATUS = {
|
||||
ISSUE_REQUESTING: "Issue Requesting",
|
||||
ISSUED: "Issued",
|
||||
CANCELED: "Order Canceled",
|
||||
};
|
||||
|
||||
/**
|
||||
* ライセンス種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_TYPE = {
|
||||
TRIAL: "TRIAL",
|
||||
NORMAL: "NORMAL",
|
||||
CARD: "CARD",
|
||||
} as const;
|
||||
/**
|
||||
* ライセンス状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ALLOCATED_STATUS = {
|
||||
UNALLOCATED: "Unallocated",
|
||||
ALLOCATED: "Allocated",
|
||||
REUSABLE: "Reusable",
|
||||
DELETED: "Deleted",
|
||||
} as const;
|
||||
/**
|
||||
* 切り替え元種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const SWITCH_FROM_TYPE = {
|
||||
NONE: "NONE",
|
||||
CARD: "CARD",
|
||||
TRIAL: "TRIAL",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ライセンスの期限切れが近いと見なす日数のしきい値
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
|
||||
|
||||
/**
|
||||
* ライセンスの有効期間
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_DAYS = 365;
|
||||
|
||||
/**
|
||||
* タイムゾーンを加味したライセンスの有効期間(8時間)
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8;
|
||||
|
||||
/**
|
||||
* カードライセンスの桁数
|
||||
* @const {number}
|
||||
*/
|
||||
export const CARD_LICENSE_LENGTH = 20;
|
||||
|
||||
/**
|
||||
* 音声ファイルに紐づくオプションアイテムの数
|
||||
* @const {string}
|
||||
*/
|
||||
export const OPTION_ITEM_NUM = 10;
|
||||
|
||||
/**
|
||||
* 文字起こしタスクのステータス
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TASK_STATUS = {
|
||||
UPLOADED: "Uploaded",
|
||||
PENDING: "Pending",
|
||||
IN_PROGRESS: "InProgress",
|
||||
FINISHED: "Finished",
|
||||
BACKUP: "Backup",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* タスク一覧でソート可能な属性の一覧
|
||||
*/
|
||||
export const TASK_LIST_SORTABLE_ATTRIBUTES = [
|
||||
"JOB_NUMBER",
|
||||
"STATUS",
|
||||
"ENCRYPTION",
|
||||
"AUTHOR_ID",
|
||||
"WORK_TYPE",
|
||||
"FILE_NAME",
|
||||
"FILE_LENGTH",
|
||||
"FILE_SIZE",
|
||||
"RECORDING_STARTED_DATE",
|
||||
"RECORDING_FINISHED_DATE",
|
||||
"UPLOAD_DATE",
|
||||
"TRANSCRIPTION_STARTED_DATE",
|
||||
"TRANSCRIPTION_FINISHED_DATE",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* タスク一覧のソート条件(昇順・降順)
|
||||
*/
|
||||
export const SORT_DIRECTIONS = ["ASC", "DESC"] as const;
|
||||
|
||||
/**
|
||||
* 通知タグの最大個数
|
||||
* NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで
|
||||
* https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions
|
||||
*/
|
||||
export const TAG_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* 通知のプラットフォーム種別文字列
|
||||
*/
|
||||
export const PNS = {
|
||||
WNS: "wns",
|
||||
APNS: "apns",
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザーのライセンスの有効期限の状態
|
||||
*/
|
||||
export const USER_LICENSE_EXPIRY_STATUS = {
|
||||
NORMAL: "Normal",
|
||||
NO_LICENSE: "NoLicense",
|
||||
ALERT: "Alert",
|
||||
RENEW: "Renew",
|
||||
};
|
||||
|
||||
/**
|
||||
*トライアルライセンスの有効期限(日数)
|
||||
* @const {number}
|
||||
*/
|
||||
export const TRIAL_LICENSE_EXPIRATION_DAYS = 30;
|
||||
|
||||
/**
|
||||
* ライセンスの発行数
|
||||
* @const {number}
|
||||
*/
|
||||
export const TRIAL_LICENSE_ISSUE_NUM = 100;
|
||||
|
||||
/**
|
||||
* worktypeの最大登録数
|
||||
* @const {number}
|
||||
*/
|
||||
export const WORKTYPE_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* worktypeのDefault値の取りうる値
|
||||
**/
|
||||
export const OPTION_ITEM_VALUE_TYPE = {
|
||||
DEFAULT: "Default",
|
||||
BLANK: "Blank",
|
||||
LAST_INPUT: "LastInput",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* オプションアイテムのタイプ文字列と数値の対応
|
||||
**/
|
||||
export const OPTION_ITEM_VALUE_TYPE_NUMBER: {
|
||||
type: string;
|
||||
value: number;
|
||||
}[] = [
|
||||
{
|
||||
type: OPTION_ITEM_VALUE_TYPE.BLANK,
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
type: OPTION_ITEM_VALUE_TYPE.LAST_INPUT,
|
||||
value: 3,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* ADB2Cユーザのidentity.signInType
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADB2C_SIGN_IN_TYPE = {
|
||||
EMAILADDRESS: "emailAddress",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* MANUAL_RECOVERY_REQUIRED
|
||||
* @const {string}
|
||||
*/
|
||||
export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]";
|
||||
|
||||
/**
|
||||
* 利用規約種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERM_TYPE = {
|
||||
EULA: "EULA",
|
||||
DPA: "DPA",
|
||||
PRIVACY_NOTICE: "PrivacyNotice",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 音声ファイルのフォーマット
|
||||
* @const {string}
|
||||
*/
|
||||
export const USER_AUDIO_FORMAT = "DS2(QP)";
|
||||
|
||||
/**
|
||||
* ユニットテスト実行をしている場合のNODE_ENVの値
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const NODE_ENV_TEST = "test";
|
||||
|
||||
/**
|
||||
* ユーザに対するライセンスの状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_LICENSE_STATUS = {
|
||||
UNALLOCATED: "unallocated",
|
||||
ALLOCATED: "allocated",
|
||||
EXPIRED: "expired",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* typeの取りうる値(移行元CSV)
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const MIGRATION_TYPE = {
|
||||
ADMINISTRATOR: "Administrator",
|
||||
BC: "BC",
|
||||
COUNTRY: "Country",
|
||||
CUSTOMER: "Customer",
|
||||
DEALER: "Dealer",
|
||||
DISTRIBUTOR: "Distributor",
|
||||
USER: "USER",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 移行先の名称と移行元の値
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const COUNTRY_LIST = [
|
||||
{ value: "CA", label: "Canada" },
|
||||
{ value: "KY", label: "Cayman Islands" },
|
||||
{ value: "US", label: "United States" },
|
||||
{ value: "AU", label: "Australia" },
|
||||
{ value: "NZ", label: "New Zealand" },
|
||||
{ value: "AT", label: "Austria" },
|
||||
{ value: "BE", label: "Belgium" },
|
||||
{ value: "BG", label: "Bulgaria" },
|
||||
{ value: "HR", label: "Croatia" },
|
||||
{ value: "CY", label: "Cyprus" },
|
||||
{ value: "CZ", label: "Czech" },
|
||||
{ value: "DK", label: "Denmark" },
|
||||
{ value: "EE", label: "Estonia" },
|
||||
{ value: "FI", label: "Finland" },
|
||||
{ value: "FR", label: "France" },
|
||||
{ value: "DE", label: "Germany" },
|
||||
{ value: "GR", label: "Greece" },
|
||||
{ value: "HU", label: "Hungary" },
|
||||
{ value: "IS", label: "Iceland" },
|
||||
{ value: "IE", label: "Ireland" },
|
||||
{ value: "IT", label: "Italy" },
|
||||
{ value: "LV", label: "Latvia" },
|
||||
{ value: "LI", label: "Liechtenstein" },
|
||||
{ value: "LT", label: "Lithuania" },
|
||||
{ value: "LU", label: "Luxembourg" },
|
||||
{ value: "MT", label: "Malta" },
|
||||
{ value: "NL", label: "Netherlands" },
|
||||
{ value: "NO", label: "Norway" },
|
||||
{ value: "PL", label: "Poland" },
|
||||
{ value: "PT", label: "Portugal" },
|
||||
{ value: "RO", label: "Romania" },
|
||||
{ value: "RS", label: "Serbia" },
|
||||
{ value: "SK", label: "Slovakia" },
|
||||
{ value: "SI", label: "Slovenia" },
|
||||
{ value: "ZA", label: "South Africa" },
|
||||
{ value: "ES", label: "Spain" },
|
||||
{ value: "SE", label: "Sweden" },
|
||||
{ value: "CH", label: "Switzerland" },
|
||||
{ value: "TR", label: "Turkey" },
|
||||
{ value: "GB", label: "United Kingdom" },
|
||||
];
|
||||
|
||||
/**
|
||||
* recording_modeの取りうる値(移行元CSV)
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const RECORDING_MODE = {
|
||||
DS2_QP: "DS2 (QP)",
|
||||
DS2_SP: "DS2 (SP)",
|
||||
DSS: "DSS",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* AutoIncrementの初期値
|
||||
* @const {number}
|
||||
*/
|
||||
export const AUTO_INCREMENT_START = 853211;
|
||||
|
||||
/**
|
||||
* 移行データ登録時のsleep間隔
|
||||
* @const {number}
|
||||
*/
|
||||
export const MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC = 13;
|
||||
@ -0,0 +1,12 @@
|
||||
import { Controller, Logger } from "@nestjs/common";
|
||||
import { ApiTags } from "@nestjs/swagger";
|
||||
import { AccountsService } from "./accounts.service";
|
||||
|
||||
@ApiTags("accounts")
|
||||
@Controller("accounts")
|
||||
export class AccountsController {
|
||||
private readonly logger = new Logger(AccountsController.name);
|
||||
constructor(
|
||||
private readonly accountService: AccountsService //private readonly cryptoService: CryptoService,
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
|
||||
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
|
||||
import { AccountsController } from "./accounts.controller";
|
||||
import { AccountsService } from "./accounts.service";
|
||||
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
|
||||
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
AdB2cModule,
|
||||
BlobstorageModule,
|
||||
],
|
||||
controllers: [AccountsController],
|
||||
providers: [AccountsService],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
@ -0,0 +1,232 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
|
||||
import {
|
||||
AdB2cService,
|
||||
ConflictError,
|
||||
isConflictError,
|
||||
} from "../../gateways/adb2c/adb2c.service";
|
||||
import { Account } from "../../repositories/accounts/entity/account.entity";
|
||||
import { User } from "../../repositories/users/entity/user.entity";
|
||||
import { MANUAL_RECOVERY_REQUIRED } from "../../constants";
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
import { Context } from "../../common/log";
|
||||
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
|
||||
|
||||
@Injectable()
|
||||
export class AccountsService {
|
||||
constructor(
|
||||
private readonly accountRepository: AccountsRepositoryService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly blobStorageService: BlobstorageService
|
||||
) {}
|
||||
private readonly logger = new Logger(AccountsService.name);
|
||||
|
||||
/**
|
||||
* アカウント情報をDBに作成する
|
||||
* @param companyName
|
||||
* @param country
|
||||
* @param [dealerAccountId]
|
||||
* @returns account
|
||||
*/
|
||||
async createAccount(
|
||||
context: Context,
|
||||
companyName: string,
|
||||
country: string,
|
||||
dealerAccountId: number | undefined,
|
||||
email: string,
|
||||
password: string,
|
||||
username: string,
|
||||
role: string,
|
||||
authorId: string,
|
||||
acceptedEulaVersion: string,
|
||||
acceptedPrivacyNoticeVersion: string,
|
||||
acceptedDpaVersion: string,
|
||||
type: number,
|
||||
accountId: number,
|
||||
userId: number
|
||||
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.createAccount.name
|
||||
} | params: { ` +
|
||||
`dealerAccountId: ${dealerAccountId}, ` +
|
||||
`role: ${role}, ` +
|
||||
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
|
||||
`acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` +
|
||||
`acceptedDpaVersion: ${acceptedDpaVersion}, ` +
|
||||
`type: ${type}, ` +
|
||||
`accountId: ${accountId}, ` +
|
||||
`userId: ${userId} };`
|
||||
);
|
||||
try {
|
||||
let externalUser: { sub: string } | ConflictError;
|
||||
try {
|
||||
// idpにユーザーを作成
|
||||
externalUser = await this.adB2cService.createUser(
|
||||
context,
|
||||
email,
|
||||
password,
|
||||
username
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] create externalUser failed`
|
||||
);
|
||||
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
this.logger.log("idpにユーザーを作成成功");
|
||||
|
||||
// メールアドレス重複エラー
|
||||
if (isConflictError(externalUser)) {
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] email conflict. externalUser: ${externalUser}`
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E010301"),
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
this.logger.log("メールアドレスは重複していません");
|
||||
|
||||
let account: Account;
|
||||
let user: User;
|
||||
try {
|
||||
// アカウントと管理者をセットで作成
|
||||
const { newAccount, adminUser } =
|
||||
await this.accountRepository.createAccount(
|
||||
context,
|
||||
companyName,
|
||||
country,
|
||||
dealerAccountId,
|
||||
type,
|
||||
externalUser.sub,
|
||||
role,
|
||||
authorId,
|
||||
accountId,
|
||||
userId,
|
||||
acceptedEulaVersion,
|
||||
acceptedPrivacyNoticeVersion,
|
||||
acceptedDpaVersion
|
||||
);
|
||||
account = newAccount;
|
||||
user = adminUser;
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] adminUser.external_id: ${
|
||||
user.external_id
|
||||
}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(`[${context.getTrackingId()}] create account failed`);
|
||||
//リカバリ処理
|
||||
// idpのユーザーを削除
|
||||
await this.deleteAdB2cUser(externalUser.sub, context);
|
||||
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
// 新規作成アカウント用のBlobコンテナを作成
|
||||
try {
|
||||
await this.blobStorageService.createContainer(
|
||||
context,
|
||||
account.id,
|
||||
country
|
||||
);
|
||||
this.logger.log("コンテナー作成成功");
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] create container failed`
|
||||
);
|
||||
//リカバリ処理
|
||||
// idpのユーザーを削除
|
||||
await this.deleteAdB2cUser(externalUser.sub, context);
|
||||
|
||||
// DBのアカウントを削除
|
||||
await this.deleteAccount(account.id, user.id, context);
|
||||
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
accountId: account.id,
|
||||
userId: user.id,
|
||||
externalUserId: user.external_id,
|
||||
};
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.createAccount.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// AdB2cのユーザーを削除
|
||||
// TODO「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
|
||||
private async deleteAdB2cUser(
|
||||
externalUserId: string,
|
||||
context: Context
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.createAccount.name
|
||||
} | params: { ` + `externalUserId: ${externalUserId}};`
|
||||
);
|
||||
try {
|
||||
await this.adB2cService.deleteUser(externalUserId, context);
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] delete externalUser: ${externalUserId} | params: { ` +
|
||||
`externalUserId: ${externalUserId}, };`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
|
||||
this.logger.error(
|
||||
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.deleteAdB2cUser.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DBのアカウントを削除
|
||||
private async deleteAccount(
|
||||
accountId: number,
|
||||
userId: number,
|
||||
context: Context
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.deleteAccount.name
|
||||
} | params: { accountId: ${accountId}, userId: ${userId} };`
|
||||
);
|
||||
try {
|
||||
await this.accountRepository.deleteAccount(context, accountId, userId);
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
|
||||
this.logger.error(
|
||||
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete account: ${accountId}, user: ${userId}`
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.deleteAccount.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { DeleteService } from "./delete.service";
|
||||
import { DeleteController } from "./delete.controller";
|
||||
|
||||
describe("DeleteController", () => {
|
||||
let controller: DeleteController;
|
||||
const mockTemplatesService = {};
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: [".env.test", ".env"],
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
controllers: [DeleteController],
|
||||
providers: [DeleteService],
|
||||
})
|
||||
.overrideProvider(DeleteService)
|
||||
.useValue(mockTemplatesService)
|
||||
.compile();
|
||||
|
||||
controller = module.get<DeleteController>(DeleteController);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
import {
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Post,
|
||||
Req,
|
||||
} from "@nestjs/common";
|
||||
import { ErrorResponse } from "../../common/errors/types/types";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { DeleteService } from "./delete.service";
|
||||
import { DeleteResponse } from "./types/types";
|
||||
import { makeContext } from "src/common/log";
|
||||
|
||||
@ApiTags("delete")
|
||||
@Controller("delete")
|
||||
export class DeleteController {
|
||||
constructor(private readonly deleteService: DeleteService) {}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: DeleteResponse,
|
||||
description: "成功時のレスポンス",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: "想定外のサーバーエラー",
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: "deleteData",
|
||||
description: "すべてのデータを削除します",
|
||||
})
|
||||
@Post()
|
||||
async deleteData(): Promise<{}> {
|
||||
const context = makeContext("tool", "delete");
|
||||
|
||||
await this.deleteService.deleteData(context);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { DeleteRepositoryModule } from "../../repositories/delete/delete.repository.module";
|
||||
import { DeleteController } from "./delete.controller";
|
||||
import { DeleteService } from "./delete.service";
|
||||
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
|
||||
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
|
||||
|
||||
@Module({
|
||||
imports: [DeleteRepositoryModule, AdB2cModule, BlobstorageModule],
|
||||
providers: [DeleteService],
|
||||
controllers: [DeleteController],
|
||||
})
|
||||
export class DeleteModule {}
|
||||
@ -0,0 +1,29 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { DeleteService } from "./delete.service";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
|
||||
describe("DeleteController", () => {
|
||||
let service: DeleteService;
|
||||
const mockTemplatesService = {};
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: [".env.test", ".env"],
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
providers: [DeleteService],
|
||||
})
|
||||
.overrideProvider(DeleteService)
|
||||
.useValue(mockTemplatesService)
|
||||
.compile();
|
||||
|
||||
service = module.get<DeleteService>(DeleteService);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,69 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { DeleteRepositoryService } from "../../repositories/delete/delete.repository.service";
|
||||
import { makeErrorResponse } from "../../common/errors/makeErrorResponse";
|
||||
import { AdB2cService } from "../../gateways/adb2c/adb2c.service";
|
||||
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
|
||||
import { Context } from "../../common/log";
|
||||
|
||||
@Injectable()
|
||||
export class DeleteService {
|
||||
private readonly logger = new Logger(DeleteService.name);
|
||||
constructor(
|
||||
private readonly deleteRepositoryService: DeleteRepositoryService,
|
||||
private readonly blobstorageService: BlobstorageService,
|
||||
private readonly adB2cService: AdB2cService
|
||||
) { }
|
||||
|
||||
/**
|
||||
* データを削除する
|
||||
* @returns data
|
||||
*/
|
||||
async deleteData(context: Context): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.deleteData.name}`
|
||||
);
|
||||
try {
|
||||
// BlobStorageからデータを削除する
|
||||
await this.blobstorageService.deleteContainers(context);
|
||||
|
||||
// 100件ずつのユーザー取得なのですべて削除するまでループする
|
||||
for (let i = 0; i < 500; i++) {
|
||||
// ADB2Cからユーザ情報を取得する
|
||||
const { users, hasNext } = await this.adB2cService.getUsers(context);
|
||||
|
||||
|
||||
const externalIds = users.map((user) => user.id);
|
||||
await this.adB2cService.deleteUsers(context, externalIds);
|
||||
|
||||
// 削除していないユーザーがいない場合はループを抜ける
|
||||
if (!hasNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// データベースからデータを削除する
|
||||
await this.deleteRepositoryService.deleteData();
|
||||
// AutoIncrementの値をリセットする
|
||||
await this.deleteRepositoryService.resetAutoIncrement();
|
||||
// 初期データを挿入する
|
||||
await this.deleteRepositoryService.insertInitData(context);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.deleteData.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
import { DataSource } from "typeorm";
|
||||
@ -0,0 +1 @@
|
||||
export class DeleteResponse {}
|
||||
@ -0,0 +1,239 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
Logger,
|
||||
HttpException,
|
||||
} from "@nestjs/common";
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
import fs from "fs";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { RegisterRequest, RegisterResponse } from "./types/types";
|
||||
import { RegisterService } from "./register.service";
|
||||
import { AccountsService } from "../accounts/accounts.service";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { makeContext } from "../../common/log";
|
||||
import {
|
||||
isAccountsFileArray,
|
||||
isUsersFileArray,
|
||||
isLicensesFileArray,
|
||||
isWorktypesFileArray,
|
||||
isCardLicensesFileArray,
|
||||
} from "../../common/types/types";
|
||||
import { makePassword } from "../../common/password/password";
|
||||
import {
|
||||
USER_ROLES,
|
||||
MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC,
|
||||
} from "../../constants";
|
||||
|
||||
@ApiTags("register")
|
||||
@Controller("register")
|
||||
export class RegisterController {
|
||||
private readonly logger = new Logger(RegisterController.name);
|
||||
constructor(
|
||||
private readonly registerService: RegisterService,
|
||||
private readonly accountsService: AccountsService,
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: RegisterResponse,
|
||||
description: "成功時のレスポンス",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: "想定外のサーバーエラー",
|
||||
})
|
||||
@ApiOperation({ operationId: "dataRegist" })
|
||||
async dataRegist(
|
||||
@Body() body: RegisterRequest,
|
||||
@Req() req: Request
|
||||
): Promise<RegisterResponse> {
|
||||
const context = makeContext("iko", "register");
|
||||
|
||||
const inputFilePath = body.inputFilePath;
|
||||
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.dataRegist.name
|
||||
} | params: { inputFilePath: ${inputFilePath}};`
|
||||
);
|
||||
|
||||
try {
|
||||
// 読み込みファイルのフルパス
|
||||
const accouncsFileFullPath = inputFilePath + "accounts.json";
|
||||
const usersFileFullPath = inputFilePath + "users.json";
|
||||
const licensesFileFullPath = inputFilePath + "licenses.json";
|
||||
const worktypesFileFullPath = inputFilePath + "worktypes.json";
|
||||
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
|
||||
|
||||
// ファイル存在チェックと読み込み
|
||||
// どのファイルがないのかわからないのでそれぞれに存在しない場合はエラーを出す
|
||||
if (!fs.existsSync(accouncsFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(usersFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(licensesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(worktypesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
if (!fs.existsSync(cardLicensesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
|
||||
// アカウントの登録用ファイル読み込み
|
||||
const accountsObject = JSON.parse(
|
||||
fs.readFileSync(accouncsFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(account)
|
||||
if (!isAccountsFileArray(accountsObject)) {
|
||||
throw new Error("input file is not AccountsFiles");
|
||||
}
|
||||
|
||||
for (const AccountsFile of accountsObject) {
|
||||
this.logger.log("ランダムパスワード生成開始");
|
||||
// ランダムなパスワードを生成する
|
||||
const ramdomPassword = makePassword();
|
||||
this.logger.log("ランダムパスワード生成完了");
|
||||
// roleの設定
|
||||
// roleの値がnullなら"none"、null以外ならroleの値、
|
||||
// また、roleの値が"author"なら"author"を設定
|
||||
let role: string;
|
||||
let authorId: string;
|
||||
if (AccountsFile.role === null) {
|
||||
role = USER_ROLES.NONE;
|
||||
authorId = null;
|
||||
} else if (AccountsFile.role === USER_ROLES.AUTHOR) {
|
||||
role = USER_ROLES.AUTHOR;
|
||||
authorId = AccountsFile.authorId;
|
||||
} else {
|
||||
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
|
||||
throw new Error("Invalid role value");
|
||||
}
|
||||
this.logger.log("account生成開始");
|
||||
await this.accountsService.createAccount(
|
||||
context,
|
||||
AccountsFile.companyName,
|
||||
AccountsFile.country,
|
||||
AccountsFile.dealerAccountId,
|
||||
AccountsFile.adminMail,
|
||||
ramdomPassword,
|
||||
AccountsFile.adminName,
|
||||
role,
|
||||
authorId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
AccountsFile.type,
|
||||
AccountsFile.accountId,
|
||||
AccountsFile.userId
|
||||
);
|
||||
|
||||
// ratelimit対応のためsleepを行う
|
||||
await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC);
|
||||
}
|
||||
// const AccountsFiles = accountsObject as AccountsFile[];
|
||||
|
||||
// ユーザの登録用ファイル読み込み
|
||||
const usersObject = JSON.parse(
|
||||
fs.readFileSync(usersFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(user)
|
||||
if (!isUsersFileArray(usersObject)) {
|
||||
throw new Error("input file is not UsersFiles");
|
||||
}
|
||||
|
||||
for (const UsersFile of usersObject) {
|
||||
this.logger.log(UsersFile.name);
|
||||
await this.usersService.createUser(
|
||||
context,
|
||||
UsersFile.name,
|
||||
UsersFile.role === USER_ROLES.AUTHOR
|
||||
? USER_ROLES.AUTHOR
|
||||
: USER_ROLES.NONE,
|
||||
UsersFile.email,
|
||||
true,
|
||||
true,
|
||||
UsersFile.accountId,
|
||||
UsersFile.userId,
|
||||
UsersFile.authorId,
|
||||
false,
|
||||
null,
|
||||
true
|
||||
);
|
||||
// ratelimit対応のためsleepを行う
|
||||
await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC);
|
||||
}
|
||||
|
||||
// ライセンスの登録用ファイル読み込み
|
||||
const licensesObject = JSON.parse(
|
||||
fs.readFileSync(licensesFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(license)
|
||||
if (!isLicensesFileArray(licensesObject)) {
|
||||
throw new Error("input file is not LicensesFiles");
|
||||
}
|
||||
|
||||
// ワークタイプの登録用ファイル読み込み
|
||||
const worktypesObject = JSON.parse(
|
||||
fs.readFileSync(worktypesFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(Worktypes)
|
||||
if (!isWorktypesFileArray(worktypesObject)) {
|
||||
throw new Error("input file is not WorktypesFiles");
|
||||
}
|
||||
|
||||
// カードライセンスの登録用ファイル読み込み
|
||||
const cardLicensesObject = JSON.parse(
|
||||
fs.readFileSync(cardLicensesFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(cardLicenses)
|
||||
if (!isCardLicensesFileArray(cardLicensesObject)) {
|
||||
throw new Error("input file is not cardLicensesFiles");
|
||||
}
|
||||
|
||||
// ライセンス・ワークタイプ・カードライセンスの登録
|
||||
await this.registerService.registLicenseAndWorktypeData(
|
||||
context,
|
||||
licensesObject,
|
||||
worktypesObject,
|
||||
cardLicensesObject
|
||||
);
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.dataRegist.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { RegisterController } from "./register.controller";
|
||||
import { RegisterService } from "./register.service";
|
||||
import { AccountsService } from "../accounts/accounts.service";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
|
||||
import { WorktypesRepositoryModule } from "../../repositories/worktypes/worktypes.repository.module";
|
||||
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
|
||||
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
|
||||
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
|
||||
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
LicensesRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
AdB2cModule,
|
||||
BlobstorageModule,
|
||||
],
|
||||
controllers: [RegisterController],
|
||||
providers: [RegisterService, AccountsService, UsersService],
|
||||
})
|
||||
export class RegisterModule {}
|
||||
@ -0,0 +1,65 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
LicensesFile,
|
||||
WorktypesFile,
|
||||
CardLicensesFile,
|
||||
} from "../../common/types/types";
|
||||
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
|
||||
import { WorktypesRepositoryService } from "../../repositories/worktypes/worktypes.repository.service";
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
@Injectable()
|
||||
export class RegisterService {
|
||||
constructor(
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
private readonly worktypesRepository: WorktypesRepositoryService
|
||||
) {}
|
||||
private readonly logger = new Logger(RegisterService.name);
|
||||
|
||||
/**
|
||||
* Regist Data
|
||||
* @param inputFilePath: string
|
||||
*/
|
||||
async registLicenseAndWorktypeData(
|
||||
context: Context,
|
||||
LicensesFiles: LicensesFile[],
|
||||
WorktypesFiles: WorktypesFile[],
|
||||
cardLicensesFiles: CardLicensesFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.registLicenseAndWorktypeData.name
|
||||
}`
|
||||
);
|
||||
|
||||
try {
|
||||
this.logger.log("Licenses register start");
|
||||
await this.licensesRepository.insertLicenses(context, LicensesFiles);
|
||||
this.logger.log("Licenses register end");
|
||||
|
||||
this.logger.log("Worktypes register start");
|
||||
await this.worktypesRepository.createWorktype(context, WorktypesFiles);
|
||||
this.logger.log("Worktypes register end");
|
||||
|
||||
this.logger.log("CardLicenses register start");
|
||||
await this.licensesRepository.insertCardLicenses(
|
||||
context,
|
||||
cardLicensesFiles
|
||||
);
|
||||
this.logger.log("CardLicenses register end");
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${
|
||||
this.registLicenseAndWorktypeData.name
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class RegisterRequest {
|
||||
@ApiProperty()
|
||||
inputFilePath: string;
|
||||
|
||||
}
|
||||
|
||||
export class RegisterResponse {}
|
||||
|
||||
@ -0,0 +1,225 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
HttpException,
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import fs from "fs";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { transferRequest, transferResponse } from "./types/types";
|
||||
import { TransferService } from "./transfer.service";
|
||||
import { makeContext } from "../../common/log";
|
||||
import { csvInputFile, AccountsMappingFile } from "../../common/types/types";
|
||||
import { makeErrorResponse } from "src/common/errors/makeErrorResponse";
|
||||
import { AUTO_INCREMENT_START } from "../../constants";
|
||||
@ApiTags("transfer")
|
||||
@Controller("transfer")
|
||||
export class TransferController {
|
||||
private readonly logger = new Logger(TransferController.name);
|
||||
constructor(private readonly transferService: TransferService) {}
|
||||
|
||||
@Post()
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: transferResponse,
|
||||
description: "成功時のレスポンス",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: "想定外のサーバーエラー",
|
||||
})
|
||||
@ApiOperation({ operationId: "dataRegist" })
|
||||
async dataRegist(
|
||||
@Body() body: transferRequest,
|
||||
@Req() req: Request
|
||||
): Promise<transferResponse> {
|
||||
const context = makeContext("iko", "transfer");
|
||||
|
||||
const inputFilePath = body.inputFilePath;
|
||||
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.dataRegist.name
|
||||
} | params: { inputFilePath: ${inputFilePath}};`
|
||||
);
|
||||
try {
|
||||
// 読み込みファイルのフルパス
|
||||
const accouncsFileFullPath = inputFilePath + "Account_transition.csv";
|
||||
|
||||
// ファイル存在チェックと読み込み
|
||||
if (!fs.existsSync(accouncsFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${inputFilePath}`);
|
||||
throw new Error(`file not exists from ${inputFilePath}`);
|
||||
}
|
||||
|
||||
// CSVファイルを全行読み込む
|
||||
const inputFile = fs.readFileSync(accouncsFileFullPath, "utf-8");
|
||||
|
||||
// レコードごとに分割
|
||||
const csvInputFileLines = inputFile.split("\n");
|
||||
|
||||
// ヘッダー行を削除
|
||||
csvInputFileLines.shift();
|
||||
|
||||
// 項目ごとに切り分ける
|
||||
let csvInputFile: csvInputFile[] = [];
|
||||
csvInputFileLines.forEach((line) => {
|
||||
// 項目にカンマが入っている場合を考慮して、ダブルクォーテーションで囲まれた部分を一つの項目として扱う
|
||||
const regExp = /"[^"]*"/g;
|
||||
const matchList = line.match(regExp);
|
||||
if (matchList) {
|
||||
matchList.forEach((match) => {
|
||||
// カンマを\に変換
|
||||
const replaced = match.replace(/,/g, "\\");
|
||||
line = line.replace(match, replaced);
|
||||
});
|
||||
}
|
||||
const data = line.split(",");
|
||||
// ダブルクォーテーションを削除
|
||||
data.forEach((value, index) => {
|
||||
data[index] = value.replace(/"/g, "");
|
||||
});
|
||||
// "\r"を削除
|
||||
data[data.length - 1] = data[data.length - 1].replace(/\r/g, "");
|
||||
// dataの要素数が34(csvInputFileの要素数)より多い場合、フォーマット不一致エラー(移行元はworktypeの数が20より多く設定できるので理論上は存在する)
|
||||
// worktypeの数の確認を促すエラーを出す
|
||||
if (data.length > 34) {
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] format error.please check the number of elements in worktype. data=${data}`
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
// data[1]がundefinedの場合、配列には格納しない
|
||||
if (data[1] !== undefined) {
|
||||
// バックスラッシュをカンマに戻す
|
||||
data.forEach((value, index) => {
|
||||
data[index] = value.replace(/\\/g, ",");
|
||||
});
|
||||
csvInputFile.push({
|
||||
type: data[0],
|
||||
account_id: data[1],
|
||||
parent_id: data[2],
|
||||
email: data[3],
|
||||
company_name: data[4],
|
||||
first_name: data[5],
|
||||
last_name: data[6],
|
||||
country: data[7],
|
||||
state: data[8],
|
||||
start_date: data[9],
|
||||
expired_date: data[10],
|
||||
user_email: data[11],
|
||||
author_id: data[12],
|
||||
recording_mode: data[13],
|
||||
wt1: data[14],
|
||||
wt2: data[15],
|
||||
wt3: data[16],
|
||||
wt4: data[17],
|
||||
wt5: data[18],
|
||||
wt6: data[19],
|
||||
wt7: data[20],
|
||||
wt8: data[21],
|
||||
wt9: data[22],
|
||||
wt10: data[23],
|
||||
wt11: data[24],
|
||||
wt12: data[25],
|
||||
wt13: data[26],
|
||||
wt14: data[27],
|
||||
wt15: data[28],
|
||||
wt16: data[29],
|
||||
wt17: data[30],
|
||||
wt18: data[31],
|
||||
wt19: data[32],
|
||||
wt20: data[33],
|
||||
});
|
||||
}
|
||||
});
|
||||
// 各データのバリデーションチェック
|
||||
await this.transferService.validateInputData(context, csvInputFile);
|
||||
|
||||
// account_idを通番に変換し、変換前account_id: 変換後accountId配列を作成する。
|
||||
const accountIdList = csvInputFile.map((line) => line.account_id);
|
||||
const accountIdListSet = new Set(accountIdList);
|
||||
const accountIdListArray = Array.from(accountIdListSet);
|
||||
const accountIdMap = new Map<string, number>();
|
||||
accountIdListArray.forEach((accountId, index) => {
|
||||
accountIdMap.set(accountId, index + AUTO_INCREMENT_START);
|
||||
});
|
||||
|
||||
// アカウントID numberとstring対応表の出力
|
||||
const accountsMappingFiles: AccountsMappingFile[] = [];
|
||||
accountIdMap.forEach((value, key) => {
|
||||
const accountsMappingFile = new AccountsMappingFile();
|
||||
accountsMappingFile.accountIdNumber = value;
|
||||
accountsMappingFile.accountIdText = key;
|
||||
accountsMappingFiles.push(accountsMappingFile);
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
`${inputFilePath}account_map.json`,
|
||||
JSON.stringify(accountsMappingFiles)
|
||||
);
|
||||
|
||||
// CSVファイルの変換
|
||||
const transferResponseCsv = await this.transferService.transferInputData(
|
||||
context,
|
||||
csvInputFile,
|
||||
accountIdMap
|
||||
);
|
||||
|
||||
// countryを除いた階層の再配置
|
||||
const AccountsFileTypeLines = transferResponseCsv.accountsFileTypeLines;
|
||||
const AccountsFile = await this.transferService.relocateHierarchy(
|
||||
context,
|
||||
AccountsFileTypeLines
|
||||
);
|
||||
const UsersFile = transferResponseCsv.usersFileLines;
|
||||
const LicensesFile = transferResponseCsv.licensesFileLines;
|
||||
// メールアドレスの重複を削除
|
||||
const resultDuplicateEmail =
|
||||
await this.transferService.removeDuplicateEmail(
|
||||
context,
|
||||
AccountsFile,
|
||||
UsersFile,
|
||||
LicensesFile
|
||||
);
|
||||
|
||||
// AuthorIDが重複している場合通番を付与する
|
||||
const transferDuplicateAuthorResultUsers =
|
||||
await this.transferService.transferDuplicateAuthor(
|
||||
context,
|
||||
resultDuplicateEmail.accountsFileLines,
|
||||
resultDuplicateEmail.usersFileLines
|
||||
);
|
||||
|
||||
// transferResponseCsvを4つのJSONファイルの出力する(出力先はinputと同じにする)
|
||||
const outputFilePath = body.inputFilePath;
|
||||
const WorktypesFile = transferResponseCsv.worktypesFileLines;
|
||||
this.transferService.outputJsonFile(
|
||||
context,
|
||||
outputFilePath,
|
||||
resultDuplicateEmail.accountsFileLines,
|
||||
transferDuplicateAuthorResultUsers,
|
||||
resultDuplicateEmail.licensesFileLines,
|
||||
WorktypesFile
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.dataRegist.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TransferController } from "./transfer.controller";
|
||||
import { TransferService } from "./transfer.service";
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [TransferController],
|
||||
providers: [TransferService],
|
||||
})
|
||||
export class TransferModule {}
|
||||
@ -0,0 +1,697 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
AccountsFileType,
|
||||
UsersFile,
|
||||
LicensesFile,
|
||||
WorktypesFile,
|
||||
csvInputFile,
|
||||
AccountsFile,
|
||||
} from "../../common/types/types";
|
||||
import {
|
||||
COUNTRY_LIST,
|
||||
MIGRATION_TYPE,
|
||||
TIERS,
|
||||
WORKTYPE_MAX_COUNT,
|
||||
RECORDING_MODE,
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
USER_ROLES,
|
||||
SWITCH_FROM_TYPE,
|
||||
} from "src/constants";
|
||||
import {
|
||||
registInputDataResponse,
|
||||
removeDuplicateEmailResponse,
|
||||
} from "./types/types";
|
||||
import fs from "fs";
|
||||
import { makeErrorResponse } from "src/common/error/makeErrorResponse";
|
||||
|
||||
@Injectable()
|
||||
export class TransferService {
|
||||
constructor() {}
|
||||
private readonly logger = new Logger(TransferService.name);
|
||||
|
||||
/**
|
||||
* Transfer Input Data
|
||||
* @param OutputFilePath: string
|
||||
* @param csvInputFile: csvInputFile[]
|
||||
*/
|
||||
async transferInputData(
|
||||
context: Context,
|
||||
csvInputFile: csvInputFile[],
|
||||
accountIdMap: Map<string, number>
|
||||
): Promise<registInputDataResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.transferInputData.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
let accountsFileTypeLines: AccountsFileType[] = [];
|
||||
let usersFileLines: UsersFile[] = [];
|
||||
let licensesFileLines: LicensesFile[] = [];
|
||||
let worktypesFileLines: WorktypesFile[] = [];
|
||||
let errorArray: string[] = [];
|
||||
let userIdIndex = 0;
|
||||
// authorIdとuserIdの対応関係を保持するMapを定義
|
||||
const authorIdToUserIdMap: Map<string, number> = new Map();
|
||||
|
||||
// countryのリストを生成
|
||||
const countryAccounts = csvInputFile.filter(
|
||||
(item) => item.type === "Country"
|
||||
);
|
||||
|
||||
// csvInputFileを一行読み込みする
|
||||
csvInputFile.forEach((line) => {
|
||||
// typeが"USER"以外の場合、アカウントデータの作成を行う
|
||||
if (line.type !== MIGRATION_TYPE.USER) {
|
||||
// line.countryの値を読み込みCOUNTRY_LISTのlabelからvalueに変換する
|
||||
const country = COUNTRY_LIST.find(
|
||||
(country) => country.label === line.country
|
||||
)?.value;
|
||||
// adminNameの変換(last_name + " "+ first_name)
|
||||
// もしline.last_nameとline.first_nameが存在しない場合、line.admin_mailをnameにする
|
||||
let adminName = line.email;
|
||||
if (line.last_name && line.first_name) {
|
||||
adminName = `${line.last_name} ${line.first_name}`;
|
||||
// スペースが前後に入っている場合があるのでTrimする
|
||||
adminName = adminName.trim();
|
||||
}
|
||||
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
|
||||
// common/password/password.tsのmakePasswordを使用
|
||||
// const autoGeneratedPassword = makePassword();
|
||||
|
||||
// parentAccountIdの設定
|
||||
// parent_idが存在する場合、accountIdMapを参照し、accountIdに変換する
|
||||
let parentAccountId: number | null = null;
|
||||
if (line.parent_id) {
|
||||
parentAccountId = accountIdMap.get(line.parent_id);
|
||||
}
|
||||
// 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する
|
||||
if (parentAccountId === undefined) {
|
||||
errorArray.push(
|
||||
`parent_id is invalid. parent_id=${line.parent_id}`
|
||||
);
|
||||
}
|
||||
|
||||
// userIdIndexをインクリメントする
|
||||
userIdIndex++;
|
||||
|
||||
// AccountsFile配列にPush
|
||||
accountsFileTypeLines.push({
|
||||
// accountIdはaccountIdMapから取得する
|
||||
accountId: accountIdMap.get(line.account_id),
|
||||
type: line.type,
|
||||
companyName: line.company_name,
|
||||
country: country,
|
||||
dealerAccountId: parentAccountId,
|
||||
adminName: adminName,
|
||||
adminMail: line.email,
|
||||
userId: userIdIndex,
|
||||
role: null,
|
||||
authorId: null,
|
||||
});
|
||||
} else {
|
||||
// typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合
|
||||
if (
|
||||
line.type == MIGRATION_TYPE.USER &&
|
||||
!countryAccounts.some(
|
||||
(countryAccount) => countryAccount.account_id === line.account_id
|
||||
)
|
||||
) {
|
||||
// line.author_idが存在する場合のみユーザーデータを作成する
|
||||
if (line.author_id) {
|
||||
// userIdIndexをインクリメントする
|
||||
userIdIndex++;
|
||||
|
||||
// nameの変換
|
||||
// もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする
|
||||
// 存在する場合は、last_name + " " + first_name
|
||||
let name = line.user_email;
|
||||
if (line.last_name && line.first_name) {
|
||||
name = `${line.last_name} ${line.first_name}`;
|
||||
}
|
||||
// UsersFileの作成
|
||||
usersFileLines.push({
|
||||
accountId: accountIdMap.get(line.account_id),
|
||||
userId: userIdIndex,
|
||||
name: name,
|
||||
role: USER_ROLES.AUTHOR,
|
||||
authorId: line.author_id,
|
||||
email: line.user_email,
|
||||
});
|
||||
// authorIdとuserIdの対応関係をマッピング
|
||||
authorIdToUserIdMap.set(line.author_id, userIdIndex);
|
||||
}
|
||||
|
||||
// ライセンスのデータの作成を行う
|
||||
// line.expired_dateが"9999/12/31"で始まるデータの場合はデモライセンスなので登録しない
|
||||
if (!line.expired_date.startsWith("9999/12/31")) {
|
||||
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
|
||||
// されていない場合、statusは"reusable"、allocated_user_idはnull
|
||||
let status: string;
|
||||
let allocated_user_id: number | null;
|
||||
if (line.author_id) {
|
||||
status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
|
||||
allocated_user_id =
|
||||
authorIdToUserIdMap.get(line.author_id) ?? null; // authorIdに対応するuserIdを取得
|
||||
} else {
|
||||
status = LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
allocated_user_id = null;
|
||||
}
|
||||
// LicensesFileの作成
|
||||
licensesFileLines.push({
|
||||
expiry_date: line.expired_date,
|
||||
account_id: accountIdMap.get(line.account_id),
|
||||
type: SWITCH_FROM_TYPE.NONE,
|
||||
status: status,
|
||||
allocated_user_id: allocated_user_id,
|
||||
});
|
||||
}
|
||||
// WorktypesFileの作成
|
||||
// wt1~wt20まで読み込み、account単位で作成する
|
||||
// 作成したWorktypesFileを配列にPush
|
||||
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
|
||||
const wt = `wt${i}`;
|
||||
if (line[wt]) {
|
||||
// account_idで同一のcustom_worktype_idが存在しない場合は、作成する
|
||||
if (
|
||||
!worktypesFileLines.find(
|
||||
(worktype) =>
|
||||
worktype.account_id ===
|
||||
accountIdMap.get(line.account_id) &&
|
||||
worktype.custom_worktype_id === line[wt]
|
||||
)
|
||||
) {
|
||||
worktypesFileLines.push({
|
||||
account_id: accountIdMap.get(line.account_id),
|
||||
custom_worktype_id: line[wt],
|
||||
});
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// エラー配列に値が存在する場合はエラーファイルを出力する
|
||||
if (errorArray.length > 0) {
|
||||
const errorFileJson = JSON.stringify(errorArray);
|
||||
fs.writeFileSync(`error.json`, errorFileJson);
|
||||
throw new HttpException(
|
||||
`errorArray is invalid. errorArray=${errorArray}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
return {
|
||||
accountsFileTypeLines,
|
||||
usersFileLines,
|
||||
licensesFileLines,
|
||||
worktypesFileLines,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.transferInputData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 階層の付け替えを行う
|
||||
* @param accountsFileType: AccountsFileType[]
|
||||
* @returns AccountsFile[]
|
||||
*/
|
||||
async relocateHierarchy(
|
||||
context: Context,
|
||||
accountsFileType: AccountsFileType[]
|
||||
): Promise<AccountsFile[]> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.relocateHierarchy.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
const relocatedAccounts: AccountsFile[] = [];
|
||||
const dealerRecords: Map<number, number> = new Map();
|
||||
|
||||
const countryAccounts = accountsFileType.filter(
|
||||
(item) => item.type === MIGRATION_TYPE.COUNTRY
|
||||
);
|
||||
|
||||
const notCountryAccounts = accountsFileType.filter(
|
||||
(item) => item.type !== MIGRATION_TYPE.COUNTRY
|
||||
);
|
||||
|
||||
notCountryAccounts.forEach((notCountryAccount) => {
|
||||
let assignDealerAccountId = notCountryAccount.dealerAccountId;
|
||||
// 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する
|
||||
for (const countryAccount of countryAccounts) {
|
||||
if (countryAccount.accountId === notCountryAccount.dealerAccountId) {
|
||||
assignDealerAccountId = countryAccount.dealerAccountId;
|
||||
}
|
||||
}
|
||||
|
||||
const assignType = this.getAccountType(notCountryAccount.type);
|
||||
|
||||
const newAccount: AccountsFile = {
|
||||
accountId: notCountryAccount.accountId,
|
||||
type: assignType,
|
||||
companyName: notCountryAccount.companyName,
|
||||
country: notCountryAccount.country,
|
||||
dealerAccountId: assignDealerAccountId,
|
||||
adminName: notCountryAccount.adminName,
|
||||
adminMail: notCountryAccount.adminMail,
|
||||
userId: notCountryAccount.userId,
|
||||
role: notCountryAccount.role,
|
||||
authorId: notCountryAccount.authorId,
|
||||
};
|
||||
relocatedAccounts.push(newAccount);
|
||||
});
|
||||
|
||||
return relocatedAccounts;
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.relocateHierarchy.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// メソッド: アカウントタイプを数値に変換するヘルパー関数
|
||||
private getAccountType(type: string): number {
|
||||
switch (type) {
|
||||
case MIGRATION_TYPE.ADMINISTRATOR:
|
||||
return TIERS.TIER1;
|
||||
case MIGRATION_TYPE.BC:
|
||||
return TIERS.TIER2;
|
||||
case MIGRATION_TYPE.DISTRIBUTOR:
|
||||
return TIERS.TIER3;
|
||||
case MIGRATION_TYPE.DEALER:
|
||||
return TIERS.TIER4;
|
||||
case MIGRATION_TYPE.CUSTOMER:
|
||||
return TIERS.TIER5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* JSONファイルの出力
|
||||
* @param outputFilePath: string
|
||||
* @param accountsFile: AccountsFile[]
|
||||
* @param usersFile: UsersFile[]
|
||||
* @param licensesFile: LicensesFile[]
|
||||
* @param worktypesFile: WorktypesFile[]
|
||||
*/
|
||||
async outputJsonFile(
|
||||
context: Context,
|
||||
outputFilePath: string,
|
||||
accountsFile: AccountsFile[],
|
||||
usersFile: UsersFile[],
|
||||
licensesFile: LicensesFile[],
|
||||
worktypesFile: WorktypesFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.outputJsonFile.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
// JSONファイルの出力を行う
|
||||
// AccountsFile配列の出力
|
||||
const accountsFileJson = JSON.stringify(accountsFile);
|
||||
fs.writeFileSync(`${outputFilePath}accounts.json`, accountsFileJson);
|
||||
// UsersFile
|
||||
const usersFileJson = JSON.stringify(usersFile);
|
||||
fs.writeFileSync(`${outputFilePath}users.json`, usersFileJson);
|
||||
// LicensesFile
|
||||
const licensesFileJson = JSON.stringify(licensesFile);
|
||||
fs.writeFileSync(`${outputFilePath}licenses.json`, licensesFileJson);
|
||||
// WorktypesFile
|
||||
const worktypesFileJson = JSON.stringify(worktypesFile);
|
||||
fs.writeFileSync(`${outputFilePath}worktypes.json`, worktypesFileJson);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.outputJsonFile.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* データのバリデーションチェック
|
||||
* @param csvInputFile: csvInputFile[]
|
||||
*/
|
||||
async validateInputData(
|
||||
context: Context,
|
||||
csvInputFile: csvInputFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.validateInputData.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
// エラー配列を定義
|
||||
let errorArray: string[] = [];
|
||||
// アカウントに対するworktypeのMap配列を作成する
|
||||
const accountWorktypeMap = new Map<string, string[]>();
|
||||
// csvInputFileのバリデーションチェックを行う
|
||||
csvInputFile.forEach((line, index) => {
|
||||
// typeのバリデーションチェック
|
||||
if (
|
||||
line.type !== MIGRATION_TYPE.ADMINISTRATOR &&
|
||||
line.type !== MIGRATION_TYPE.BC &&
|
||||
line.type !== MIGRATION_TYPE.COUNTRY &&
|
||||
line.type !== MIGRATION_TYPE.DISTRIBUTOR &&
|
||||
line.type !== MIGRATION_TYPE.DEALER &&
|
||||
line.type !== MIGRATION_TYPE.CUSTOMER &&
|
||||
line.type !== MIGRATION_TYPE.USER
|
||||
) {
|
||||
throw new HttpException(
|
||||
`type is invalid. index=${index} type=${line.type}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
// typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する
|
||||
if (line.type !== MIGRATION_TYPE.USER) {
|
||||
if (!line.country) {
|
||||
// countryがnullの場合エラー配列に格納する
|
||||
errorArray.push(`country is null. index=${index}`);
|
||||
}
|
||||
}
|
||||
// countryのバリデーションチェック
|
||||
if (line.country) {
|
||||
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
|
||||
throw new HttpException(
|
||||
`country is invalid. index=${index} country=${line.country}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// mailのバリデーションチェック
|
||||
// メールアドレスの形式が正しいかどうかのチェック
|
||||
const mailRegExp =
|
||||
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
|
||||
if (line.email) {
|
||||
if (!mailRegExp.test(line.email)) {
|
||||
throw new HttpException(
|
||||
`email is invalid. index=${index} email=${line.email}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
if (line.user_email) {
|
||||
if (!mailRegExp.test(line.user_email)) {
|
||||
throw new HttpException(
|
||||
`user_email is invalid. index=${index} user_email=${line.email}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// recording_modeの値が存在する場合
|
||||
if (line.recording_mode) {
|
||||
// recording_modeのバリデーションチェック
|
||||
if (
|
||||
line.recording_mode !== RECORDING_MODE.DS2_QP &&
|
||||
line.recording_mode !== RECORDING_MODE.DS2_SP &&
|
||||
line.recording_mode !== RECORDING_MODE.DSS
|
||||
) {
|
||||
throw new HttpException(
|
||||
`recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
// worktypeの1アカウント20件上限チェック
|
||||
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
|
||||
const wt = `wt${i}`;
|
||||
if (line[wt]) {
|
||||
if (accountWorktypeMap.has(line.account_id)) {
|
||||
const worktypes = accountWorktypeMap.get(line.account_id);
|
||||
// 重複している場合はPushしない
|
||||
if (worktypes?.includes(line[wt])) {
|
||||
continue;
|
||||
} else {
|
||||
worktypes?.push(line[wt]);
|
||||
}
|
||||
// 20件を超えたらエラー
|
||||
if (worktypes?.length > WORKTYPE_MAX_COUNT) {
|
||||
throw new HttpException(
|
||||
`worktype is over. index=${index} account_id=${line.account_id}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
} else {
|
||||
accountWorktypeMap.set(line.account_id, [line[wt]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// エラー配列に値が存在する場合はエラーファイルを出力する
|
||||
if (errorArray.length > 0) {
|
||||
const errorFileJson = JSON.stringify(errorArray);
|
||||
fs.writeFileSync(`error.json`, errorFileJson);
|
||||
throw new HttpException(
|
||||
`errorArray is invalid. errorArray=${errorArray}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.validateInputData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* removeDuplicateEmail
|
||||
* @param accountsFileLines: AccountsFile[]
|
||||
* @param usersFileLines: UsersFile[]
|
||||
* @param licensesFileLines: LicensesFile[]
|
||||
* @returns registInputDataResponse
|
||||
*/
|
||||
async removeDuplicateEmail(
|
||||
context: Context,
|
||||
accountsFileLines: AccountsFile[],
|
||||
usersFileLines: UsersFile[],
|
||||
licensesFileLines: LicensesFile[]
|
||||
): Promise<removeDuplicateEmailResponse> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
const newAccountsFileLines: AccountsFile[] = [];
|
||||
const newUsersFileLines: UsersFile[] = [];
|
||||
const newLicensesFileLines: LicensesFile[] = [...licensesFileLines]; // licensesFileLinesを新規配列にコピー
|
||||
|
||||
// accountsFileLinesの行ループ
|
||||
accountsFileLines.forEach((account) => {
|
||||
const duplicateAdminMail = newAccountsFileLines.find(
|
||||
(a) => a.adminMail.toLowerCase() === account.adminMail.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
|
||||
if (duplicateAdminMail) {
|
||||
// 重複がある場合はどちらが取込対象か判断できないのでファイルを出力し、エラーにする
|
||||
const errorFileJson = JSON.stringify(account);
|
||||
fs.writeFileSync(`duplicate_error.json`, errorFileJson);
|
||||
throw new HttpException(
|
||||
`adminMail is duplicate. adminMail=${account.adminMail}`,
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
} else {
|
||||
// 重複がない場合
|
||||
newAccountsFileLines.push(account);
|
||||
}
|
||||
});
|
||||
|
||||
// usersFileLinesの行ループ
|
||||
usersFileLines.forEach((user) => {
|
||||
const duplicateUserEmail = newUsersFileLines.find(
|
||||
(u) => u.email.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
|
||||
if (duplicateUserEmail) {
|
||||
// 重複がある場合
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === duplicateUserEmail.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
// ライセンスの割り当てを解除
|
||||
newLicensesFileLines[index].status =
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
newLicensesFileLines[index].allocated_user_id = null;
|
||||
}
|
||||
} else {
|
||||
// 重複がない場合
|
||||
newUsersFileLines.push(user);
|
||||
}
|
||||
// newAccountsFileLinesとの突合せ
|
||||
const duplicateAdminUserEmail = newAccountsFileLines.find(
|
||||
(a) => a.adminMail.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
|
||||
);
|
||||
// 重複がある場合
|
||||
if (duplicateAdminUserEmail) {
|
||||
// 同一アカウント内での重複の場合
|
||||
const isDuplicateInSameAccount =
|
||||
duplicateAdminUserEmail.accountId === user.accountId;
|
||||
|
||||
if (isDuplicateInSameAccount) {
|
||||
// アカウント管理者にauthorロールを付与する
|
||||
duplicateAdminUserEmail.role = USER_ROLES.AUTHOR;
|
||||
duplicateAdminUserEmail.authorId = user.authorId;
|
||||
|
||||
// アカウントにライセンスが割り当てられているか確認する
|
||||
const isAllocatedLicense = newLicensesFileLines.some(
|
||||
(license) =>
|
||||
license.account_id === duplicateAdminUserEmail.accountId &&
|
||||
license.allocated_user_id === duplicateAdminUserEmail.userId
|
||||
);
|
||||
// 割り当てられていなければアカウントに割り当てる
|
||||
if (!isAllocatedLicense) {
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === user.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
newLicensesFileLines[index].allocated_user_id =
|
||||
duplicateAdminUserEmail.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ユーザーから割り当て解除する
|
||||
const index = newLicensesFileLines.findIndex(
|
||||
(license) =>
|
||||
license.account_id === user.accountId &&
|
||||
license.allocated_user_id === user.userId
|
||||
);
|
||||
if (index !== -1) {
|
||||
// ライセンスの割り当てを解除
|
||||
newLicensesFileLines[index].status =
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE;
|
||||
newLicensesFileLines[index].allocated_user_id = null;
|
||||
}
|
||||
// ユーザーの削除
|
||||
const userIndex = newUsersFileLines.findIndex(
|
||||
(u) => u.userId === user.userId
|
||||
);
|
||||
if (userIndex !== -1) {
|
||||
newUsersFileLines.splice(userIndex, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
accountsFileLines: newAccountsFileLines,
|
||||
usersFileLines: newUsersFileLines,
|
||||
licensesFileLines: newLicensesFileLines,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* transferDuplicateAuthor
|
||||
* @param accountsFileLines: AccountsFile[]
|
||||
* @param usersFileLines: UsersFile[]
|
||||
* @returns UsersFile[]
|
||||
*/
|
||||
async transferDuplicateAuthor(
|
||||
context: Context,
|
||||
accountsFileLines: AccountsFile[],
|
||||
usersFileLines: UsersFile[]
|
||||
): Promise<UsersFile[]> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}`
|
||||
);
|
||||
|
||||
try {
|
||||
const newUsersFileLines: UsersFile[] = [];
|
||||
|
||||
for (const accountsFileLine of accountsFileLines) {
|
||||
let duplicateSequence: number = 2;
|
||||
let authorIdList: String[] = [];
|
||||
|
||||
// メールアドレス重複時はアカウントにもAuthorIdが設定されるので重複チェック用のリストに追加しておく
|
||||
if (accountsFileLine.authorId) {
|
||||
authorIdList.push(accountsFileLine.authorId);
|
||||
}
|
||||
|
||||
const targetaccountUsers = usersFileLines.filter(
|
||||
(item) => item.accountId === accountsFileLine.accountId
|
||||
);
|
||||
|
||||
for (const targetaccountUser of targetaccountUsers) {
|
||||
let assignAuthorId = targetaccountUser.authorId;
|
||||
if (authorIdList.includes(targetaccountUser.authorId)) {
|
||||
// 同じauthorIdがいる場合、自分のauthorIdに連番を付与する
|
||||
assignAuthorId = assignAuthorId + duplicateSequence;
|
||||
duplicateSequence = duplicateSequence + 1;
|
||||
}
|
||||
authorIdList.push(targetaccountUser.authorId);
|
||||
|
||||
// 新しいAuthorIdのユーザに詰め替え
|
||||
const newUser: UsersFile = {
|
||||
accountId: targetaccountUser.accountId,
|
||||
userId: targetaccountUser.userId,
|
||||
name: targetaccountUser.name,
|
||||
role: targetaccountUser.role,
|
||||
authorId: assignAuthorId,
|
||||
email: targetaccountUser.email,
|
||||
};
|
||||
newUsersFileLines.push(newUser);
|
||||
}
|
||||
}
|
||||
|
||||
return newUsersFileLines;
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${
|
||||
this.transferDuplicateAuthor.name
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import {
|
||||
AccountsFile,
|
||||
AccountsFileType,
|
||||
LicensesFile,
|
||||
UsersFile,
|
||||
WorktypesFile,
|
||||
} from "src/common/types/types";
|
||||
|
||||
export class transferRequest {
|
||||
@ApiProperty()
|
||||
inputFilePath: string;
|
||||
}
|
||||
|
||||
export class transferResponse {}
|
||||
|
||||
export class registInputDataResponse {
|
||||
@ApiProperty()
|
||||
accountsFileTypeLines: AccountsFileType[];
|
||||
@ApiProperty()
|
||||
usersFileLines: UsersFile[];
|
||||
@ApiProperty()
|
||||
licensesFileLines: LicensesFile[];
|
||||
@ApiProperty()
|
||||
worktypesFileLines: WorktypesFile[];
|
||||
}
|
||||
|
||||
export class removeDuplicateEmailResponse {
|
||||
@ApiProperty()
|
||||
accountsFileLines: AccountsFile[];
|
||||
@ApiProperty()
|
||||
usersFileLines: UsersFile[];
|
||||
@ApiProperty()
|
||||
licensesFileLines: LicensesFile[];
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Controller, Logger } from "@nestjs/common";
|
||||
import { ApiTags } from "@nestjs/swagger";
|
||||
import { UsersService } from "./users.service";
|
||||
|
||||
@ApiTags("users")
|
||||
@Controller("users")
|
||||
export class UsersController {
|
||||
private readonly logger = new Logger(UsersController.name);
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
|
||||
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
|
||||
import { UsersController } from "./users.controller";
|
||||
import { UsersService } from "./users.service";
|
||||
|
||||
@Module({
|
||||
imports: [UsersRepositoryModule, AdB2cModule],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
310
data_migration_tools/server/src/features/users/users.service.ts
Normal file
310
data_migration_tools/server/src/features/users/users.service.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
import { makePassword } from "../../common/password/password";
|
||||
import {
|
||||
AdB2cService,
|
||||
ConflictError,
|
||||
isConflictError,
|
||||
} from "../../gateways/adb2c/adb2c.service";
|
||||
import {
|
||||
User as EntityUser,
|
||||
newUser,
|
||||
} from "../../repositories/users/entity/user.entity";
|
||||
import { UsersRepositoryService } from "../../repositories/users/users.repository.service";
|
||||
import { MANUAL_RECOVERY_REQUIRED, USER_ROLES } from "../../constants";
|
||||
import { Context } from "../../common/log";
|
||||
import { UserRoles } from "../../common/types/role";
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
private readonly logger = new Logger(UsersService.name);
|
||||
constructor(
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly adB2cService: AdB2cService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates user
|
||||
* @param context
|
||||
* @param name
|
||||
* @param role
|
||||
* @param email
|
||||
* @param autoRenew
|
||||
* @param notification
|
||||
* @param accountId
|
||||
* @param userid
|
||||
* @param [authorId]
|
||||
* @param [encryption]
|
||||
* @param [encryptionPassword]
|
||||
* @param [prompt]
|
||||
* @returns user
|
||||
*/
|
||||
async createUser(
|
||||
context: Context,
|
||||
name: string,
|
||||
role: UserRoles,
|
||||
email: string,
|
||||
autoRenew: boolean,
|
||||
notification: boolean,
|
||||
accountId: number,
|
||||
userid: number,
|
||||
authorId?: string | undefined,
|
||||
encryption?: boolean | undefined,
|
||||
encryptionPassword?: string | undefined,
|
||||
prompt?: boolean | undefined
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` +
|
||||
`role: ${role}, ` +
|
||||
`autoRenew: ${autoRenew}, ` +
|
||||
`notification: ${notification}, ` +
|
||||
`accountId: ${accountId}, ` +
|
||||
`userid: ${userid}, ` +
|
||||
`authorId: ${authorId}, ` +
|
||||
`encryption: ${encryption}, ` +
|
||||
`prompt: ${prompt} };`
|
||||
);
|
||||
|
||||
//authorIdが重複していないかチェックする
|
||||
if (authorId) {
|
||||
let isAuthorIdDuplicated = false;
|
||||
try {
|
||||
isAuthorIdDuplicated = await this.usersRepository.existsAuthorId(
|
||||
context,
|
||||
accountId,
|
||||
authorId
|
||||
);
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
if (isAuthorIdDuplicated) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E010302"),
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
}
|
||||
this.logger.log("ランダムパスワード生成開始");
|
||||
// ランダムなパスワードを生成する
|
||||
const ramdomPassword = makePassword();
|
||||
this.logger.log("ランダムパスワード生成完了");
|
||||
|
||||
//Azure AD B2Cにユーザーを新規登録する
|
||||
let externalUser: { sub: string } | ConflictError;
|
||||
try {
|
||||
this.logger.log(`name=${name}`);
|
||||
// idpにユーザーを作成
|
||||
externalUser = await this.adB2cService.createUser(
|
||||
context,
|
||||
email,
|
||||
ramdomPassword,
|
||||
name
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] create externalUser failed`
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
// メールアドレス重複エラー
|
||||
if (isConflictError(externalUser)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E010301"),
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
//Azure AD B2Cに登録したユーザー情報のID(sub)と受け取った情報を使ってDBにユーザーを登録する
|
||||
let newUser: EntityUser;
|
||||
|
||||
try {
|
||||
//roleに応じてユーザー情報を作成する
|
||||
const newUserInfo = this.createNewUserInfo(
|
||||
context,
|
||||
userid,
|
||||
role,
|
||||
accountId,
|
||||
externalUser.sub,
|
||||
autoRenew,
|
||||
notification,
|
||||
authorId,
|
||||
encryption,
|
||||
encryptionPassword,
|
||||
prompt
|
||||
);
|
||||
// ユーザ作成
|
||||
newUser = await this.usersRepository.createNormalUser(
|
||||
context,
|
||||
newUserInfo
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.error(`[${context.getTrackingId()}]create user failed`);
|
||||
//リカバリー処理
|
||||
//Azure AD B2Cに登録したユーザー情報を削除する
|
||||
await this.deleteB2cUser(externalUser.sub, context);
|
||||
|
||||
switch (e.code) {
|
||||
case "ER_DUP_ENTRY":
|
||||
//AuthorID重複エラー
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E010302"),
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Azure AD B2Cに登録したユーザー情報を削除する
|
||||
// TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
|
||||
private async deleteB2cUser(externalUserId: string, context: Context) {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.deleteB2cUser.name
|
||||
} | params: { externalUserId: ${externalUserId} }`
|
||||
);
|
||||
try {
|
||||
await this.adB2cService.deleteUser(externalUserId, context);
|
||||
this.logger.log(
|
||||
`[${context.getTrackingId()}] delete externalUser: ${externalUserId}`
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
|
||||
this.logger.error(
|
||||
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.deleteB2cUser.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DBに登録したユーザー情報を削除する
|
||||
private async deleteUser(userId: number, context: Context) {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.deleteUser.name
|
||||
} | params: { userId: ${userId} }`
|
||||
);
|
||||
try {
|
||||
await this.usersRepository.deleteNormalUser(context, userId);
|
||||
this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
|
||||
this.logger.error(
|
||||
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}`
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.deleteUser.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// roleを受け取って、roleに応じたnewUserを作成して返却する
|
||||
private createNewUserInfo(
|
||||
context: Context,
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
accountId: number,
|
||||
externalId: string,
|
||||
autoRenew: boolean,
|
||||
notification: boolean,
|
||||
authorId?: string | undefined,
|
||||
encryption?: boolean | undefined,
|
||||
encryptionPassword?: string | undefined,
|
||||
prompt?: boolean | undefined
|
||||
): newUser {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.createNewUserInfo.name
|
||||
} | params: { ` +
|
||||
`id: ${id}, ` +
|
||||
`role: ${role}, ` +
|
||||
`accountId: ${accountId}, ` +
|
||||
`authorId: ${authorId}, ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`autoRenew: ${autoRenew}, ` +
|
||||
`notification: ${notification}, ` +
|
||||
`authorId: ${authorId}, ` +
|
||||
`encryption: ${encryption}, ` +
|
||||
`prompt: ${prompt} };`
|
||||
);
|
||||
try {
|
||||
switch (role) {
|
||||
case USER_ROLES.NONE:
|
||||
case USER_ROLES.TYPIST:
|
||||
return {
|
||||
id,
|
||||
account_id: accountId,
|
||||
external_id: externalId,
|
||||
auto_renew: autoRenew,
|
||||
notification,
|
||||
role,
|
||||
accepted_dpa_version: null,
|
||||
accepted_eula_version: null,
|
||||
accepted_privacy_notice_version: null,
|
||||
encryption: false,
|
||||
encryption_password: null,
|
||||
prompt: false,
|
||||
author_id: null,
|
||||
};
|
||||
case USER_ROLES.AUTHOR:
|
||||
return {
|
||||
id,
|
||||
account_id: accountId,
|
||||
external_id: externalId,
|
||||
auto_renew: autoRenew,
|
||||
notification,
|
||||
role,
|
||||
author_id: authorId ?? null,
|
||||
encryption: encryption ?? false,
|
||||
encryption_password: encryptionPassword ?? null,
|
||||
prompt: prompt ?? false,
|
||||
accepted_dpa_version: null,
|
||||
accepted_eula_version: null,
|
||||
accepted_privacy_notice_version: null,
|
||||
};
|
||||
default:
|
||||
//不正なroleが指定された場合はログを出力してエラーを返す
|
||||
this.logger.error(
|
||||
`[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}`
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
return e;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.createNewUserInfo.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class VerificationRequest {
|
||||
@ApiProperty()
|
||||
inputFilePath: string;
|
||||
}
|
||||
|
||||
export class VerificationResponse {}
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
Logger,
|
||||
HttpException,
|
||||
} from "@nestjs/common";
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
import fs from "fs";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
import { VerificationRequest, VerificationResponse } from "./types/types";
|
||||
import { VerificationService } from "./verification.service";
|
||||
import { makeContext } from "../../common/log";
|
||||
import {
|
||||
csvInputFileWithRow,
|
||||
isAccountsMappingFileArray,
|
||||
isCardLicensesFileArray,
|
||||
isCsvInputFileForValidateArray,
|
||||
} from "../../common/types/types";
|
||||
import * as csv from "csv";
|
||||
|
||||
@ApiTags("verification")
|
||||
@Controller("verification")
|
||||
export class VerificationController {
|
||||
private readonly logger = new Logger(VerificationController.name);
|
||||
constructor(private readonly verificationService: VerificationService) {}
|
||||
|
||||
@Post()
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: VerificationResponse,
|
||||
description: "成功時のレスポンス",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: "想定外のサーバーエラー",
|
||||
})
|
||||
@ApiOperation({ operationId: "dataVerification" })
|
||||
async dataVerification(
|
||||
@Body() body: VerificationRequest,
|
||||
@Req() req: Request
|
||||
): Promise<VerificationResponse> {
|
||||
const context = makeContext("iko", "varification");
|
||||
|
||||
const inputFilePath = body.inputFilePath;
|
||||
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.dataVerification.name
|
||||
} | params: { inputFilePath: ${inputFilePath}};`
|
||||
);
|
||||
|
||||
try {
|
||||
// 読み込みファイルのフルパス
|
||||
const accountTransitionFileFullPath =
|
||||
inputFilePath + "Account_transition.csv";
|
||||
const accountMapFileFullPath = inputFilePath + "account_map.json";
|
||||
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
|
||||
|
||||
// ファイル存在チェックと読み込み
|
||||
if (!fs.existsSync(accountTransitionFileFullPath)) {
|
||||
this.logger.error(
|
||||
`file not exists from ${accountTransitionFileFullPath}`
|
||||
);
|
||||
throw new Error(
|
||||
`file not exists from ${accountTransitionFileFullPath}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(accountMapFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${accountMapFileFullPath}`);
|
||||
throw new Error(`file not exists from ${accountMapFileFullPath}`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(cardLicensesFileFullPath)) {
|
||||
this.logger.error(`file not exists from ${cardLicensesFileFullPath}`);
|
||||
throw new Error(`file not exists from ${cardLicensesFileFullPath}`);
|
||||
}
|
||||
|
||||
// カードライセンスの登録用ファイル読み込み
|
||||
const cardLicensesObject = JSON.parse(
|
||||
fs.readFileSync(cardLicensesFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(cardLicenses)
|
||||
if (!isCardLicensesFileArray(cardLicensesObject)) {
|
||||
throw new Error("input file is not cardLicensesInputFiles");
|
||||
}
|
||||
|
||||
// アカウントIDマッピング用ファイル読み込み
|
||||
const accountsMapObject = JSON.parse(
|
||||
fs.readFileSync(accountMapFileFullPath, "utf8")
|
||||
);
|
||||
|
||||
// 型ガード(accountsMapingFile)
|
||||
if (!isAccountsMappingFileArray(accountsMapObject)) {
|
||||
throw new Error("input file is not accountsMapingFile");
|
||||
}
|
||||
|
||||
// 移行用csvファイルの読み込み(csv parse)
|
||||
fs.createReadStream(accountTransitionFileFullPath).pipe(
|
||||
csv.parse({ columns: true, delimiter: "," }, (err, csvInputFiles) => {
|
||||
// 型ガード(csvInputFile)
|
||||
if (!isCsvInputFileForValidateArray(csvInputFiles)) {
|
||||
throw new Error("input file is not csvInputFile");
|
||||
}
|
||||
|
||||
const csvInputFileswithRows: csvInputFileWithRow[] = [];
|
||||
let rowCount = 2; // csvの何行目かを表す変数。ヘッダ行があるので2から開始
|
||||
for (const csvInputFile of csvInputFiles) {
|
||||
const csvInputFileswithRow: csvInputFileWithRow = {
|
||||
...csvInputFile,
|
||||
row: rowCount
|
||||
};
|
||||
csvInputFileswithRows.push(csvInputFileswithRow);
|
||||
rowCount = rowCount + 1;
|
||||
}
|
||||
this.verificationService.varificationData(
|
||||
context,
|
||||
inputFilePath,
|
||||
csvInputFileswithRows,
|
||||
accountsMapObject,
|
||||
cardLicensesObject
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.dataVerification.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { VerificationController } from "./verification.controller";
|
||||
import { VerificationService } from "./verification.service";
|
||||
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
|
||||
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
|
||||
import { UsersRepositoryModule } from "../../repositories//users/users.repository.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
LicensesRepositoryModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
],
|
||||
controllers: [VerificationController],
|
||||
providers: [VerificationService],
|
||||
})
|
||||
export class VerificationModule {}
|
||||
@ -0,0 +1,747 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { Context } from "../../common/log";
|
||||
import {
|
||||
AccountsMappingFile,
|
||||
CardLicensesFile,
|
||||
csvInputFileWithRow,
|
||||
VerificationResultDetails,
|
||||
} from "../../common/types/types";
|
||||
import {
|
||||
AUTO_INCREMENT_START,
|
||||
MIGRATION_TYPE,
|
||||
COUNTRY_LIST,
|
||||
} from "../../constants/index";
|
||||
|
||||
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
|
||||
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
|
||||
import {
|
||||
License,
|
||||
CardLicense,
|
||||
} from "../../repositories/licenses/entity/license.entity";
|
||||
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
|
||||
import { UsersRepositoryService } from "../../repositories//users/users.repository.service";
|
||||
import { Account } from "src/repositories/accounts/entity/account.entity";
|
||||
import fs from "fs";
|
||||
|
||||
@Injectable()
|
||||
export class VerificationService {
|
||||
constructor(
|
||||
private readonly AccountsRepository: AccountsRepositoryService,
|
||||
private readonly UsersRepository: UsersRepositoryService,
|
||||
private readonly licensesRepository: LicensesRepositoryService
|
||||
) {}
|
||||
private readonly logger = new Logger(VerificationService.name);
|
||||
|
||||
/**
|
||||
* Verification Data
|
||||
* @param inputFilePath: string
|
||||
*/
|
||||
async varificationData(
|
||||
context: Context,
|
||||
inputFilePath: string,
|
||||
csvInputFiles: csvInputFileWithRow[],
|
||||
accountsMappingInputFiles: AccountsMappingFile[],
|
||||
cardlicensesInputFiles: CardLicensesFile[]
|
||||
): Promise<void> {
|
||||
// パラメータ内容が長大なのでログには出さない
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${this.varificationData.name}`
|
||||
);
|
||||
|
||||
// this.logger.log(csvInputFiles);
|
||||
try {
|
||||
// 件数情報の取得
|
||||
this.logger.log(`入力ファイルから件数情報を取得する`);
|
||||
|
||||
const accountFromFile = csvInputFiles.filter(
|
||||
(item) => item.type !== "USER" && item.type !== "Country"
|
||||
);
|
||||
const accountCountFromFile = accountFromFile.length;
|
||||
|
||||
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
|
||||
|
||||
const licensesCountFromFile =
|
||||
csvInputFiles.filter(
|
||||
(item) =>
|
||||
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
|
||||
).length + cardLicensesCountFromFile;
|
||||
|
||||
// 管理ユーザ数のカウント
|
||||
const administratorCountFromFile = accountCountFromFile;
|
||||
|
||||
// 一般ユーザ数のカウント
|
||||
// countryのアカウントに所属するユーザをカウント対象外とする
|
||||
const countryAccountFromFile = csvInputFiles.filter(
|
||||
(item) => item.type === "Country"
|
||||
);
|
||||
|
||||
// USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする
|
||||
const normaluserFromFile = csvInputFiles.filter(
|
||||
(item) =>
|
||||
item.type === "USER" &&
|
||||
item.user_email.length !== 0 &&
|
||||
!countryAccountFromFile.some(
|
||||
(countryItem) => countryItem.account_id === item.account_id
|
||||
)
|
||||
);
|
||||
|
||||
const normaluserCountFromFile = normaluserFromFile.length;
|
||||
|
||||
// ユーザ重複数のカウント
|
||||
let mailAdresses: string[] = [];
|
||||
accountFromFile.forEach((item) => {
|
||||
// メールアドレスの要素を配列に追加
|
||||
if (item.email.length !== 0) {
|
||||
mailAdresses.push(item.email);
|
||||
}
|
||||
});
|
||||
normaluserFromFile.forEach((item) => {
|
||||
// メールアドレスの要素を配列に追加
|
||||
if (item.user_email.length !== 0) {
|
||||
mailAdresses.push(item.user_email);
|
||||
}
|
||||
});
|
||||
|
||||
// 重複する要素を抽出
|
||||
const duplicates: { [key: string]: number } = {};
|
||||
mailAdresses.forEach((str) => {
|
||||
duplicates[str.toLowerCase()] =
|
||||
(duplicates[str.toLowerCase()] || 0) + 1;
|
||||
});
|
||||
|
||||
// 重複する要素と件数を表示
|
||||
let duplicateCount = 0;
|
||||
Object.keys(duplicates).forEach((key) => {
|
||||
const count = duplicates[key];
|
||||
if (count > 1) {
|
||||
// 重複件数をカウント
|
||||
duplicateCount = duplicateCount + (count - 1);
|
||||
//console.log(`${key}が${count}件`);
|
||||
}
|
||||
});
|
||||
const userCountFromFile =
|
||||
administratorCountFromFile + normaluserCountFromFile - duplicateCount;
|
||||
|
||||
this.logger.log(`accountCountFromFile=${accountCountFromFile}`);
|
||||
this.logger.log(`cardLicensesCountFromFile=${cardLicensesCountFromFile}`);
|
||||
this.logger.log(`licensesCountFromFile=${licensesCountFromFile}`);
|
||||
this.logger.log(`userCountFromFile=${userCountFromFile}`);
|
||||
|
||||
// DBから情報を取得する
|
||||
this.logger.log(`DBの情報を取得する`);
|
||||
|
||||
const accounts = await this.AccountsRepository.getAllAccounts(context);
|
||||
const users = await this.UsersRepository.getAllUsers(context);
|
||||
const licenses = await this.licensesRepository.getAllLicenses(context);
|
||||
const cardLicenses = await this.licensesRepository.getAllCardLicense(
|
||||
context
|
||||
);
|
||||
|
||||
// DB件数のカウント
|
||||
this.logger.log(`DBの情報から件数を取得する`);
|
||||
const accountsCountFromDB = accounts.length;
|
||||
const usersCountFromDB = users.length;
|
||||
const licensesCountFromDB = licenses.length;
|
||||
const cardLicensesCountFromDB = cardLicenses.length;
|
||||
|
||||
this.logger.log(`accountsCountFromDB=${accountsCountFromDB}`);
|
||||
this.logger.log(`usersCountFromDB=${usersCountFromDB}`);
|
||||
this.logger.log(`licensesCountFromDB=${licensesCountFromDB}`);
|
||||
this.logger.log(`cardLicensesCountFromDB=${cardLicensesCountFromDB}`);
|
||||
|
||||
// エラー情報の定義
|
||||
const VerificationResultDetails: VerificationResultDetails[] = [];
|
||||
|
||||
// カードライセンス関連の情報突き合わせ
|
||||
this.logger.log(`カードライセンス関連の情報突き合わせ`);
|
||||
const isCardDetailNoError = compareCardLicenses(
|
||||
VerificationResultDetails,
|
||||
cardlicensesInputFiles,
|
||||
cardLicenses,
|
||||
licenses
|
||||
);
|
||||
|
||||
// ライセンス関連の情報突き合わせ
|
||||
this.logger.log(`ライセンス関連の情報突き合わせ`);
|
||||
const isLicensesDetailNoError = compareLicenses(
|
||||
VerificationResultDetails,
|
||||
csvInputFiles.filter(
|
||||
(item) =>
|
||||
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
|
||||
),
|
||||
licenses.filter((item) => item.expiry_date !== null),
|
||||
accountsMappingInputFiles
|
||||
);
|
||||
|
||||
// アカウント情報の突き合わせ
|
||||
this.logger.log(`アカウント関連の情報突き合わせ`);
|
||||
const isAccountsDetailNoError = compareAccounts(
|
||||
VerificationResultDetails,
|
||||
csvInputFiles.filter(
|
||||
(item) => item.type !== "USER" && item.type !== "Country"
|
||||
),
|
||||
csvInputFiles.filter((item) => item.type === "Country"),
|
||||
accounts,
|
||||
accountsMappingInputFiles
|
||||
);
|
||||
|
||||
// 結果の判定と出力
|
||||
this.logger.log(`結果の判定と出力`);
|
||||
const isAccountCountNoDifference =
|
||||
accountCountFromFile === accountsCountFromDB;
|
||||
const isUsersCountNoDifference = userCountFromFile === usersCountFromDB;
|
||||
const isLicensesCountNoDifference =
|
||||
licensesCountFromFile === licensesCountFromDB;
|
||||
const isCardLicensesCountNoDifference =
|
||||
cardLicensesCountFromFile === cardLicensesCountFromDB;
|
||||
const isNoDetailError = VerificationResultDetails.length === 0;
|
||||
|
||||
const isSummaryNoError =
|
||||
isAccountCountNoDifference &&
|
||||
isUsersCountNoDifference &&
|
||||
isLicensesCountNoDifference &&
|
||||
isCardLicensesCountNoDifference &&
|
||||
isNoDetailError;
|
||||
|
||||
const summaryString = `
|
||||
サマリファイル:
|
||||
|
||||
比較結果:${isSummaryNoError ? "OK" : "NG"}
|
||||
|
||||
件数:
|
||||
アカウント:${
|
||||
isAccountCountNoDifference ? "OK" : "NG"
|
||||
}(csv件数:${accountCountFromFile}/DB件数:${accountsCountFromDB})
|
||||
ライセンス:${
|
||||
isLicensesCountNoDifference ? "OK" : "NG"
|
||||
}(csv件数:${licensesCountFromFile}/DB件数:${licensesCountFromDB})
|
||||
カードライセンス:${
|
||||
isCardLicensesCountNoDifference ? "OK" : "NG"
|
||||
}(csv件数:${cardLicensesCountFromFile}/DB件数:${cardLicensesCountFromDB})
|
||||
ユーザー:${
|
||||
isUsersCountNoDifference ? "OK" : "NG"
|
||||
}(csv件数:${userCountFromFile}/DB件数:${usersCountFromDB})
|
||||
|
||||
項目比較:
|
||||
アカウント:${isAccountsDetailNoError ? "OK" : "NG"}
|
||||
カードライセンス:${isCardDetailNoError ? "OK" : "NG"}
|
||||
ライセンス:${isLicensesDetailNoError ? "OK" : "NG"}
|
||||
`;
|
||||
|
||||
// サマリファイルの書き込み
|
||||
fs.writeFileSync(`${inputFilePath}resultsummary.txt`, summaryString);
|
||||
|
||||
// 詳細ファイルの書き込み
|
||||
// 配列をJSON文字列に変換
|
||||
const jsonContent = JSON.stringify(VerificationResultDetails, null, 2);
|
||||
|
||||
// JSONをファイルに書き込み
|
||||
fs.writeFileSync(`${inputFilePath}resultdetail.json`, jsonContent);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse("E009999"),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.varificationData.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dateを任意のフォーマットに変換する
|
||||
const getFormattedDate = (
|
||||
date: Date | null,
|
||||
format: string,
|
||||
padHours: boolean = false // trueの場合、hhについてゼロパディングする(00→0、01→1、23→23)
|
||||
) => {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
const symbol = {
|
||||
M: date.getMonth() + 1,
|
||||
d: date.getDate(),
|
||||
h: date.getHours(),
|
||||
m: date.getMinutes(),
|
||||
s: date.getSeconds(),
|
||||
};
|
||||
|
||||
// hhの値をゼロパディングするかどうかのフラグを確認
|
||||
const hourSymbol = padHours ? "hh" : "h";
|
||||
|
||||
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
|
||||
(
|
||||
(v.length > 1 && v !== hourSymbol ? "0" : "") +
|
||||
symbol[v.slice(-1) as keyof typeof symbol]
|
||||
).slice(-2)
|
||||
);
|
||||
|
||||
return formatted.replace(/(y+)/g, (v) =>
|
||||
date.getFullYear().toString().slice(-v.length)
|
||||
);
|
||||
};
|
||||
|
||||
// 親の階層がcountryの場合、countryの親を返却する
|
||||
function transrateCountryHierarchy(
|
||||
countriesFromFile: csvInputFileWithRow[],
|
||||
targetParentAccountIdString: string
|
||||
): string {
|
||||
for (const countryFromFile of countriesFromFile) {
|
||||
if (countryFromFile.account_id === targetParentAccountIdString) {
|
||||
return countryFromFile.parent_id;
|
||||
}
|
||||
}
|
||||
return targetParentAccountIdString;
|
||||
}
|
||||
|
||||
// アカウントID(number)を対応するアカウントID(string)に変換する
|
||||
function findAccountIdText(
|
||||
accountsMappings: AccountsMappingFile[],
|
||||
targetAccountIdNumber: number
|
||||
): string {
|
||||
if (targetAccountIdNumber == null) {
|
||||
return "";
|
||||
}
|
||||
for (const accountsMapping of accountsMappings) {
|
||||
if (accountsMapping.accountIdNumber === targetAccountIdNumber) {
|
||||
return accountsMapping.accountIdText;
|
||||
}
|
||||
}
|
||||
return `NO_MATCHED_ACCOUNTID_${targetAccountIdNumber}`; // マッチするものが見つからない場合
|
||||
}
|
||||
|
||||
// 階層(number)を対応する階層(string)に変換する
|
||||
function getMigrationTypeByNumber(numberValue: number): string {
|
||||
switch (numberValue) {
|
||||
case 1:
|
||||
return MIGRATION_TYPE.ADMINISTRATOR;
|
||||
case 2:
|
||||
return MIGRATION_TYPE.BC;
|
||||
case 3:
|
||||
return MIGRATION_TYPE.DISTRIBUTOR;
|
||||
case 4:
|
||||
return MIGRATION_TYPE.DEALER;
|
||||
case 5:
|
||||
return MIGRATION_TYPE.CUSTOMER;
|
||||
default:
|
||||
return `NO_MATCHED_TIER_${numberValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 国(省略版)を対応する国(非省略版)に変換する
|
||||
function getCountryLabelByValue(value: string): string {
|
||||
const country = COUNTRY_LIST.find((country) => country.value === value);
|
||||
return country ? country.label : `NO_MATCHED_COUNTRY_${value}`;
|
||||
}
|
||||
|
||||
// カードライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
|
||||
function compareCardLicenses(
|
||||
VerificationResultDetails: VerificationResultDetails[],
|
||||
cardlicensesInputFiles: CardLicensesFile[],
|
||||
cardLicenses: CardLicense[],
|
||||
licenses: License[]
|
||||
): boolean {
|
||||
let isNoError = true;
|
||||
|
||||
let row = 1; // カードライセンスファイルの行数
|
||||
for (const cardlicensesInputFile of cardlicensesInputFiles) {
|
||||
const filterdCardLicenses = cardLicenses.filter(
|
||||
(cardLicenses) =>
|
||||
cardLicenses.card_license_key === cardlicensesInputFile.card_license_key
|
||||
);
|
||||
|
||||
if (filterdCardLicenses.length === 0) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "cardLicenses",
|
||||
columnName: "card_license_key",
|
||||
fileData: cardlicensesInputFile.card_license_key,
|
||||
databaseData: "-",
|
||||
reason: "レコード無し",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* issue_idは自動採番のため比較しない
|
||||
if (cardlicensesInputFile.issue_id !== filterdCardLicenses[0].issue_id) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
diffTargetTable: "cardLicenses",
|
||||
columnName: "issue_id",
|
||||
fileData: cardlicensesInputFile.issue_id.toString(),
|
||||
databaseData: filterdCardLicenses[0].issue_id.toString(),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
*/
|
||||
|
||||
const formattedFileActivated = getFormattedDate(
|
||||
cardlicensesInputFile.activated_at
|
||||
? new Date(cardlicensesInputFile.activated_at)
|
||||
: null,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
);
|
||||
const formattedDbActivated = getFormattedDate(
|
||||
filterdCardLicenses[0].activated_at,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
);
|
||||
if (formattedFileActivated !== formattedDbActivated) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "cardLicenses",
|
||||
columnName: "activated_at",
|
||||
fileData: formattedFileActivated,
|
||||
databaseData: formattedDbActivated,
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
|
||||
const filterdLicenses = licenses.filter(
|
||||
(licenses) => licenses.id === filterdCardLicenses[0].license_id
|
||||
);
|
||||
if (filterdLicenses.length === 0) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "id",
|
||||
fileData: filterdCardLicenses[0].license_id.toString(),
|
||||
databaseData: "-",
|
||||
reason: "紐つくライセンスのレコード無し",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filterdLicenses[0].expiry_date !== null) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "expiry_date",
|
||||
fileData: null,
|
||||
databaseData: getFormattedDate(
|
||||
filterdLicenses[0].expiry_date,
|
||||
`yyyy/MM/dd hh:mm:ss`
|
||||
),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
|
||||
if (filterdLicenses[0].account_id !== AUTO_INCREMENT_START) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "account_id",
|
||||
fileData: AUTO_INCREMENT_START.toString(),
|
||||
databaseData: filterdLicenses[0].account_id.toString(),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
|
||||
if (filterdLicenses[0].type !== "CARD") {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "type",
|
||||
fileData: "CARD",
|
||||
databaseData: filterdLicenses[0].type,
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
|
||||
if (filterdLicenses[0].status !== "Unallocated") {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "status",
|
||||
fileData: "Unallocated",
|
||||
databaseData: filterdLicenses[0].status,
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
if (filterdLicenses[0].allocated_user_id !== null) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "allocated_user_id",
|
||||
fileData: null,
|
||||
databaseData: filterdLicenses[0].allocated_user_id.toString(),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
if (filterdLicenses[0].order_id !== null) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "order_id",
|
||||
fileData: null,
|
||||
databaseData: filterdLicenses[0].order_id.toString(),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
if (filterdLicenses[0].deleted_at !== null) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "deleted_at",
|
||||
fileData: null,
|
||||
databaseData: getFormattedDate(
|
||||
filterdLicenses[0].deleted_at,
|
||||
`yyyy/MM/dd hh:mm:ss`
|
||||
),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
if (filterdLicenses[0].delete_order_id !== null) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "cardLicenses",
|
||||
inputRow: row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "delete_order_id",
|
||||
fileData: null,
|
||||
databaseData: filterdLicenses[0].delete_order_id.toString(),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
row = row + 1;
|
||||
}
|
||||
return isNoError;
|
||||
}
|
||||
|
||||
// ライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
|
||||
function compareLicenses(
|
||||
VerificationResultDetails: VerificationResultDetails[],
|
||||
licensesFromFile: csvInputFileWithRow[],
|
||||
licensesFromDatabase: License[],
|
||||
accountsMappingInputFiles: AccountsMappingFile[]
|
||||
): boolean {
|
||||
let isNoError = true;
|
||||
for (let i = 0; i < licensesFromFile.length; i++) {
|
||||
if (
|
||||
!licensesFromDatabase[i] ||
|
||||
licensesFromFile[i].account_id !==
|
||||
findAccountIdText(
|
||||
accountsMappingInputFiles,
|
||||
licensesFromDatabase[i].account_id
|
||||
)
|
||||
) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: licensesFromFile[i].row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "account_id",
|
||||
fileData: licensesFromFile[i].account_id,
|
||||
databaseData: licensesFromDatabase[i]
|
||||
? findAccountIdText(
|
||||
accountsMappingInputFiles,
|
||||
licensesFromDatabase[i].account_id
|
||||
) + `(${licensesFromDatabase[i].account_id})`
|
||||
: "undifined",
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
|
||||
// expiry_dateについて、時はゼロパディングした値で比較する(×01~09 ○1~9)
|
||||
if (
|
||||
!licensesFromDatabase[i] ||
|
||||
getFormattedDate(
|
||||
licensesFromFile[i].expired_date
|
||||
? new Date(licensesFromFile[i].expired_date)
|
||||
: null,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
) !==
|
||||
getFormattedDate(
|
||||
licensesFromDatabase[i].expiry_date,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
)
|
||||
) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: licensesFromFile[i].row,
|
||||
diffTargetTable: "licenses",
|
||||
columnName: "expired_date",
|
||||
fileData: getFormattedDate(
|
||||
licensesFromFile[i].expired_date
|
||||
? new Date(licensesFromFile[i].expired_date)
|
||||
: null,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
),
|
||||
databaseData: licensesFromDatabase[i]
|
||||
? getFormattedDate(
|
||||
licensesFromDatabase[i].expiry_date,
|
||||
`yyyy/MM/dd hh:mm:ss`,
|
||||
true
|
||||
)
|
||||
: "undifined",
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
}
|
||||
}
|
||||
return isNoError;
|
||||
}
|
||||
|
||||
// アカウント情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
|
||||
function compareAccounts(
|
||||
VerificationResultDetails: VerificationResultDetails[],
|
||||
accountsFromFile: csvInputFileWithRow[],
|
||||
countriesFromFile: csvInputFileWithRow[],
|
||||
accountsFromDatabase: Account[],
|
||||
accountsMappingInputFiles: AccountsMappingFile[]
|
||||
): boolean {
|
||||
let isNoError = true;
|
||||
for (const accountFromFile of accountsFromFile) {
|
||||
// DBレコードの存在チェック
|
||||
const filterdAccounts = accountsFromDatabase.filter(
|
||||
(accountsFromDatabase) =>
|
||||
findAccountIdText(
|
||||
accountsMappingInputFiles,
|
||||
accountsFromDatabase.id
|
||||
) === accountFromFile.account_id
|
||||
);
|
||||
|
||||
if (filterdAccounts.length === 0) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: accountFromFile.row,
|
||||
diffTargetTable: "accounts",
|
||||
columnName: "account_id",
|
||||
fileData: accountFromFile.account_id,
|
||||
databaseData: "-",
|
||||
reason: "レコード無し",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 項目チェック(parent_account_id)
|
||||
const transratedParentId = transrateCountryHierarchy(
|
||||
countriesFromFile,
|
||||
accountFromFile.parent_id
|
||||
);
|
||||
if (
|
||||
transratedParentId !==
|
||||
findAccountIdText(
|
||||
accountsMappingInputFiles,
|
||||
filterdAccounts[0].parent_account_id
|
||||
)
|
||||
) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: accountFromFile.row,
|
||||
diffTargetTable: "accounts",
|
||||
columnName: "parent_account_id",
|
||||
fileData:
|
||||
transratedParentId === accountFromFile.parent_id
|
||||
? accountFromFile.parent_id
|
||||
: `${transratedParentId}(${accountFromFile.parent_id})`,
|
||||
databaseData:
|
||||
findAccountIdText(
|
||||
accountsMappingInputFiles,
|
||||
filterdAccounts[0].parent_account_id
|
||||
) + `(${filterdAccounts[0].parent_account_id})`,
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 項目チェック(tier)
|
||||
if (
|
||||
accountFromFile.type !== getMigrationTypeByNumber(filterdAccounts[0].tier)
|
||||
) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: accountFromFile.row,
|
||||
diffTargetTable: "accounts",
|
||||
columnName: "tier",
|
||||
fileData: accountFromFile.type,
|
||||
databaseData: getMigrationTypeByNumber(filterdAccounts[0].tier),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 項目チェック(country)
|
||||
if (
|
||||
accountFromFile.country !==
|
||||
getCountryLabelByValue(filterdAccounts[0].country)
|
||||
) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: accountFromFile.row,
|
||||
diffTargetTable: "accounts",
|
||||
columnName: "country",
|
||||
fileData: accountFromFile.country,
|
||||
databaseData: getCountryLabelByValue(filterdAccounts[0].country),
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 項目チェック(company_name)
|
||||
if (accountFromFile.company_name !== filterdAccounts[0].company_name) {
|
||||
const VerificationResultDetailsOne: VerificationResultDetails = {
|
||||
input: "Account_transition",
|
||||
inputRow: accountFromFile.row,
|
||||
diffTargetTable: "accounts",
|
||||
columnName: "company_name",
|
||||
fileData: accountFromFile.company_name,
|
||||
databaseData: filterdAccounts[0].company_name,
|
||||
reason: "内容不一致",
|
||||
};
|
||||
VerificationResultDetails.push(VerificationResultDetailsOne);
|
||||
isNoError = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return isNoError;
|
||||
}
|
||||
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