From 4bedc9ec951fcfd3f3a762dcc1af10163a96b808 Mon Sep 17 00:00:00 2001 From: masaaki Date: Wed, 25 Oct 2023 06:17:01 +0000 Subject: [PATCH 1/8] =?UTF-8?q?Merged=20PR=20526:=20=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF2898=EF=BC=9A=E3=83=AA=E3=83=95=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3=E6=88=BB=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2933: タスク2898:リフレッシュトークン処理の修正戻し](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2933) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - タスク2898で実施した比較演算子の修正について、バグの恒久対応が行われたので厳密等価演算子に戻しました。 ## レビューポイント - 特にありません ## UIの変更 - 無し ## 動作確認状況 - unittest + ローカル動作確認済 ## 補足 - 相談、参考資料などがあれば --- dictation_server/src/features/auth/auth.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index ca0b032..1454960 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -142,8 +142,8 @@ export class AuthService { { //ユーザーの属しているアカウントの管理者にユーザーが設定されていればadminをセットする role: `${role} ${ - user.account.primary_admin_user_id == user.id || - user.account.secondary_admin_user_id == user.id + user.account.primary_admin_user_id === user.id || + user.account.secondary_admin_user_id === user.id ? ADMIN_ROLES.ADMIN : ADMIN_ROLES.STANDARD }`, From 855cfbf34770487707ad040c16243e88a95416a6 Mon Sep 17 00:00:00 2001 From: masaaki Date: Wed, 25 Oct 2023 06:44:59 +0000 Subject: [PATCH 2/8] =?UTF-8?q?Merged=20PR=20527:=20terms=E5=88=9D?= =?UTF-8?q?=E6=9C=9F=E5=80=A4=E7=94=A8=E3=81=AEmigrate=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=8C=E8=AA=A4=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2901: terms初期値用のmigrateファイルが誤っていたのを修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2901) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - termsテーブルの初期値について、ドキュメントタイプが「EURA」で登録していたので「EULA」となるように修正しました ## レビューポイント - 特になし ## UIの変更 - 無し ## 動作確認状況 - migrate upおよびdown実施+ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../db/migrations/047-modify_initial_data_terms.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 dictation_server/db/migrations/047-modify_initial_data_terms.sql diff --git a/dictation_server/db/migrations/047-modify_initial_data_terms.sql b/dictation_server/db/migrations/047-modify_initial_data_terms.sql new file mode 100644 index 0000000..60bad4f --- /dev/null +++ b/dictation_server/db/migrations/047-modify_initial_data_terms.sql @@ -0,0 +1,8 @@ +-- +migrate Up +delete from terms where terms.document_type = 'EURA' and terms.version = 'V0.1'; +insert into terms(terms.document_type, terms.version) values('EULA', 'V0.1'); +commit; + +-- +migrate Down +delete from terms where terms.document_type = 'EULA' and terms.version = 'V0.1'; +commit; From 911d028073e77b9c5f56b564c995ea7c123f75fe Mon Sep 17 00:00:00 2001 From: "oura.a" Date: Wed, 25 Oct 2023 07:03:48 +0000 Subject: [PATCH 3/8] =?UTF-8?q?Merged=20PR=20510:=20[sprint20=E5=AE=8C?= =?UTF-8?q?=E4=BA=86]=E6=96=B0=E8=A6=8FDocker=E3=82=B3=E3=83=B3=E3=83=86?= =?UTF-8?q?=E3=83=8A=E3=81=AE=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2864: [sprint20完了]新規Dockerコンテナの作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2864) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば --- dictation_function/.dockerignore | 1 + dictation_function/.funcignore | 10 + dictation_function/.gitignore | 48 + dictation_function/.vscode/extensions.json | 5 + dictation_function/Dockerfile | 21 + dictation_function/docker-compose.yml | 20 + dictation_function/host.json | 15 + dictation_function/package-lock.json | 936 ++++++++++++++++++ dictation_function/package.json | 23 + .../src/functions/HttpExample.ts | 15 + dictation_function/tsconfig.json | 10 + 11 files changed, 1104 insertions(+) create mode 100644 dictation_function/.dockerignore create mode 100644 dictation_function/.funcignore create mode 100644 dictation_function/.gitignore create mode 100644 dictation_function/.vscode/extensions.json create mode 100644 dictation_function/Dockerfile create mode 100644 dictation_function/docker-compose.yml create mode 100644 dictation_function/host.json create mode 100644 dictation_function/package-lock.json create mode 100644 dictation_function/package.json create mode 100644 dictation_function/src/functions/HttpExample.ts create mode 100644 dictation_function/tsconfig.json diff --git a/dictation_function/.dockerignore b/dictation_function/.dockerignore new file mode 100644 index 0000000..1927772 --- /dev/null +++ b/dictation_function/.dockerignore @@ -0,0 +1 @@ +local.settings.json \ No newline at end of file diff --git a/dictation_function/.funcignore b/dictation_function/.funcignore new file mode 100644 index 0000000..17bd0f6 --- /dev/null +++ b/dictation_function/.funcignore @@ -0,0 +1,10 @@ +*.js.map +*.ts +.git* +.vscode +local.settings.json +test +getting_started.md +node_modules/@types/ +node_modules/azure-functions-core-tools/ +node_modules/typescript/ \ No newline at end of file diff --git a/dictation_function/.gitignore b/dictation_function/.gitignore new file mode 100644 index 0000000..f15ac3f --- /dev/null +++ b/dictation_function/.gitignore @@ -0,0 +1,48 @@ +bin +obj +csx +.vs +edge +Publish + +*.user +*.suo +*.cscfg +*.Cache +project.lock.json + +/packages +/TestResults + +/tools/NuGet.exe +/App_Data +/secrets +/data +.secrets +appsettings.json +local.settings.json + +node_modules +dist + +# Local python packages +.python_packages/ + +# Python Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Azurite artifacts +__blobstorage__ +__queuestorage__ +__azurite_db*__.json \ No newline at end of file diff --git a/dictation_function/.vscode/extensions.json b/dictation_function/.vscode/extensions.json new file mode 100644 index 0000000..dde673d --- /dev/null +++ b/dictation_function/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/dictation_function/Dockerfile b/dictation_function/Dockerfile new file mode 100644 index 0000000..1d971e3 --- /dev/null +++ b/dictation_function/Dockerfile @@ -0,0 +1,21 @@ +# To enable ssh & remote debugging on app service change the base image to the one below +# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice +FROM mcr.microsoft.com/azure-functions/node:4-node18 + +RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \ + echo "Asia/Tokyo" > /etc/timezone + +ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ + AzureFunctionsJobHost__Logging__Console__IsEnabled=true + +RUN apt-get -y update +RUN apt-get install -y \ + curl \ + gnupg +RUN apt-get install -y nodejs + +WORKDIR /home/site/wwwroot +RUN cd /home/site/wwwroot +COPY ./ /home/site/wwwroot + +RUN npm install -g npm \ No newline at end of file diff --git a/dictation_function/docker-compose.yml b/dictation_function/docker-compose.yml new file mode 100644 index 0000000..5cb6e06 --- /dev/null +++ b/dictation_function/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3" + +services: + dictation_function: + build: . + working_dir: /app/dictation_function + ports: + - "3001:3001" + - "6007:6007" + volumes: + - ../../:/app + - node_modules:/app/dictation_function/node_modules + expose: + - "8082" + environment: + - CHOKIDAR_USEPOLLING=true + +# Data Volume として永続化する +volumes: + node_modules: diff --git a/dictation_function/host.json b/dictation_function/host.json new file mode 100644 index 0000000..9df9136 --- /dev/null +++ b/dictation_function/host.json @@ -0,0 +1,15 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + }, + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} \ No newline at end of file diff --git a/dictation_function/package-lock.json b/dictation_function/package-lock.json new file mode 100644 index 0000000..6c1d2a9 --- /dev/null +++ b/dictation_function/package-lock.json @@ -0,0 +1,936 @@ +{ + "name": "dictation_function", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "version": "1.0.0", + "dependencies": { + "@azure/functions": "^4.0.0-alpha.9" + }, + "devDependencies": { + "@types/node": "18.x", + "azure-functions-core-tools": "^4.x", + "rimraf": "^5.0.0", + "typescript": "^4.0.0" + } + }, + "node_modules/@azure/functions": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@azure/functions/-/functions-4.0.1.tgz", + "integrity": "sha512-Ol38b4XOlu6IDkLnO91HaYeo2utMixG0LIA1NR9Qehu17U/cGjNx+bAcOEdNlSJWNYh5ChhzjxA/uFB5dQJtmg==", + "dependencies": { + "long": "^4.0.0", + "undici": "^5.13.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/node": { + "version": "18.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz", + "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/azure-functions-core-tools": { + "version": "4.0.5413", + "resolved": "https://registry.npmjs.org/azure-functions-core-tools/-/azure-functions-core-tools-4.0.5413.tgz", + "integrity": "sha512-jHfw7JrfH5RZEQni098Bf1HZorIzLlQH8aoNNKYwmhF6mvh5J6aTqKyDSJKUbUN2/xqS9vxGYGpKzqv4US3dcA==", + "dev": true, + "hasInstallScript": true, + "hasShrinkwrap": true, + "os": [ + "win32", + "darwin", + "linux" + ], + "dependencies": { + "chalk": "3.0.0", + "extract-zip": "^2.0.1", + "https-proxy-agent": "5.0.0", + "progress": "2.0.3", + "rimraf": "3.0.2" + }, + "bin": { + "azfun": "lib/main.js", + "azurefunctions": "lib/main.js", + "func": "lib/main.js" + }, + "engines": { + "node": ">=6.9.1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", + "dev": true, + "optional": true + }, + "node_modules/azure-functions-core-tools/node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/azure-functions-core-tools/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/ansi-styles": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", + "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/azure-functions-core-tools/node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/azure-functions-core-tools/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/azure-functions-core-tools/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/azure-functions-core-tools/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/azure-functions-core-tools/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/azure-functions-core-tools/node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/azure-functions-core-tools/node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/azure-functions-core-tools/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/azure-functions-core-tools/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/azure-functions-core-tools/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/azure-functions-core-tools/node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/azure-functions-core-tools/node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/azure-functions-core-tools/node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici": { + "version": "5.26.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.4.tgz", + "integrity": "sha512-OG+QOf0fTLtazL9P9X7yqWxQ+Z0395Wk6DSkyTxtaq3wQEjIroVe7Y4asCX/vcCxYpNGMnwz8F0qbRYUoaQVMw==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/dictation_function/package.json b/dictation_function/package.json new file mode 100644 index 0000000..02efe21 --- /dev/null +++ b/dictation_function/package.json @@ -0,0 +1,23 @@ +{ + "name": "", + "version": "1.0.0", + "description": "", + "main": "dist/src/functions/*.js", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "clean": "rimraf dist", + "prestart": "npm run clean && npm run build", + "start": "func start", + "test": "echo \"No tests yet...\"" + }, + "dependencies": { + "@azure/functions": "^4.0.0-alpha.9" + }, + "devDependencies": { + "azure-functions-core-tools": "^4.x", + "@types/node": "18.x", + "typescript": "^4.0.0", + "rimraf": "^5.0.0" + } +} \ No newline at end of file diff --git a/dictation_function/src/functions/HttpExample.ts b/dictation_function/src/functions/HttpExample.ts new file mode 100644 index 0000000..3d6b80d --- /dev/null +++ b/dictation_function/src/functions/HttpExample.ts @@ -0,0 +1,15 @@ +import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; + +export async function HttpExample(request: HttpRequest, context: InvocationContext): Promise { + context.log(`Http function processed request for url "${request.url}"`); + + const name = request.query.get('name') || await request.text() || 'world'; + + return { body: `Hello, ${name}!` }; +}; + +app.http('HttpExample', { + methods: ['GET', 'POST'], + authLevel: 'anonymous', + handler: HttpExample +}); diff --git a/dictation_function/tsconfig.json b/dictation_function/tsconfig.json new file mode 100644 index 0000000..fe1d761 --- /dev/null +++ b/dictation_function/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "dist", + "rootDir": ".", + "sourceMap": true, + "strict": false + } +} \ No newline at end of file From c283df9b0ac0da4d89422f08a04568f9918e6c2f Mon Sep 17 00:00:00 2001 From: "maruyama.t" Date: Wed, 25 Oct 2023 08:28:57 +0000 Subject: [PATCH 4/8] =?UTF-8?q?Merged=20PR=20521:=20API=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E3=83=A6=E3=83=BC=E3=82=B6=E5=90=8D=E5=8F=96=E5=BE=97?= =?UTF-8?q?API=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2924: API実装(ユーザ名取得API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2924) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) 新規のため、なし ## レビューポイント ユーザー存在チェックを行う必要性はあるか? ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/features/users/users.controller.ts | 3 +- .../src/features/users/users.service.spec.ts | 42 +++++++++++++++++++ .../src/features/users/users.service.ts | 39 ++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index c9d6a56..cee1a78 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -662,8 +662,7 @@ export class UsersController { } const { userId } = decodedAccessToken as AccessToken; const context = makeContext(userId); - const userName = 'TEST'; - //const userName = await this.usersService.getUserName(context, userId); + const userName = await this.usersService.getUserName(context, userId); return { userName }; } } diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 6c5c2b4..e749058 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -2624,3 +2624,45 @@ describe('UsersService.updateAcceptedVersion', () => { ); }); }); + +describe('UsersService.getUserName', () => { + let source: DataSource | null = null; + + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, + }); + return source.initialize(); + }); + + afterEach(async () => { + if (!source) return; + await source.destroy(); + source = null; + }); + + it('ユーザーが存在しない場合は、ユーザー未存在エラー', async () => { + if (!source) fail(); + + try { + const module = await makeTestingModule(source); + if (!module) fail(); + const context = makeContext(uuidv4()); + + const service = module.get(UsersService); + await service.getUserName(context, 'external_id'); + fail(); + } catch (e) { + if (e instanceof HttpException) { + expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST); + expect(e.getResponse()).toEqual(makeErrorResponse('E010204')); + } else { + fail(); + } + } + }); +}); diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 5ba3c84..66beb3c 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -4,7 +4,6 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { isVerifyError, verify } from '../../common/jwt'; import { getPublicKey } from '../../common/jwt/jwt'; import { makePassword } from '../../common/password/password'; -import { AccessToken, IDToken } from '../../common/token'; import { SortDirection, TaskListSortableAttribute, @@ -1052,4 +1051,42 @@ export class UsersService { ); } } + /** + * Azure AD B2Cからユーザー名を取得する + * @param context + * @param externalId + */ + async getUserName(context: Context, externalId: string): Promise { + this.logger.log(`[IN] [${context.trackingId}] ${this.getUserName.name}`); + + try { + // extarnalIdの存在チェックを行う + await this.usersRepository.findUserByExternalId(externalId); + // ADB2Cからユーザー名を取得する + const adb2cUser = await this.adB2cService.getUser(externalId); + return adb2cUser.displayName; + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + case UserNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log(`[OUT] [${context.trackingId}] ${this.getUserName.name}`); + } + } } From 08e5a9cd4af261aa7eb4deced13e061bc2815007 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Wed, 25 Oct 2023 08:47:15 +0000 Subject: [PATCH 5/8] =?UTF-8?q?Merged=20PR=20525:=20=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=85=88=E5=8F=96=E5=BE=97API=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2935: ファイルアップロード先取得APIの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2935) - ファイルアップロード先取得APIの実装を修正しました。 - ContorollerからServiceへ外部ユーザーIDを渡す想定の部分がアクセストークンをそのまま渡すようになっていたので引数を修正しました。 - ログを整理して引数がわかるようにしています。 ## レビューポイント - 共有 - ログ内容は適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- .../src/features/files/files.controller.ts | 2 +- .../src/features/files/files.service.ts | 30 ++----------------- .../users/users.repository.service.ts | 2 +- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/dictation_server/src/features/files/files.controller.ts b/dictation_server/src/features/files/files.controller.ts index 4a72268..8578e52 100644 --- a/dictation_server/src/features/files/files.controller.ts +++ b/dictation_server/src/features/files/files.controller.ts @@ -182,7 +182,7 @@ export class FilesController { const context = makeContext(userId); - const url = await this.filesService.publishUploadSas(context, accessToken); + const url = await this.filesService.publishUploadSas(context, userId); return { url }; } diff --git a/dictation_server/src/features/files/files.service.ts b/dictation_server/src/features/files/files.service.ts index db97e77..96ac698 100644 --- a/dictation_server/src/features/files/files.service.ts +++ b/dictation_server/src/features/files/files.service.ts @@ -210,49 +210,25 @@ export class FilesService { externalId: string, ): Promise { this.logger.log( - `[IN] [${context.trackingId}] ${this.publishUploadSas.name}`, + `[IN] [${context.trackingId}] ${this.publishUploadSas.name} | params: { externalId: ${externalId} };`, ); //DBから国情報とアカウントIDを取得する - let accountId: number; - let country: string; try { const user = await this.usersRepository.findUserByExternalId(externalId); if (!user.account) { throw new AccountNotFoundError('account not found.'); } - accountId = user.account_id; - country = user.account.country; - } catch (e) { - this.logger.error(`error=${e}`); - this.logger.log( - `[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`, - ); - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const accountId = user.account_id; + const country = user.account.country; - try { // 国に応じたリージョンのBlobストレージにコンテナが存在するか確認 await this.blobStorageService.containerExists( context, accountId, country, ); - } catch (e) { - this.logger.error(`error=${e}`); - this.logger.log( - `[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`, - ); - throw new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - try { // SASトークン発行 const url = await this.blobStorageService.publishUploadSas( context, diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 0a9dcab..f579c3e 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -116,7 +116,7 @@ export class UsersRepositoryService { }); if (!user) { - throw new UserNotFoundError(); + throw new UserNotFoundError(`User not found. externalId: ${sub}`); } return user; } From 8ace80de741b980fd7d0d043c9b5e319832c7f30 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Thu, 26 Oct 2023 01:37:35 +0000 Subject: [PATCH 6/8] =?UTF-8?q?Merged=20PR=20517:=20API=20IF=E5=AE=9F?= =?UTF-8?q?=E8=A3=85=EF=BC=88=E4=BB=A3=E8=A1=8C=E6=93=8D=E4=BD=9C=E7=94=A8?= =?UTF-8?q?=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3=E7=94=9F=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2904: API IF](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2904) - 以下のAPIのIF実装 - POST /api/auth/delegation-token - POST /api/auth/delegation-access-token - OpenApiを生成 - 不要なas変換を削除 ## レビューポイント - リクエスト・レスポンスの型は認識通りか - 代行操作用トークン生成APIの引数のパラメータ名はよさそうか(delegatedAccountId) - アクセストークン再生成APIについてはガードを付けず、service内で引数に受け取ったリフレッシュトークンを検証し、正しいロール、階層かチェックする方向でよさそうか(通常のアクセストークン再生成と同様に) ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_server/src/api/odms/openapi.json | 136 +++++++++++++++++- .../features/accounts/accounts.controller.ts | 40 +++--- .../src/features/accounts/types/types.ts | 18 +-- .../src/features/auth/auth.controller.ts | 101 ++++++++++++- .../src/features/auth/types/types.ts | 16 +++ .../features/licenses/licenses.controller.ts | 10 +- .../src/features/tasks/tasks.controller.ts | 12 +- .../src/features/tasks/types/types.ts | 12 +- .../src/features/users/types/types.ts | 26 ++-- .../src/features/users/users.controller.ts | 14 +- .../src/features/workflows/types/types.ts | 12 +- 11 files changed, 317 insertions(+), 80 deletions(-) diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json index 76bf8c9..c316181 100644 --- a/dictation_server/src/api/odms/openapi.json +++ b/dictation_server/src/api/odms/openapi.json @@ -88,6 +88,100 @@ "security": [{ "bearer": [] }] } }, + "/auth/delegation/token": { + "post": { + "operationId": "delegationToken", + "summary": "", + "description": "代行操作用のリフレッシュトークン・アクセストークンを生成します", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DelegationTokenRequest" + } + } + } + }, + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DelegationTokenResponse" + } + } + } + }, + "400": { + "description": "指定したアカウントが代行操作を許可していない場合", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["auth"], + "security": [{ "bearer": [] }] + } + }, + "/auth/delegation/access-token": { + "post": { + "operationId": "delegationAccessToken", + "summary": "", + "description": "代行操作用のアクセストークンを再生成します", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DelegationAccessTokenResponse" + } + } + } + }, + "401": { + "description": "認証エラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["auth"], + "security": [{ "bearer": [] }] + } + }, "/accounts": { "post": { "operationId": "createAccount", @@ -3399,6 +3493,40 @@ "properties": { "accessToken": { "type": "string" } }, "required": ["accessToken"] }, + "DelegationTokenRequest": { + "type": "object", + "properties": { + "delegatedAccountId": { + "type": "number", + "description": "代行操作対象のアカウントID" + } + }, + "required": ["delegatedAccountId"] + }, + "DelegationTokenResponse": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string", + "description": "代行操作用のリフレッシュトークン" + }, + "accessToken": { + "type": "string", + "description": "代行操作用のアクセストークン" + } + }, + "required": ["refreshToken", "accessToken"] + }, + "DelegationAccessTokenResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string", + "description": "代行操作用のアクセストークン" + } + }, + "required": ["accessToken"] + }, "CreateAccountRequest": { "type": "object", "properties": { @@ -3680,13 +3808,7 @@ "poNumber": { "type": "string", "description": "POナンバー" }, "status": { "type": "string", "description": "注文状態" } }, - "required": [ - "orderDate", - "issueDate", - "numberOfOrder", - "poNumber", - "status" - ] + "required": ["orderDate", "numberOfOrder", "poNumber", "status"] }, "GetOrderHistoriesResponse": { "type": "object", diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts index cba3bb2..241a72a 100644 --- a/dictation_server/src/features/accounts/accounts.controller.ts +++ b/dictation_server/src/features/accounts/accounts.controller.ts @@ -200,7 +200,7 @@ export class AccountsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get('me') async getMyAccount(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -249,7 +249,7 @@ export class AccountsController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get('authors') async getAuthors(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -295,7 +295,7 @@ export class AccountsController { @UseGuards(AuthGuard) @Get('typists') async getTypists(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -340,7 +340,7 @@ export class AccountsController { @UseGuards(AuthGuard) @Get('typist-groups') async getTypistGroups(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -398,7 +398,7 @@ export class AccountsController { // アクセストークン取得 - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -461,7 +461,7 @@ export class AccountsController { const { typistGroupName, typistIds } = body; // アクセストークン取得 - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -525,7 +525,7 @@ export class AccountsController { // アクセストークン取得 - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -590,7 +590,7 @@ export class AccountsController { ): Promise { const { companyName, country, email, adminName } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -731,7 +731,7 @@ export class AccountsController { ): Promise { const { orderedAccountId, poNumber } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -812,7 +812,7 @@ export class AccountsController { @Req() req: Request, @Body() body: CancelIssueRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -860,7 +860,7 @@ export class AccountsController { @UseGuards(AuthGuard) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) async getWorktypes(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -913,7 +913,7 @@ export class AccountsController { ): Promise { const { worktypeId, description } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -973,7 +973,7 @@ export class AccountsController { const { worktypeId, description } = body; const { id } = param; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1033,7 +1033,7 @@ export class AccountsController { ): Promise { const { id } = param; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1086,7 +1086,7 @@ export class AccountsController { ): Promise { const { id } = param; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1146,7 +1146,7 @@ export class AccountsController { const { optionItems } = body; const { id } = param; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1205,7 +1205,7 @@ export class AccountsController { ): Promise { const { id } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1263,7 +1263,7 @@ export class AccountsController { ): Promise { const { limit, offset } = query; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1330,7 +1330,7 @@ export class AccountsController { secondryAdminUserId, } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -1390,7 +1390,7 @@ export class AccountsController { ): Promise { const { accountId } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index ccb45d9..921bd10 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -34,7 +34,7 @@ export class CreateAccountRequest { @ApiProperty({ required: false }) @IsInt() @IsOptional() - dealerAccountId?: number | undefined; + dealerAccountId?: number; @ApiProperty() adminName: string; @ApiProperty() @@ -116,19 +116,19 @@ export class Account { country: string; @ApiProperty({ required: false }) - parentAccountId?: number | undefined; + parentAccountId?: number; @ApiProperty() delegationPermission: boolean; @ApiProperty({ required: false }) - primaryAdminUserId?: number | undefined; + primaryAdminUserId?: number; @ApiProperty({ required: false }) - secondryAdminUserId?: number | undefined; + secondryAdminUserId?: number; @ApiProperty({ required: false }) - parentAccountName?: string | undefined; + parentAccountName?: string; } export class GetMyAccountResponse { @@ -394,7 +394,7 @@ export class GetWorktypesResponse { required: false, description: 'Active WorktypeIDに設定されているWorkTypeの内部ID', }) - active?: number | undefined; + active?: number; } export class CreateWorktypesRequest { @@ -516,7 +516,7 @@ export class PostActiveWorktypeRequest { @Type(() => Number) @IsInt() @Min(0) - id?: number | undefined; + id?: number; } export class PostActiveWorktypeResponse {} @@ -571,14 +571,14 @@ export type PartnerInfoFromDb = { export class UpdateAccountInfoRequest { @ApiProperty({ description: '親アカウントのID', required: false }) @IsOptional() - parentAccountId?: number | undefined; + parentAccountId?: number; @ApiProperty({ description: '代行操作許可' }) delegationPermission: boolean; @ApiProperty({ description: 'プライマリ管理者ID' }) primaryAdminUserId: number; @ApiProperty({ description: 'セカンダリ管理者ID', required: false }) @IsOptional() - secondryAdminUserId?: number | undefined; + secondryAdminUserId?: number; } export class UpdateAccountInfoResponse {} diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index 4d8d33b..aa82fc2 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -5,6 +5,7 @@ import { HttpStatus, Post, Req, + UseGuards, } from '@nestjs/common'; import { ApiResponse, @@ -19,10 +20,17 @@ import { AccessTokenResponse, TokenRequest, TokenResponse, + DelegationTokenRequest, + DelegationTokenResponse, + DelegationAccessTokenResponse, } from './types/types'; import { retrieveAuthorizationToken } from '../../common/http/helper'; import { makeContext } from '../../common/log'; import { v4 as uuidv4 } from 'uuid'; +import { Request } from 'express'; +import { AuthGuard } from '../../common/guards/auth/authguards'; +import { RoleGuard } from '../../common/guards/role/roleguards'; +import { ADMIN_ROLES, TIERS } from '../../constants'; @ApiTags('auth') @Controller('auth') @@ -117,7 +125,7 @@ export class AuthController { operationId: 'accessToken', description: 'リフレッシュトークンを元にアクセストークンを再生成します', }) - async accessToken(@Req() req): Promise { + async accessToken(@Req() req: Request): Promise { const refreshToken = retrieveAuthorizationToken(req); if (!refreshToken) { @@ -135,4 +143,95 @@ export class AuthController { ); return { accessToken }; } + + @Post('delegation/token') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: DelegationTokenResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: '指定したアカウントが代行操作を許可していない場合', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'delegationToken', + description: + '代行操作用のリフレッシュトークン・アクセストークンを生成します', + }) + @UseGuards(AuthGuard) + @UseGuards( + RoleGuard.requireds({ + roles: [ADMIN_ROLES.ADMIN], + tiers: [TIERS.TIER4], + }), + ) + async delegationToken( + @Req() req: Request, + @Body() body: DelegationTokenRequest, + ): Promise { + const { delegatedAccountId } = body; + const refreshToken = retrieveAuthorizationToken(req); + + if (!refreshToken) { + throw new HttpException( + makeErrorResponse('E000107'), + HttpStatus.UNAUTHORIZED, + ); + } + + const context = makeContext(uuidv4()); + + return { accessToken: '', refreshToken: '' }; + } + + @Post('delegation/access-token') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: DelegationAccessTokenResponse, + description: '成功時のレスポンス', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: '認証エラー', + type: ErrorResponse, + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: '想定外のサーバーエラー', + type: ErrorResponse, + }) + @ApiOperation({ + operationId: 'delegationAccessToken', + description: '代行操作用のアクセストークンを再生成します', + }) + async delegationAccessToken( + @Req() req: Request, + ): Promise { + const refreshToken = retrieveAuthorizationToken(req); + + if (!refreshToken) { + throw new HttpException( + makeErrorResponse('E000107'), + HttpStatus.UNAUTHORIZED, + ); + } + + const context = makeContext(uuidv4()); + + return { accessToken: '' }; + } } diff --git a/dictation_server/src/features/auth/types/types.ts b/dictation_server/src/features/auth/types/types.ts index a5250bc..7528fa7 100644 --- a/dictation_server/src/features/auth/types/types.ts +++ b/dictation_server/src/features/auth/types/types.ts @@ -25,3 +25,19 @@ export type TermsCheckInfo = { latestEulaVersion: string; latestDpaVersion: string; }; + +export class DelegationTokenRequest { + @ApiProperty({ description: '代行操作対象のアカウントID' }) + delegatedAccountId: number; +} +export class DelegationTokenResponse { + @ApiProperty({ description: '代行操作用のリフレッシュトークン' }) + refreshToken: string; + @ApiProperty({ description: '代行操作用のアクセストークン' }) + accessToken: string; +} + +export class DelegationAccessTokenResponse { + @ApiProperty({ description: '代行操作用のアクセストークン' }) + accessToken: string; +} diff --git a/dictation_server/src/features/licenses/licenses.controller.ts b/dictation_server/src/features/licenses/licenses.controller.ts index fac9163..ae47eb1 100644 --- a/dictation_server/src/features/licenses/licenses.controller.ts +++ b/dictation_server/src/features/licenses/licenses.controller.ts @@ -75,7 +75,7 @@ export class LicensesController { @Req() req: Request, @Body() body: CreateOrdersRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -126,7 +126,7 @@ export class LicensesController { @Req() req: Request, @Body() body: IssueCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -182,7 +182,7 @@ export class LicensesController { @Req() req: Request, @Body() body: ActivateCardLicensesRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -235,7 +235,7 @@ export class LicensesController { // eslint-disable-next-line @typescript-eslint/no-unused-vars @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -296,7 +296,7 @@ export class LicensesController { @Req() req: Request, @Body() body: CancelOrderRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), diff --git a/dictation_server/src/features/tasks/tasks.controller.ts b/dictation_server/src/features/tasks/tasks.controller.ts index 49fbe9e..72bd402 100644 --- a/dictation_server/src/features/tasks/tasks.controller.ts +++ b/dictation_server/src/features/tasks/tasks.controller.ts @@ -84,7 +84,7 @@ export class TasksController { @Req() req, @Query() body: TasksRequest, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -204,7 +204,7 @@ export class TasksController { ): Promise { // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -274,7 +274,7 @@ export class TasksController { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -341,7 +341,7 @@ export class TasksController { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -410,7 +410,7 @@ export class TasksController { const { audioFileId } = params; // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -560,7 +560,7 @@ export class TasksController { ): Promise { const { assignees } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), diff --git a/dictation_server/src/features/tasks/types/types.ts b/dictation_server/src/features/tasks/types/types.ts index ac11e1b..4e07f73 100644 --- a/dictation_server/src/features/tasks/types/types.ts +++ b/dictation_server/src/features/tasks/types/types.ts @@ -77,7 +77,7 @@ export class Assignee { @IsInt() @Min(1) @IsOptional() - typistUserId?: number | undefined; + typistUserId?: number; @ApiProperty({ required: false, description: 'TypistGroupID(TypistGroupIDかTypistIDのどちらかに値が入る)', @@ -85,7 +85,7 @@ export class Assignee { @IsInt() @Min(1) @IsOptional() - typistGroupId?: number | undefined; + typistGroupId?: number; @ApiProperty({ description: 'Typist名 / TypistGroup名' }) typistName: string; } @@ -143,7 +143,7 @@ export class Task { required: false, description: '割り当てられたユーザー', }) - typist?: Typist | undefined; + typist?: Typist; @ApiProperty({ type: [Assignee], description: @@ -159,12 +159,12 @@ export class Task { required: false, description: '文字起こし開始日時(yyyy-mm-ddThh:mm:ss.sss)', }) - transcriptionStartedDate?: string | undefined; + transcriptionStartedDate?: string; @ApiProperty({ required: false, description: '文字起こし終了日時(yyyy-mm-ddThh:mm:ss.sss)', }) - transcriptionFinishedDate?: string | undefined; + transcriptionFinishedDate?: string; } export class TasksResponse { @@ -198,7 +198,7 @@ export class AudioNextResponse { required: false, description: 'ODMS Cloud上の次の音声ファイルID(存在しなければundefind)', }) - nextFileId?: number | undefined; + nextFileId?: number; } export class ChangeStatusRequest { diff --git a/dictation_server/src/features/users/types/types.ts b/dictation_server/src/features/users/types/types.ts index b75b6b6..b30b637 100644 --- a/dictation_server/src/features/users/types/types.ts +++ b/dictation_server/src/features/users/types/types.ts @@ -30,7 +30,7 @@ export class User { role: string; @ApiProperty({ required: false }) - authorId?: string | undefined; + authorId?: string; @ApiProperty() typistGroupName: string[]; @@ -57,10 +57,10 @@ export class User { prompt: boolean; @ApiProperty({ required: false }) - expiration?: string | undefined; + expiration?: string; @ApiProperty({ required: false }) - remaining?: number | undefined; + remaining?: number; @ApiProperty({ description: `${Object.values(USER_LICENSE_STATUS).join('/')}`, @@ -86,7 +86,7 @@ export class SignupRequest { @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - authorId?: string | undefined; + authorId?: string; @ApiProperty() email: string; @@ -102,16 +102,16 @@ export class SignupRequest { @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - encryption?: boolean | undefined; + encryption?: boolean; @ApiProperty({ required: false }) @IsPasswordvalid() @IsEncryptionPasswordPresent() - encryptionPassword?: string | undefined; + encryptionPassword?: string; @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - prompt?: boolean | undefined; + prompt?: boolean; } export class SignupResponse {} @@ -162,7 +162,7 @@ export class GetRelationsResponse { description: 'ユーザーが暗号化を掛ける場合のパスワード', required: false, }) - encryptionPassword?: string | undefined; + encryptionPassword?: string; @ApiProperty({ description: 'アカウントがデフォルトで利用するWorkTypeID(アカウントに紐づくWorkTypeIDから一つ指定。activeWorktypeがなければ空文字を返却する)', @@ -215,7 +215,7 @@ export class PostUpdateUserRequest { @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - authorId?: string | undefined; + authorId?: string; @ApiProperty() autoRenew: boolean; @@ -228,15 +228,15 @@ export class PostUpdateUserRequest { @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - encryption?: boolean | undefined; + encryption?: boolean; @ApiProperty({ required: false }) @IsPasswordvalid() - encryptionPassword?: string | undefined; + encryptionPassword?: string; @ApiProperty({ required: false }) @IsRoleAuthorDataValid() - prompt?: boolean | undefined; + prompt?: boolean; } export class PostUpdateUserResponse {} @@ -263,7 +263,7 @@ export class UpdateAcceptedVersionRequest { @ApiProperty({ description: '更新バージョン(EULA)' }) acceptedEULAVersion: string; @ApiProperty({ description: '更新バージョン(DPA)', required: false }) - acceptedDPAVersion?: string | undefined; + acceptedDPAVersion?: string; } export class UpdateAcceptedVersionResponse {} diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index cee1a78..c0e4330 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -132,7 +132,7 @@ export class UsersController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getUsers(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -194,7 +194,7 @@ export class UsersController { prompt, } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -253,7 +253,7 @@ export class UsersController { @UseGuards(AuthGuard) @Get('relations') async getRelations(@Req() req: Request): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -307,7 +307,7 @@ export class UsersController { ): Promise { const { direction, paramName } = body; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -365,7 +365,7 @@ export class UsersController { ): Promise { const {} = query; - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -499,7 +499,7 @@ export class UsersController { @Body() body: AllocateLicenseRequest, @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), @@ -558,7 +558,7 @@ export class UsersController { @Body() body: DeallocateLicenseRequest, @Req() req: Request, ): Promise { - const accessToken = retrieveAuthorizationToken(req) as string; + const accessToken = retrieveAuthorizationToken(req); if (!accessToken) { throw new HttpException( makeErrorResponse('E000107'), diff --git a/dictation_server/src/features/workflows/types/types.ts b/dictation_server/src/features/workflows/types/types.ts index ced5b59..87f77c0 100644 --- a/dictation_server/src/features/workflows/types/types.ts +++ b/dictation_server/src/features/workflows/types/types.ts @@ -44,9 +44,9 @@ export class GetWorkflowsResponse { export class WorkflowTypist { @ApiProperty({ description: 'タイピストユーザーの内部ID', required: false }) - typistId?: number | undefined; + typistId?: number; @ApiProperty({ description: 'タイピストグループの内部ID', required: false }) - typistGroupId?: number | undefined; + typistGroupId?: number; } export class CreateWorkflowsRequest { @@ -60,13 +60,13 @@ export class CreateWorkflowsRequest { @Type(() => Number) @IsInt() @Min(0) - worktypeId?: number | undefined; + worktypeId?: number; @ApiProperty({ description: 'テンプレートの内部ID', required: false }) @IsOptional() @Type(() => Number) @IsInt() @Min(0) - templateId?: number | undefined; + templateId?: number; @ApiProperty({ description: 'ルーティング候補のタイピストユーザー/タイピストグループ', type: [WorkflowTypist], @@ -99,13 +99,13 @@ export class UpdateWorkflowRequest { @Type(() => Number) @IsInt() @Min(0) - worktypeId?: number | undefined; + worktypeId?: number; @ApiProperty({ description: 'テンプレートの内部ID', required: false }) @IsOptional() @Type(() => Number) @IsInt() @Min(0) - templateId?: number | undefined; + templateId?: number; @ApiProperty({ description: 'ルーティング候補のタイピストユーザー/タイピストグループ', type: [WorkflowTypist], From 8474c6a4f55cb93f7e86407f37392d13e022fd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=AF=E6=9C=AC=20=E9=96=8B?= Date: Thu, 26 Oct 2023 04:37:58 +0000 Subject: [PATCH 7/8] =?UTF-8?q?Merged=20PR=20529:=20Pipeline=E3=81=AE?= =?UTF-8?q?=E4=BB=95=E7=B5=84=E3=81=BF=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2949: Pipelineの仕組みを修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2949) - .env.(環境名) の環境変数を用いてフロント側ビルドを行うよう修正 - ビルドパイプラインでstaging/production用の両方のフロント側ビルドを行うよう変更 ## レビューポイント - 仕組み上の不明点はないか - 情報共有 ## 動作確認状況 - 本番環境へデプロイされ、本当の最低限だけ動作することを確認 --- azure-pipelines-production.yml | 4 +-- azure-pipelines-staging.yml | 59 ++++++++++++++++++++++++++++---- dictation_client/.env.production | 5 +++ 3 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 dictation_client/.env.production diff --git a/azure-pipelines-production.yml b/azure-pipelines-production.yml index 1359ec5..e34e28f 100644 --- a/azure-pipelines-production.yml +++ b/azure-pipelines-production.yml @@ -49,7 +49,7 @@ jobs: displayName: Deploy Frontend Files variables: storageAccountName: saomdspipeline - containerName: staging + environment: production pool: vmImage: ubuntu-latest steps: @@ -65,7 +65,7 @@ jobs: az storage blob download \ --auth-mode login \ --account-name $(storageAccountName) \ - --container-name $(containerName) \ + --container-name $(environment) \ --name $(Build.SourceVersion).zip \ --file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip - task: Bash@3 diff --git a/azure-pipelines-staging.yml b/azure-pipelines-staging.yml index 46175dc..b8e341e 100644 --- a/azure-pipelines-staging.yml +++ b/azure-pipelines-staging.yml @@ -99,13 +99,13 @@ jobs: azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}' action: Push an image imageName: odmscloud/staging/dictation:$(Build.SourceVersion) -- job: frontend_build +- job: frontend_build_staging dependsOn: backend_build condition: succeeded('backend_build') - displayName: Build Frontend Files + displayName: Build Frontend Files(staging) variables: storageAccountName: saomdspipeline - containerName: staging + environment: staging pool: name: odms-deploy-pipeline steps: @@ -122,7 +122,7 @@ jobs: displayName: Bash Script inputs: targetType: inline - script: cd dictation_client && npm run build + script: cd dictation_client && cp -f .env.$(environment) .env && npm run build - task: ArchiveFiles@2 inputs: rootFolderOrFile: dictation_client/build @@ -139,14 +139,59 @@ jobs: az storage blob upload \ --auth-mode login \ --account-name $(storageAccountName) \ - --container-name $(containerName) \ + --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 && cp -f .env.$(environment) .env && npm run build + - 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: backend_deploy - dependsOn: frontend_build - condition: succeeded('frontend_build') + dependsOn: frontend_build_production + condition: succeeded('frontend_build_production') displayName: Backend Deploy pool: name: odms-deploy-pipeline diff --git a/dictation_client/.env.production b/dictation_client/.env.production new file mode 100644 index 0000000..7169682 --- /dev/null +++ b/dictation_client/.env.production @@ -0,0 +1,5 @@ +VITE_STAGE=production +VITE_B2C_CLIENTID=b0ec473b-6b2b-4f12-adc6-39a24ebe6a3f +VITE_B2C_AUTHORITY=https://adb2codmsprod.b2clogin.com/adb2codmsprod.onmicrosoft.com/b2c_1_signin_prod +VITE_B2C_KNOWNAUTHORITIES=adb2codmsprod.b2clogin.com +VITE_DESK_TOP_APP_SCHEME=odms-desktopapp \ No newline at end of file From 375e3a5a3bd45f35b2298276dfd98e31dac9958e Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Thu, 26 Oct 2023 06:35:59 +0000 Subject: [PATCH 8/8] =?UTF-8?q?Merged=20PR=20524:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=EF=BC=88=E4=BB=A3=E8=A1=8C=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E4=B8=AD=E8=A1=A8=E7=A4=BA=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=8D=E3=83=B3=E3=83=88=EF=BC=8B=E4=BB=A3=E8=A1=8C=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E4=B8=AD=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AE=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2908: 画面実装(代行操作中表示コンポーネント+代行操作中に表示する画面のデザイン)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2908) - 代行操作中に表示する画面に対して、代行操作中のデザインを反映 - 代行操作中に画面上部に表示するバーのコンポーネントを作成 - 代行操作用のトークンをauthStateに追加 ## レビューポイント - 代行操作バーのコンポーネントに表示する会社名をpropsとしたが良いか - このコンポーネントを表示する画面で会社名を取得して、会社名とトークンがあれば表示するという風にしたいから - 代行操作用トークンはauthStateでよいと思っているが認識会うか ## UIの変更 - https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2908?csf=1&web=1&e=Cr5NCL ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- .../src/components/delegate/index.tsx | 36 +++++++++++++++++++ .../src/features/auth/authSlice.ts | 9 ++++- .../src/features/auth/selectors.ts | 4 +++ dictation_client/src/features/auth/state.ts | 2 ++ .../pages/LicensePage/licenseOrderHistory.tsx | 13 ++++++- .../src/pages/LicensePage/licenseSummary.tsx | 17 ++++++++- .../src/pages/TemplateFilePage/index.tsx | 17 ++++++++- .../pages/TypistGroupSettingPage/index.tsx | 18 ++++++++-- .../src/pages/UserListPage/index.tsx | 17 ++++++++- .../src/pages/WorkTypeIdSettingPage/index.tsx | 17 ++++++++- .../src/pages/WorkflowPage/index.tsx | 18 ++++++++-- dictation_client/src/translation/de.json | 1 + dictation_client/src/translation/en.json | 1 + dictation_client/src/translation/es.json | 1 + dictation_client/src/translation/fr.json | 1 + 15 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 dictation_client/src/components/delegate/index.tsx diff --git a/dictation_client/src/components/delegate/index.tsx b/dictation_client/src/components/delegate/index.tsx new file mode 100644 index 0000000..264facc --- /dev/null +++ b/dictation_client/src/components/delegate/index.tsx @@ -0,0 +1,36 @@ +// 代行操作中に表示するコンポーネント +// ------------------------------------------------------ +import React from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import styles from "../../styles/app.module.scss"; +import exit from "../../assets/images/exit.svg"; +import reportWhite from "../../assets/images/report_white.svg"; +import { getTranslationID } from "translation"; + +interface DelegationBarProps { + delegatedCompanyName: string; +} + +export const DelegationBar: React.FC = ( + porps +): JSX.Element => { + const { delegatedCompanyName } = porps; + const { t } = useTranslation(); + + return ( +
+ report +

+ {t(getTranslationID("common.label.operationInsteadOf"))} + {delegatedCompanyName} +

+ Exit +
+ ); +}; diff --git a/dictation_client/src/features/auth/authSlice.ts b/dictation_client/src/features/auth/authSlice.ts index a35be42..a9c5dc5 100644 --- a/dictation_client/src/features/auth/authSlice.ts +++ b/dictation_client/src/features/auth/authSlice.ts @@ -14,6 +14,8 @@ const initialState: AuthState = { configuration: initialConfig(), accessToken: loadAccessToken(), refreshToken: loadRefreshToken(), + delegatedAccessToken: null, + delegatedRefreshToken: null, }; export const authSlice = createSlice({ @@ -22,7 +24,12 @@ export const authSlice = createSlice({ reducers: { setToken: ( state, - action: PayloadAction> + action: PayloadAction< + Omit< + AuthState, + "configuration" | "delegatedAccessToken" | "delegatedRefreshToken" + > + > ) => { const { accessToken, refreshToken } = action.payload; if (accessToken && refreshToken) { diff --git a/dictation_client/src/features/auth/selectors.ts b/dictation_client/src/features/auth/selectors.ts index db5f6a9..db20981 100644 --- a/dictation_client/src/features/auth/selectors.ts +++ b/dictation_client/src/features/auth/selectors.ts @@ -22,3 +22,7 @@ export const isTokenExpired = (state: RootState): boolean => { } return true; }; + +// 代行操作用のトークンを取得する +export const selectDelegatedAccessToken = (state: RootState): string | null => + state.auth.delegatedAccessToken; diff --git a/dictation_client/src/features/auth/state.ts b/dictation_client/src/features/auth/state.ts index 8fbfd8a..1d8d82d 100644 --- a/dictation_client/src/features/auth/state.ts +++ b/dictation_client/src/features/auth/state.ts @@ -4,4 +4,6 @@ export interface AuthState { configuration: ConfigurationParameters; accessToken: string | null; refreshToken: string | null; + delegatedAccessToken: string | null; + delegatedRefreshToken: string | null; } diff --git a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx index e20c9f1..4ccc949 100644 --- a/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx +++ b/dictation_client/src/pages/LicensePage/licenseOrderHistory.tsx @@ -30,6 +30,8 @@ import { selectSelectedRow } from "features/license/partnerLicense"; import undo from "../../assets/images/undo.svg"; import history from "../../assets/images/history.svg"; import progress_activit from "../../assets/images/progress_activit.svg"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; +import { DelegationBar } from "components/delegate"; interface LicenseOrderHistoryProps { onReturn: () => void; @@ -46,6 +48,8 @@ export const LicenseOrderHistory: React.FC = ( const currentPage = useSelector(selectCurrentPage); const isLoading = useSelector(selectIsLoading); const selectedRow = useSelector(selectSelectedRow); + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); // Return押下時の処理 const returnGui = useCallback(() => { @@ -154,7 +158,14 @@ export const LicenseOrderHistory: React.FC = ( }, [dispatch, currentPage]); return ( -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && + }
diff --git a/dictation_client/src/pages/LicensePage/licenseSummary.tsx b/dictation_client/src/pages/LicensePage/licenseSummary.tsx index 8700902..85b0fd0 100644 --- a/dictation_client/src/pages/LicensePage/licenseSummary.tsx +++ b/dictation_client/src/pages/LicensePage/licenseSummary.tsx @@ -22,6 +22,8 @@ import { LicenseOrderPopup } from "./licenseOrderPopup"; import { CardLicenseActivatePopup } from "./cardLicenseActivatePopup"; // eslint-disable-next-line import/no-named-as-default import LicenseOrderHistory from "./licenseOrderHistory"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; +import { DelegationBar } from "components/delegate"; interface LicenseSummaryProps { onReturn?: () => void; @@ -33,6 +35,8 @@ export const LicenseSummary: React.FC = ( const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); const selectedRow = useSelector(selectSelectedRow); + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); // popup制御関係 const [islicenseOrderPopupOpen, setIslicenseOrderPopupOpen] = useState(false); @@ -96,7 +100,18 @@ export const LicenseSummary: React.FC = ( /> )} {!islicenseOrderHistoryOpen && ( -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/pages/TemplateFilePage/index.tsx b/dictation_client/src/pages/TemplateFilePage/index.tsx index 117ba01..17959c0 100644 --- a/dictation_client/src/pages/TemplateFilePage/index.tsx +++ b/dictation_client/src/pages/TemplateFilePage/index.tsx @@ -15,10 +15,14 @@ import { selectIsLoading, } from "features/workflow/template"; import { AddTemplateFilePopup } from "./addTemplateFilePopup"; +import { DelegationBar } from "components/delegate"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; export const TemplateFilePage: React.FC = () => { const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); + // authStateに配置予定の代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); const templates = useSelector(selectTemplates); const isLoading = useSelector(selectIsLoading); @@ -38,7 +42,18 @@ export const TemplateFilePage: React.FC = () => { }} /> )} -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/pages/TypistGroupSettingPage/index.tsx b/dictation_client/src/pages/TypistGroupSettingPage/index.tsx index 6b1ff06..d6958ef 100644 --- a/dictation_client/src/pages/TypistGroupSettingPage/index.tsx +++ b/dictation_client/src/pages/TypistGroupSettingPage/index.tsx @@ -17,11 +17,14 @@ import { useTranslation } from "react-i18next"; import { getTranslationID } from "translation"; import { AddTypistGroupPopup } from "./addTypistGroupPopup"; import { EditTypistGroupPopup } from "./editTypistGroupPopup"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; +import { DelegationBar } from "components/delegate"; const TypistGroupSettingPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); - + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); const isLoading = useSelector(selectIsLoading); const typistGroup = useSelector(selectTypistGroups); @@ -60,7 +63,18 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => { isOpen={isEditPopupOpen} typistGroupId={editTypistGroupId} /> -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/pages/UserListPage/index.tsx b/dictation_client/src/pages/UserListPage/index.tsx index cd875a6..d5dc966 100644 --- a/dictation_client/src/pages/UserListPage/index.tsx +++ b/dictation_client/src/pages/UserListPage/index.tsx @@ -28,10 +28,14 @@ import progress_activit from "../../assets/images/progress_activit.svg"; import { UserAddPopup } from "./popup"; import { UserUpdatePopup } from "./updatePopup"; import { AllocateLicensePopup } from "./allocateLicensePopup"; +import { DelegationBar } from "components/delegate"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; const UserListPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); const [isPopupOpen, setIsPopupOpen] = useState(false); const [isUpdatePopupOpen, setIsUpdatePopupOpen] = useState(false); @@ -106,7 +110,18 @@ const UserListPage: React.FC = (): JSX.Element => { setIsAllocateLicensePopupOpen(false); }} /> -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx index 0f4c03f..c1502fe 100644 --- a/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx +++ b/dictation_client/src/pages/WorkTypeIdSettingPage/index.tsx @@ -24,10 +24,14 @@ import { AppDispatch } from "app/store"; import { AddWorktypeIdPopup } from "./addWorktypeIdPopup"; import { EditWorktypeIdPopup } from "./editWorktypeIdPopup"; import { EditOptionItemsPopup } from "./editOptionItemsPopup"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; +import { DelegationBar } from "components/delegate"; const WorktypeIdSettingPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const [t] = useTranslation(); + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); const isLoading = useSelector(selectIsLoading); const worktypes = useSelector(selectWorktypes); const activeWorktypeId = useSelector(selectActiveWorktypeId); @@ -124,7 +128,18 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => { }} isOpen={isShowEditOptionItemPopup} /> -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/pages/WorkflowPage/index.tsx b/dictation_client/src/pages/WorkflowPage/index.tsx index 16f5c49..49bfced 100644 --- a/dictation_client/src/pages/WorkflowPage/index.tsx +++ b/dictation_client/src/pages/WorkflowPage/index.tsx @@ -21,6 +21,8 @@ import progress_activit from "assets/images/progress_activit.svg"; import { getTranslationID } from "translation"; import { AddWorkflowPopup } from "./addworkflowPopup"; import { EditWorkflowPopup } from "./editworkflowPopup"; +import { DelegationBar } from "components/delegate"; +import { selectDelegatedAccessToken } from "features/auth/selectors"; const WorkflowPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); @@ -29,7 +31,8 @@ const WorkflowPage: React.FC = (): JSX.Element => { const [isShowAddPopup, setIsShowAddPopup] = useState(false); // 編集Popupの表示制御 const [isShowEditPopup, setIsShowEditPopup] = useState(false); - + // 代行操作用のトークンを取得する + const delegatedAccessToken = useSelector(selectDelegatedAccessToken); const workflows = useSelector(selectWorkflows); const isLoading = useSelector(selectIsLoading); @@ -70,7 +73,18 @@ const WorkflowPage: React.FC = (): JSX.Element => { }} /> )} -
+
+ { + // 代行操作中の場合は、代行操作バーを表示する + // TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで) + delegatedAccessToken && ( + + ) + }
diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 55ce9ea..d821ebc 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -16,6 +16,7 @@ "save": "Speichern", "delete": "Löschen", "return": "zurückkehren", + "operationInsteadOf": "(de)Operation instead of:", "tier1": "(de)Admin", "tier2": "(de)BC", "tier3": "(de)Distributor", diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index 1958181..f11c1ad 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -16,6 +16,7 @@ "save": "Save", "delete": "Delete", "return": "Return", + "operationInsteadOf": "Operation instead of:", "tier1": "Admin", "tier2": "BC", "tier3": "Distributor", diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 6bd2dbb..b96b55d 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -16,6 +16,7 @@ "save": "Ahorrar", "delete": "Delete", "return": "Devolver", + "operationInsteadOf": "(es)Operation instead of:", "tier1": "(es)Admin", "tier2": "(es)BC", "tier3": "(es)Distributor", diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index e236a8b..750cfbf 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -16,6 +16,7 @@ "save": "Sauvegarder", "delete": "Delete", "return": "Retour", + "operationInsteadOf": "(fr)Operation instead of:", "tier1": "(fr)Admin", "tier2": "(fr)BC", "tier3": "(fr)Distributor",