From 12d168d14c47c7c37299abfcd18d34f6c7692941 Mon Sep 17 00:00:00 2001 From: masaaki Date: Wed, 21 Feb 2024 01:41:21 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20754:=20=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E7=99=BB=E9=8C=B2=E3=83=84=E3=83=BC=E3=83=AB=E4=BD=9C?= =?UTF-8?q?=E6=88=90=EF=BC=8B=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3571: データ登録ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3571) - 移行データの登録ツールを作成しました - 入力用jsonファイルの読み込み - アカウント・ユーザの登録 - 既存サービスを移植・微修正し呼び出し - rate_limit用のsleep実施 - ワークタイプ・ライセンス・カードライセンスの登録 - 実行についてはpostmanでの実行を考えており、clientは作成しておりません ## レビューポイント - 既存サービスからの流用が多いですが、メインの処理はfeatures/registerになるため、こちらをメインに見ていただければと思います。 ## UIの変更 - 無し ## 動作確認状況 - ローカルで動作確認 ## 補足 - 相談、参考資料などがあれば --- .../server/.env.local.example | 36 ++ data_migration_tools/server/package-lock.json | 548 +++++++++++++++++- data_migration_tools/server/package.json | 5 +- data_migration_tools/server/src/app.module.ts | 40 +- .../server/src/common/error/code.ts | 70 +++ .../src/common/error/makeErrorResponse.ts | 10 + .../server/src/common/error/message.ts | 59 ++ .../server/src/common/error/types/types.ts | 15 + .../server/src/common/log/context.ts | 32 + .../server/src/common/log/index.ts | 4 + .../server/src/common/log/types.ts | 34 ++ .../server/src/common/password/index.ts | 3 + .../server/src/common/password/password.ts | 35 ++ .../server/src/common/repository/index.ts | 143 +++++ .../server/src/common/types/role/index.ts | 10 + .../server/src/common/types/sort/index.ts | 27 + .../server/src/common/types/sort/util.ts | 11 + .../server/src/common/types/types.ts | 155 +++++ .../server/src/constants/index.ts | 268 +++++++++ .../features/accounts/accounts.controller.ts | 12 + .../src/features/accounts/accounts.module.ts | 19 + .../src/features/accounts/accounts.service.ts | 227 ++++++++ .../features/register/register.controller.ts | 209 +++++++ .../src/features/register/register.module.ts | 25 + .../src/features/register/register.service.ts | 68 +++ .../src/features/register/types/types.ts | 10 + .../src/features/users/users.controller.ts | 10 + .../server/src/features/users/users.module.ts | 12 + .../src/features/users/users.service.ts | 306 ++++++++++ .../src/gateways/adb2c/adb2c.service.ts | 102 ++++ .../blobstorage/blobstorage.service.ts | 74 ++- .../accounts/accounts.repository.module.ts | 11 + .../accounts/accounts.repository.service.ts | 164 ++++++ .../accounts/entity/account.entity.ts | 70 +++ .../src/repositories/accounts/errors/types.ts | 28 + .../licenses/entity/license.entity.ts | 137 +++++ .../src/repositories/licenses/errors/types.ts | 108 ++++ .../licenses/licenses.repository.module.ts | 17 + .../licenses/licenses.repository.service.ts | 130 +++++ .../entity/sort_criteria.entity.ts | 16 + .../sort_criteria.repository.module.ts | 8 + .../repositories/users/entity/user.entity.ts | 97 ++++ .../src/repositories/users/errors/types.ts | 56 ++ .../users/users.repository.module.ts | 11 + .../users/users.repository.service.ts | 141 +++++ .../worktypes/entity/option_item.entity.ts | 42 ++ .../worktypes/entity/worktype.entity.ts | 44 ++ .../repositories/worktypes/errors/types.ts | 28 + .../worktypes/worktypes.repository.module.ts | 12 + .../worktypes/worktypes.repository.service.ts | 104 ++++ 50 files changed, 3785 insertions(+), 18 deletions(-) create mode 100644 data_migration_tools/server/src/common/error/code.ts create mode 100644 data_migration_tools/server/src/common/error/makeErrorResponse.ts create mode 100644 data_migration_tools/server/src/common/error/message.ts create mode 100644 data_migration_tools/server/src/common/error/types/types.ts create mode 100644 data_migration_tools/server/src/common/log/context.ts create mode 100644 data_migration_tools/server/src/common/log/index.ts create mode 100644 data_migration_tools/server/src/common/log/types.ts create mode 100644 data_migration_tools/server/src/common/password/index.ts create mode 100644 data_migration_tools/server/src/common/password/password.ts create mode 100644 data_migration_tools/server/src/common/repository/index.ts create mode 100644 data_migration_tools/server/src/common/types/role/index.ts create mode 100644 data_migration_tools/server/src/common/types/sort/index.ts create mode 100644 data_migration_tools/server/src/common/types/sort/util.ts create mode 100644 data_migration_tools/server/src/common/types/types.ts create mode 100644 data_migration_tools/server/src/features/accounts/accounts.controller.ts create mode 100644 data_migration_tools/server/src/features/accounts/accounts.module.ts create mode 100644 data_migration_tools/server/src/features/accounts/accounts.service.ts create mode 100644 data_migration_tools/server/src/features/register/register.controller.ts create mode 100644 data_migration_tools/server/src/features/register/register.module.ts create mode 100644 data_migration_tools/server/src/features/register/register.service.ts create mode 100644 data_migration_tools/server/src/features/register/types/types.ts create mode 100644 data_migration_tools/server/src/features/users/users.controller.ts create mode 100644 data_migration_tools/server/src/features/users/users.module.ts create mode 100644 data_migration_tools/server/src/features/users/users.service.ts create mode 100644 data_migration_tools/server/src/repositories/accounts/accounts.repository.module.ts create mode 100644 data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts create mode 100644 data_migration_tools/server/src/repositories/accounts/entity/account.entity.ts create mode 100644 data_migration_tools/server/src/repositories/accounts/errors/types.ts create mode 100644 data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts create mode 100644 data_migration_tools/server/src/repositories/licenses/errors/types.ts create mode 100644 data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts create mode 100644 data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts create mode 100644 data_migration_tools/server/src/repositories/sort_criteria/entity/sort_criteria.entity.ts create mode 100644 data_migration_tools/server/src/repositories/sort_criteria/sort_criteria.repository.module.ts create mode 100644 data_migration_tools/server/src/repositories/users/entity/user.entity.ts create mode 100644 data_migration_tools/server/src/repositories/users/errors/types.ts create mode 100644 data_migration_tools/server/src/repositories/users/users.repository.module.ts create mode 100644 data_migration_tools/server/src/repositories/users/users.repository.service.ts create mode 100644 data_migration_tools/server/src/repositories/worktypes/entity/option_item.entity.ts create mode 100644 data_migration_tools/server/src/repositories/worktypes/entity/worktype.entity.ts create mode 100644 data_migration_tools/server/src/repositories/worktypes/errors/types.ts create mode 100644 data_migration_tools/server/src/repositories/worktypes/worktypes.repository.module.ts create mode 100644 data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts diff --git a/data_migration_tools/server/.env.local.example b/data_migration_tools/server/.env.local.example index e69de29..0cee3f5 100644 --- a/data_migration_tools/server/.env.local.example +++ b/data_migration_tools/server/.env.local.example @@ -0,0 +1,36 @@ +STAGE=local +NO_COLOR=TRUE +CORS=TRUE +PORT=8280 +# 開発環境ではADB2Cが別テナントになる都合上、環境変数を分けている +TENANT_NAME=adb2codmsdev +SIGNIN_FLOW_NAME=b2c_1_signin_dev +ADB2C_TENANT_ID=xxxxxxxx +ADB2C_CLIENT_ID=xxxxxxxx +ADB2C_CLIENT_SECRET=xxxxxxxx +ADB2C_ORIGIN=https://zzzzzzzzzz +JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5IZZNgDew9eGmuFTezwdHYLSaJvUPPIKYoiOeVLD1paWNI51\n7Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3yCTR6wcWR3PfFJrl9vh5SOo79koZ\noJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbWFJXnDe0DVXYXpJLb4LAlF2XAyYX0\nSYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qSfiL9zWk9dvHoKzSnfSDzDFoFcEoV\nchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//mBNNaDHv83Yuw3mGShT73iJ0JQdk\nTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GOOQIDAQABAoIBADrwp7u097+dK/tw\nWD61n3DIGAqg/lmFt8X4IH8MKLSE/FKr16CS1bqwOEuIM3ZdUtDeXd9Xs7IsyEPE\n5ZwuXK7DSF0M4+Mj8Ip49Q0Aww9aUoLQU9HGfgN/r4599GTrt31clZXA/6Mlighq\ncOZgCcEfdItz8OMu5SQuOIW4CKkCuaWnPOP26UqZocaXNZfpZH0iFLATMMH/TT8x\nay9ToHTQYE17ijdQ/EOLSwoeDV1CU1CIE3P4YfLJjvpKptly5dTevriHEzBi70Jx\n/KEPUn9Jj2gZafrUxRVhmMbm1zkeYxL3gsqRuTzRjEeeILuZhSJyCkQZyUNARxsg\nQY4DZfECgYEA+YLKUtmYTx60FS6DJ4s31TAsXY8kwhq/lB9E3GBZKDd0DPayXEeK\n4UWRQDTT6MI6fedW69FOZJ5sFLp8HQpcssb4Weq9PCpDhNTx8MCbdH3Um5QR3vfW\naKq/1XM8MDUnx5XcNYd87Aw3azvJAvOPr69as8IPnj6sKaRR9uQjbYUCgYEA6nfV\n5j0qmn0EJXZJblk4mvvjLLoWSs17j9YlrZJlJxXMDFRYtgnelv73xMxOMvcGoxn5\nifs7dpaM2x5EmA6jVU5sYaB/beZGEPWqPYGyjIwXPvUGAAv8Gbnvpp+xlSco/Dum\nIq0w+43ry5/xWh6CjfrvKV0J2bDOiJwPEdu/8iUCgYEAnBBSvL+dpN9vhFAzeOh7\nY71eAqcmNsLEUcG9MJqTKbSFwhYMOewF0iHRWHeylEPokhfBJn8kqYrtz4lVWFTC\n5o/Nh3BsLNXCpbMMIapXkeWiti1HgE9ErPMgSkJpwz18RDpYIqM8X+jEQS6D7HSr\nyxfDg+w+GJza0rEVE3hfMIECgYBw+KZ2VfhmEWBjEHhXE+QjQMR3s320MwebCUqE\nNCpKx8TWF/naVC0MwfLtvqbbBY0MHyLN6d//xpA9r3rLbRojqzKrY2KiuDYAS+3n\nzssRzxoQOozWju+8EYu30/ADdqfXyIHG6X3VZs87AGiQzGyJLmP3oR1y5y7MQa09\nJI16hQKBgHK5uwJhGa281Oo5/FwQ3uYLymbNwSGrsOJXiEu2XwJEXwVi2ELOKh4/\n03pBk3Kva3fIwEK+vCzDNnxShIQqBE76/2I1K1whOfoUehhYvKHGaXl2j70Zz9Ks\nrkGW1cx7p+yDqATDrwHBHTHFh5bUTTn8dN40n0e0W/llurpbBkJM\n-----END RSA PRIVATE KEY-----\n" +JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n" +SENDGRID_API_KEY=xxxxxxxxxxxxxxxx +MAIL_FROM=xxxxx@xxxxx.xxxx +NOTIFICATION_HUB_NAME=ntf-odms-dev +NOTIFICATION_HUB_CONNECT_STRING=XXXXXXXXXXXXXXXXXX +APP_DOMAIN=http://localhost:8081/ +STORAGE_TOKEN_EXPIRE_TIME=2 +STORAGE_ACCOUNT_NAME_US=saodmsusdev +STORAGE_ACCOUNT_NAME_AU=saodmsaudev +STORAGE_ACCOUNT_NAME_EU=saodmseudev +STORAGE_ACCOUNT_KEY_US=XXXXXXXXXXXXXXXXXXXXXXX +STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXX +STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX +STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA +STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA +STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA +ACCESS_TOKEN_LIFETIME_WEB=7200 +REFRESH_TOKEN_LIFETIME_WEB=86400 +REFRESH_TOKEN_LIFETIME_DEFAULT=2592000 +EMAIL_CONFIRM_LIFETIME=86400 +REDIS_HOST=redis-cache +REDIS_PORT=6379 +REDIS_PASSWORD=omdsredispass +ADB2C_CACHE_TTL=86400 \ No newline at end of file diff --git a/data_migration_tools/server/package-lock.json b/data_migration_tools/server/package-lock.json index 50d9f33..1c2990f 100644 --- a/data_migration_tools/server/package-lock.json +++ b/data_migration_tools/server/package-lock.json @@ -30,7 +30,8 @@ "mysql2": "^3.9.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "swagger-cli": "^4.0.4" + "swagger-cli": "^4.0.4", + "typeorm": "^0.3.10" }, "devDependencies": { "@types/express": "^4.17.17", @@ -424,17 +425,54 @@ } }, "node_modules/@azure/core-util": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", - "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.7.0.tgz", + "integrity": "sha512-Zq2i3QO6k9DA8vnm29mYM4G8IE9u1mhF1GUabVEqPNX8Lj833gdxQ2NAFxt2BZsfAL+e9cT8SyVN7dFVJ/Hf0g==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.4.2.tgz", + "integrity": "sha512-0q5DL4uyR0EZ4RXQKD8MadGH6zTIcloUoS/RVbCpNpej4pwte0xpqYxk8K97Py2RiuUvI7F4GXpoT4046VfufA==", "dependencies": { "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.5.0", + "@azure/msal-node": "^2.5.1", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", "tslib": "^2.2.0" }, "engines": { "node": ">=14.0.0" } }, +<<<<<<< HEAD +======= "node_modules/@azure/identity": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz", @@ -459,6 +497,7 @@ "node": ">=18.0.0" } }, +>>>>>>> develop "node_modules/@azure/identity/node_modules/@azure/core-tracing": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", @@ -1356,7 +1395,10 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1373,7 +1415,10 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">=12" }, @@ -1385,7 +1430,10 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">=12" }, @@ -1396,14 +1444,21 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", +<<<<<<< HEAD + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" +======= "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "peer": true +>>>>>>> develop }, "node_modules/@isaacs/cliui/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==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1420,7 +1475,10 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1435,7 +1493,10 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -2270,6 +2331,18 @@ } }, "node_modules/@nestjs/typeorm": { +<<<<<<< HEAD + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-9.0.1.tgz", + "integrity": "sha512-A2BgLIPsMtmMI0bPKEf4bmzgFPsnvHqNBx3KkvaJ7hJrBQy0OqYOb+Rr06ifblKWDWS2tUPNrAFQbZjtk3PI+g==", + "dependencies": { + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.13", +======= "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", @@ -2280,11 +2353,17 @@ "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", "reflect-metadata": "^0.1.13 || ^0.2.0", +>>>>>>> develop "rxjs": "^7.2.0", "typeorm": "^0.3.0" } }, "node_modules/@nestjs/typeorm/node_modules/uuid": { +<<<<<<< HEAD + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", +======= "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", @@ -2292,6 +2371,7 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], +>>>>>>> develop "bin": { "uuid": "dist/bin/uuid" } @@ -2576,7 +2656,10 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "optional": true, +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">=14" } @@ -2608,8 +2691,12 @@ "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", +<<<<<<< HEAD + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" +======= "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", "peer": true +>>>>>>> develop }, "node_modules/@tootallnate/once": { "version": "2.0.0", @@ -3130,9 +3217,15 @@ } }, "node_modules/acorn": { +<<<<<<< HEAD + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", +======= "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", +>>>>>>> develop "devOptional": true, "bin": { "acorn": "bin/acorn" @@ -3258,8 +3351,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" +======= "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "peer": true +>>>>>>> develop }, "node_modules/anymatch": { "version": "3.1.3", @@ -3278,7 +3375,10 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">= 6.0.0" } @@ -3773,7 +3873,10 @@ "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -4093,8 +4196,12 @@ "node_modules/dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", +<<<<<<< HEAD + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" +======= "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", "peer": true +>>>>>>> develop }, "node_modules/debug": { "version": "4.3.4", @@ -4272,8 +4379,12 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" +======= "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "peer": true +>>>>>>> develop }, "node_modules/easy-table": { "version": "1.1.0", @@ -5050,6 +5161,8 @@ "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==", +<<<<<<< HEAD +======= "peer": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -5078,6 +5191,33 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", +>>>>>>> develop + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -5310,7 +5450,10 @@ "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": "*" } @@ -5760,7 +5903,10 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -7097,9 +7243,15 @@ } }, "node_modules/long": { +<<<<<<< HEAD + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" +======= "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" +>>>>>>> develop }, "node_modules/lru-cache": { "version": "5.1.1", @@ -7247,7 +7399,10 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">=16 || 14 >=14.17" } @@ -7291,6 +7446,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/mysql2": { +<<<<<<< HEAD + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "dependencies": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", +======= "version": "3.9.1", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz", "integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==", @@ -7301,6 +7468,7 @@ "long": "^5.2.1", "lru-cache": "^8.0.0", "named-placeholders": "^1.1.3", +>>>>>>> develop "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" }, @@ -7320,6 +7488,23 @@ } }, "node_modules/mysql2/node_modules/lru-cache": { +<<<<<<< HEAD + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mysql2/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, +======= "version": "8.0.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", @@ -7327,11 +7512,15 @@ "node": ">=16.14" } }, +>>>>>>> develop "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -7678,14 +7867,21 @@ "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" +======= "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "peer": true +>>>>>>> develop }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "parse5": "^6.0.1" } @@ -7693,8 +7889,12 @@ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" +======= "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "peer": true +>>>>>>> develop }, "node_modules/parseurl": { "version": "1.3.3", @@ -7738,7 +7938,10 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -7754,7 +7957,10 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": "14 || >=16.14" } @@ -8475,7 +8681,10 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8732,7 +8941,10 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8758,7 +8970,10 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "ansi-regex": "^5.0.1" }, @@ -9069,7 +9284,10 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "any-promise": "^1.0.0" } @@ -9078,7 +9296,10 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -9351,7 +9572,10 @@ "version": "0.3.20", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "@sqltools/formatter": "^1.2.5", "app-root-path": "^3.1.0", @@ -9457,7 +9681,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "balanced-match": "^1.0.0" } @@ -9480,7 +9707,10 @@ "url": "https://feross.org/support" } ], +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -9490,7 +9720,10 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -9504,7 +9737,10 @@ "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", @@ -9526,7 +9762,10 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9541,7 +9780,10 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "bin": { "mkdirp": "dist/cjs/src/bin.js" }, @@ -9555,14 +9797,21 @@ "node_modules/typeorm/node_modules/reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" +======= "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==", "peer": true +>>>>>>> develop }, "node_modules/typeorm/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -9580,7 +9829,10 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "engines": { "node": ">=12" } @@ -9888,6 +10140,23 @@ "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==", + "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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -10159,6 +10428,8 @@ "requires": { "@azure/abort-controller": "^2.0.0", "@azure/core-util": "^1.1.0", +<<<<<<< HEAD +======= "tslib": "^2.2.0" }, "dependencies": { @@ -10183,6 +10454,7 @@ "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.0.0", "@azure/logger": "^1.0.0", +>>>>>>> develop "tslib": "^2.2.0" }, "dependencies": { @@ -10193,6 +10465,33 @@ "requires": { "tslib": "^2.2.0" } +<<<<<<< HEAD + } + } + }, + "@azure/core-client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.8.0.tgz", + "integrity": "sha512-+gHS3gEzPlhyQBMoqVPOTeNH031R5DM/xpCvz72y38C09rg4Hui/1sJS/ujoisDZbbSHyuRLVWdFlwL0pIFwbg==", + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "requires": { + "tslib": "^2.2.0" + } +======= +>>>>>>> develop }, "@azure/core-tracing": { "version": "1.0.1", @@ -10294,12 +10593,53 @@ } }, "@azure/core-util": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", - "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.7.0.tgz", + "integrity": "sha512-Zq2i3QO6k9DA8vnm29mYM4G8IE9u1mhF1GUabVEqPNX8Lj833gdxQ2NAFxt2BZsfAL+e9cT8SyVN7dFVJ/Hf0g==", + "requires": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz", + "integrity": "sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg==", + "requires": { + "tslib": "^2.2.0" + } + } + } + }, + "@azure/identity": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.4.2.tgz", + "integrity": "sha512-0q5DL4uyR0EZ4RXQKD8MadGH6zTIcloUoS/RVbCpNpej4pwte0xpqYxk8K97Py2RiuUvI7F4GXpoT4046VfufA==", "requires": { "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.5.0", + "@azure/msal-node": "^2.5.1", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", "tslib": "^2.2.0" + }, + "dependencies": { + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "requires": { + "tslib": "^2.2.0" + } + } } }, "@azure/identity": { @@ -11015,7 +11355,10 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -11028,26 +11371,41 @@ "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" +======= "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "peer": true +>>>>>>> develop }, "ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" +======= "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "peer": true +>>>>>>> develop }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", +<<<<<<< HEAD + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" +======= "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "peer": true +>>>>>>> develop }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -11058,7 +11416,10 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "ansi-regex": "^6.0.1" } @@ -11067,7 +11428,10 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -11642,6 +12006,19 @@ } }, "@nestjs/typeorm": { +<<<<<<< HEAD + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-9.0.1.tgz", + "integrity": "sha512-A2BgLIPsMtmMI0bPKEf4bmzgFPsnvHqNBx3KkvaJ7hJrBQy0OqYOb+Rr06ifblKWDWS2tUPNrAFQbZjtk3PI+g==", + "requires": { + "uuid": "8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" +======= "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", @@ -11653,6 +12030,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" +>>>>>>> develop } } }, @@ -11844,8 +12222,12 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", +<<<<<<< HEAD + "optional": true +======= "optional": true, "peer": true +>>>>>>> develop }, "@sinclair/typebox": { "version": "0.24.51", @@ -11874,8 +12256,12 @@ "@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", +<<<<<<< HEAD + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" +======= "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", "peer": true +>>>>>>> develop }, "@tootallnate/once": { "version": "2.0.0", @@ -12386,9 +12772,15 @@ } }, "acorn": { +<<<<<<< HEAD + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", +======= "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", +>>>>>>> develop "devOptional": true }, "acorn-import-assertions": { @@ -12468,8 +12860,12 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" +======= "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "peer": true +>>>>>>> develop }, "anymatch": { "version": "3.1.3", @@ -12484,8 +12880,12 @@ "app-root-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" +======= "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", "peer": true +>>>>>>> develop }, "append-field": { "version": "1.0.0", @@ -12851,7 +13251,10 @@ "version": "2.1.11", "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", @@ -13094,8 +13497,12 @@ "dayjs": { "version": "1.11.10", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", +<<<<<<< HEAD + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" +======= "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", "peer": true +>>>>>>> develop }, "debug": { "version": "4.3.4", @@ -13219,8 +13626,12 @@ "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" +======= "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "peer": true +>>>>>>> develop }, "easy-table": { "version": "1.1.0", @@ -13822,7 +14233,10 @@ "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==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -13831,8 +14245,12 @@ "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" +======= "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "peer": true +>>>>>>> develop } } }, @@ -14007,8 +14425,12 @@ "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", +<<<<<<< HEAD + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" +======= "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "peer": true +>>>>>>> develop }, "hosted-git-info": { "version": "2.8.9", @@ -14330,7 +14752,10 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "@isaacs/cliui": "^8.0.2", "@pkgjs/parseargs": "^0.11.0" @@ -15389,9 +15814,15 @@ } }, "long": { +<<<<<<< HEAD + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" +======= "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" +>>>>>>> develop }, "lru-cache": { "version": "5.1.1", @@ -15504,8 +15935,12 @@ "minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", +<<<<<<< HEAD + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" +======= "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "peer": true +>>>>>>> develop }, "mkdirp": { "version": "0.5.6", @@ -15540,6 +15975,18 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "mysql2": { +<<<<<<< HEAD + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", + "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "requires": { + "denque": "^2.0.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", +======= "version": "3.9.1", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz", "integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==", @@ -15550,6 +15997,7 @@ "long": "^5.2.1", "lru-cache": "^8.0.0", "named-placeholders": "^1.1.3", +>>>>>>> develop "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" }, @@ -15563,9 +16011,23 @@ } }, "lru-cache": { +<<<<<<< HEAD + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" +======= "version": "8.0.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" +>>>>>>> develop } } }, @@ -15573,7 +16035,10 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -15832,14 +16297,21 @@ "parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" +======= "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "peer": true +>>>>>>> develop }, "parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "parse5": "^6.0.1" }, @@ -15847,8 +16319,12 @@ "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" +======= "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "peer": true +>>>>>>> develop } } }, @@ -15882,7 +16358,10 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -15891,8 +16370,12 @@ "lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", +<<<<<<< HEAD + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" +======= "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "peer": true +>>>>>>> develop } } }, @@ -16439,7 +16922,10 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -16655,7 +17141,10 @@ "version": "npm:string-width@4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16674,7 +17163,10 @@ "version": "npm:strip-ansi@6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "ansi-regex": "^5.0.1" } @@ -16886,7 +17378,10 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "any-promise": "^1.0.0" } @@ -16895,7 +17390,10 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "thenify": ">= 3.1.0 < 4" } @@ -17072,7 +17570,10 @@ "version": "0.3.20", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "@sqltools/formatter": "^1.2.5", "app-root-path": "^3.1.0", @@ -17095,7 +17596,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "balanced-match": "^1.0.0" } @@ -17104,7 +17608,10 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -17114,7 +17621,10 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -17125,7 +17635,10 @@ "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "foreground-child": "^3.1.0", "jackspeak": "^2.3.5", @@ -17138,7 +17651,10 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "brace-expansion": "^2.0.1" } @@ -17146,20 +17662,31 @@ "mkdirp": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", +<<<<<<< HEAD + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==" +======= "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", "peer": true +>>>>>>> develop }, "reflect-metadata": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" +======= "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==", "peer": true +>>>>>>> develop }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -17173,8 +17700,12 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", +<<<<<<< HEAD + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" +======= "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "peer": true +>>>>>>> develop } } }, @@ -17387,7 +17918,10 @@ "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", +<<<<<<< HEAD +======= "peer": true, +>>>>>>> develop "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", diff --git a/data_migration_tools/server/package.json b/data_migration_tools/server/package.json index fee15e8..2b445ea 100644 --- a/data_migration_tools/server/package.json +++ b/data_migration_tools/server/package.json @@ -31,6 +31,7 @@ "@nestjs/core": "^9.3.9", "@nestjs/platform-express": "^9.4.1", "@nestjs/serve-static": "^3.0.1", + "@nestjs/typeorm": "^9.0.1", "@nestjs/swagger": "^6.2.1", "@nestjs/testing": "^9.3.9", "@nestjs/typeorm": "^10.0.2", @@ -44,7 +45,9 @@ "mysql2": "^3.9.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "swagger-cli": "^4.0.4" + "swagger-cli": "^4.0.4", + "typeorm": "^0.3.10", + "mysql2": "^2.3.3" }, "devDependencies": { "@types/express": "^4.17.17", diff --git a/data_migration_tools/server/src/app.module.ts b/data_migration_tools/server/src/app.module.ts index 23a4c76..8089b78 100644 --- a/data_migration_tools/server/src/app.module.ts +++ b/data_migration_tools/server/src/app.module.ts @@ -1,15 +1,29 @@ import { MiddlewareConsumer, Module } from "@nestjs/common"; import { ServeStaticModule } from "@nestjs/serve-static"; import { ConfigModule, ConfigService } from "@nestjs/config"; +import { TypeOrmModule } from "@nestjs/typeorm"; import { join } from "path"; import { LoggerMiddleware } from "./common/loggerMiddleware"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { DeleteModule } from "./features/delete/delete.module"; import { AdB2cModule } from "./gateways/adb2c/adb2c.module"; +import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module"; +import { RegisterController } from "./features/register/register.controller"; +import { RegisterService } from "./features/register/register.service"; +import { RegisterModule } from "./features/register/register.module"; +import { AccountsRepositoryModule } from "./repositories/accounts/accounts.repository.module"; +import { UsersRepositoryModule } from "./repositories/users/users.repository.module"; +import { SortCriteriaRepositoryModule } from "./repositories/sort_criteria/sort_criteria.repository.module"; +import { LicensesRepositoryModule } from "./repositories/licenses/licenses.repository.module"; +import { WorktypesRepositoryModule } from "./repositories/worktypes/worktypes.repository.module"; +import { AccountsController } from "./features/accounts/accounts.controller"; +import { AccountsService } from "./features/accounts/accounts.service"; +import { AccountsModule } from "./features/accounts/accounts.module"; +import { UsersController } from "./features/users/users.controller"; +import { UsersService } from "./features/users/users.service"; +import { UsersModule } from "./features/users/users.module"; +import { DeleteModule } from "./features/delete/delete.module"; import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module"; import { DeleteController } from "./features/delete/delete.controller"; import { DeleteService } from "./features/delete/delete.service"; -import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module"; @Module({ imports: [ @@ -20,6 +34,18 @@ import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module"; envFilePath: [".env.local", ".env"], isGlobal: true, }), + AdB2cModule, + AccountsModule, + UsersModule, + RegisterModule, + AccountsRepositoryModule, + UsersRepositoryModule, + SortCriteriaRepositoryModule, + LicensesRepositoryModule, + WorktypesRepositoryModule, + BlobstorageModule, + DeleteModule, + DeleteRepositoryModule, TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ @@ -34,13 +60,9 @@ import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module"; }), inject: [ConfigService], }), - DeleteModule, - AdB2cModule, - BlobstorageModule, - DeleteRepositoryModule, ], - controllers: [DeleteController], - providers: [DeleteService], + controllers: [RegisterController, AccountsController, UsersController, DeleteController], + providers: [RegisterService, AccountsService, UsersService, DeleteService], }) export class AppModule { configure(consumer: MiddlewareConsumer) { diff --git a/data_migration_tools/server/src/common/error/code.ts b/data_migration_tools/server/src/common/error/code.ts new file mode 100644 index 0000000..3c488da --- /dev/null +++ b/data_migration_tools/server/src/common/error/code.ts @@ -0,0 +1,70 @@ +/* +エラーコード作成方針 +E+6桁(数字)で構成する。 +- 1~2桁目の値は種類(業務エラー、システムエラー...) +- 3~4桁目の値は原因箇所(トークン、DB、...) +- 5~6桁目の値は任意の重複しない値 +ex) +E00XXXX : システムエラー(通信エラーやDB接続失敗など) +E01XXXX : 業務エラー +EXX00XX : 内部エラー(内部プログラムのエラー) +EXX01XX : トークンエラー(トークン認証関連) +EXX02XX : DBエラー(DB関連) +EXX03XX : ADB2Cエラー(DB関連) +*/ +export const ErrorCodes = [ + 'E009999', // 汎用エラー + 'E000101', // トークン形式不正エラー + 'E000102', // トークン有効期限切れエラー + 'E000103', // トークン非アクティブエラー + 'E000104', // トークン署名エラー + 'E000105', // トークン発行元エラー + 'E000106', // トークンアルゴリズムエラー + 'E000107', // トークン不足エラー + 'E000108', // トークン権限エラー + 'E000301', // ADB2Cへのリクエスト上限超過エラー + 'E000401', // IPアドレス未設定エラー + 'E000501', // リクエストID未設定エラー + 'E010001', // パラメータ形式不正エラー + 'E010201', // 未認証ユーザエラー + 'E010202', // 認証済ユーザエラー + 'E010203', // 管理ユーザ権限エラー + 'E010204', // ユーザ不在エラー + 'E010205', // DBのRoleが想定外の値エラー + 'E010206', // DBのTierが想定外の値エラー + 'E010207', // ユーザーのRole変更不可エラー + 'E010208', // ユーザーの暗号化パスワード不足エラー + 'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー + 'E010301', // メールアドレス登録済みエラー + 'E010302', // authorId重複エラー + 'E010401', // PONumber重複エラー + 'E010501', // アカウント不在エラー + 'E010502', // アカウント情報変更不可エラー + 'E010503', // 代行操作不許可エラー + 'E010504', // アカウントロックエラー + 'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない) + 'E010602', // タスク変更権限不足エラー + 'E010603', // タスク不在エラー + 'E010701', // Blobファイル不在エラー + 'E010801', // ライセンス不在エラー + 'E010802', // ライセンス取り込み済みエラー + 'E010803', // ライセンス発行済みエラー + 'E010804', // ライセンス不足エラー + 'E010805', // ライセンス有効期限切れエラー + 'E010806', // ライセンス割り当て不可エラー + 'E010807', // ライセンス割り当て解除済みエラー + 'E010808', // ライセンス注文キャンセル不可エラー + 'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合) + 'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合) + 'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) + 'E010812', // ライセンス未割当エラー + 'E010908', // タイピストグループ不在エラー + 'E010909', // タイピストグループ名重複エラー + 'E011001', // ワークタイプ重複エラー + 'E011002', // ワークタイプ登録上限超過エラー + 'E011003', // ワークタイプ不在エラー + 'E011004', // ワークタイプ使用中エラー + 'E012001', // テンプレートファイル不在エラー + 'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー + 'E013002', // ワークフロー不在エラー +] as const; diff --git a/data_migration_tools/server/src/common/error/makeErrorResponse.ts b/data_migration_tools/server/src/common/error/makeErrorResponse.ts new file mode 100644 index 0000000..0a677b4 --- /dev/null +++ b/data_migration_tools/server/src/common/error/makeErrorResponse.ts @@ -0,0 +1,10 @@ +import { errors } from './message'; +import { ErrorCodeType, ErrorResponse } from './types/types'; + +export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => { + const msg = errors[errorcode]; + return { + code: errorcode, + message: msg, + }; +}; diff --git a/data_migration_tools/server/src/common/error/message.ts b/data_migration_tools/server/src/common/error/message.ts new file mode 100644 index 0000000..9383694 --- /dev/null +++ b/data_migration_tools/server/src/common/error/message.ts @@ -0,0 +1,59 @@ +import { Errors } from './types/types'; + +// エラーコードとメッセージ対応表 +export const errors: Errors = { + E009999: 'Internal Server Error.', + E000101: 'Token invalid format Error.', + E000102: 'Token expired Error.', + E000103: 'Token not before Error', + E000104: 'Token invalid signature Error.', + E000105: 'Token invalid issuer Error.', + E000106: 'Token invalid algorithm Error.', + E000107: 'Token is not exist Error.', + E000108: 'Token authority failed Error.', + E000301: 'ADB2C request limit exceeded Error', + E000401: 'IP address not found Error.', + E000501: 'Request ID not found Error.', + E010001: 'Param invalid format Error.', + E010201: 'Email not verified user Error.', + E010202: 'Email already verified user Error.', + E010203: 'Administrator Permissions Error.', + E010204: 'User not Found Error.', + E010205: 'Role from DB is unexpected value Error.', + E010206: 'Tier from DB is unexpected value Error.', + E010207: 'User role change not allowed Error.', + E010208: 'User encryption password not found Error.', + E010209: 'Accepted term not latest Error.', + E010301: 'This email user already created Error', + E010302: 'This AuthorId already used Error', + E010401: 'This PoNumber already used Error', + E010501: 'Account not Found Error.', + E010502: 'Account information cannot be changed Error.', + E010503: 'Delegation not allowed Error.', + E010504: 'Account is locked Error.', + E010601: 'Task is not Editable Error', + E010602: 'No task edit permissions Error', + E010603: 'Task not found Error.', + E010701: 'File not found in Blob Storage Error.', + E010801: 'License not exist Error', + E010802: 'License already activated Error', + E010803: 'License already issued Error', + E010804: 'License shortage Error', + E010805: 'License is expired Error', + E010806: 'License is unavailable Error', + E010807: 'License is already deallocated Error', + E010808: 'Order cancel failed Error', + E010809: 'Already license order status changed Error', + E010810: 'Cancellation period expired error', + E010811: 'Already license allocated Error', + E010812: 'License not allocated Error', + E010908: 'Typist Group not exist Error', + E010909: 'Typist Group name already exist Error', + E011001: 'This WorkTypeID already used Error', + E011002: 'WorkTypeID create limit exceeded Error', + E011003: 'WorkTypeID not found Error', + E011004: 'WorkTypeID is in use Error', + E012001: 'Template file not found Error', + E013001: 'AuthorId and WorktypeId pair already exists Error', + E013002: 'Workflow not found Error', +}; diff --git a/data_migration_tools/server/src/common/error/types/types.ts b/data_migration_tools/server/src/common/error/types/types.ts new file mode 100644 index 0000000..8746924 --- /dev/null +++ b/data_migration_tools/server/src/common/error/types/types.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ErrorCodes } from '../code'; + +export class ErrorResponse { + @ApiProperty() + message: string; + @ApiProperty() + code: string; +} + +export type ErrorCodeType = (typeof ErrorCodes)[number]; + +export type Errors = { + [P in ErrorCodeType]: string; +}; diff --git a/data_migration_tools/server/src/common/log/context.ts b/data_migration_tools/server/src/common/log/context.ts new file mode 100644 index 0000000..1b887c6 --- /dev/null +++ b/data_migration_tools/server/src/common/log/context.ts @@ -0,0 +1,32 @@ +import { Request } from 'express'; +import { Context } from './types'; + +export const makeContext = ( + externalId: string, + requestId: string, + delegationId?: string, +): Context => { + return new Context(externalId, requestId, delegationId); +}; + +// リクエストヘッダーからrequestIdを取得する +export const retrieveRequestId = (req: Request): string | undefined => { + return req.header('x-request-id'); +}; + +/** + * リクエストのIPアドレスを取得します + * @param {Request} + * @return {string | undefined} + */ +export const retrieveIp = (req: Request): string | undefined => { + // ローカル環境では直近の送信元IPを取得する + if (process.env.STAGE === 'local') { + return req.ip; + } + const ip = req.header('x-forwarded-for'); + if (typeof ip === 'string') { + return ip; + } + return undefined; +}; diff --git a/data_migration_tools/server/src/common/log/index.ts b/data_migration_tools/server/src/common/log/index.ts new file mode 100644 index 0000000..7b0bf35 --- /dev/null +++ b/data_migration_tools/server/src/common/log/index.ts @@ -0,0 +1,4 @@ +import { Context } from './types'; +import { makeContext, retrieveRequestId, retrieveIp } from './context'; + +export { Context, makeContext, retrieveRequestId, retrieveIp }; diff --git a/data_migration_tools/server/src/common/log/types.ts b/data_migration_tools/server/src/common/log/types.ts new file mode 100644 index 0000000..6f56bc1 --- /dev/null +++ b/data_migration_tools/server/src/common/log/types.ts @@ -0,0 +1,34 @@ +export class Context { + /** + * APIの操作ユーザーを追跡するためのID + */ + trackingId: string; + /** + * APIの操作ユーザーのIPアドレス + */ + ip: string; + /** + * ユーザーの操作を一意に識別するためのID + */ + requestId: string; + /** + * APIの代行操作ユーザーを追跡するためのID + */ + delegationId?: string | undefined; + + constructor(externalId: string, requestId: string, delegationId?: string) { + this.trackingId = externalId; + this.delegationId = delegationId; + this.requestId = requestId; + } + /** + * ログにユーザーを特定する情報を出力する + */ + getTrackingId(): string { + if (this.delegationId) { + return `${this.requestId}_${this.trackingId} by ${this.delegationId}`; + } else { + return `${this.requestId}_${this.trackingId}`; + } + } +} diff --git a/data_migration_tools/server/src/common/password/index.ts b/data_migration_tools/server/src/common/password/index.ts new file mode 100644 index 0000000..8960a26 --- /dev/null +++ b/data_migration_tools/server/src/common/password/index.ts @@ -0,0 +1,3 @@ +import { makePassword } from './password'; + +export { makePassword }; diff --git a/data_migration_tools/server/src/common/password/password.ts b/data_migration_tools/server/src/common/password/password.ts new file mode 100644 index 0000000..f68bb3c --- /dev/null +++ b/data_migration_tools/server/src/common/password/password.ts @@ -0,0 +1,35 @@ +export const makePassword = (): string => { + // パスワードの文字数を決定 + const passLength = 8; + + // パスワードに使用可能な文字を決定(今回はアルファベットの大文字と小文字 + 数字 + symbolsの記号) + const lowerCase = 'abcdefghijklmnopqrstuvwxyz'; + const upperCase = lowerCase.toLocaleUpperCase(); + const numbers = '0123456789'; + const symbols = '@#$%^&*\\-_+=[]{}|:\',.?/`~"();!'; + const chars = lowerCase + upperCase + numbers + symbols; + + // 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ + const charaTypePattern = + /^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/; + + // autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる + let valid = false; + let autoGeneratedPassword: string = ''; + + while (!valid) { + // パスワードをランダムに決定 + while (autoGeneratedPassword.length < passLength) { + // 上で決定したcharsの中からランダムに1文字ずつ追加 + const index = Math.floor(Math.random() * chars.length); + autoGeneratedPassword += chars[index]; + } + + // パスワードが上で決定した条件をすべて満たしているかチェック + // 条件を満たすまでループ + valid = + autoGeneratedPassword.length == passLength && + charaTypePattern.test(autoGeneratedPassword); + } + return autoGeneratedPassword; +}; diff --git a/data_migration_tools/server/src/common/repository/index.ts b/data_migration_tools/server/src/common/repository/index.ts new file mode 100644 index 0000000..b3e21fa --- /dev/null +++ b/data_migration_tools/server/src/common/repository/index.ts @@ -0,0 +1,143 @@ +import { + ObjectLiteral, + Repository, + EntityTarget, + UpdateResult, + DeleteResult, + UpdateQueryBuilder, + Brackets, + FindOptionsWhere, +} from 'typeorm'; +import { Context } from '../log'; + +/** + * VS Code上で型解析エラーが発生するため、typeorm内の型定義と同一の型定義をここに記述する + */ +type QueryDeepPartialEntity = _QueryDeepPartialEntity< + ObjectLiteral extends T ? unknown : T +>; +type _QueryDeepPartialEntity = { + [P in keyof T]?: + | (T[P] extends Array + ? Array<_QueryDeepPartialEntity> + : T[P] extends ReadonlyArray + ? ReadonlyArray<_QueryDeepPartialEntity> + : _QueryDeepPartialEntity) + | (() => string); +}; + +interface InsertEntityOptions { + id: number; +} + +const insertEntity = async ( + entity: EntityTarget, + repository: Repository, + value: QueryDeepPartialEntity, + isCommentOut: boolean, + context: Context, +): Promise => { + let query = repository.createQueryBuilder().insert().into(entity); + if (isCommentOut) { + query = query.comment( + `${context.getTrackingId()}_${new Date().toUTCString()}`, + ); + } + const result = await query.values(value).execute(); + // result.identifiers[0].idがnumber型でない場合はエラー + if (typeof result.identifiers[0].id !== 'number') { + throw new Error('Failed to insert entity'); + } + const where: FindOptionsWhere = { id: result.identifiers[0].id } as T; + + // 結果をもとにセレクトする + const inserted = await repository.findOne({ + where, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + if (!inserted) { + throw new Error('Failed to insert entity'); + } + return inserted; +}; + +const insertEntities = async ( + entity: EntityTarget, + repository: Repository, + values: QueryDeepPartialEntity[], + isCommentOut: boolean, + context: Context, +): Promise => { + let query = repository.createQueryBuilder().insert().into(entity); + if (isCommentOut) { + query = query.comment( + `${context.getTrackingId()}_${new Date().toUTCString()}`, + ); + } + const result = await query.values(values).execute(); + + // 挿入するレコードが0で、結果も0であれば、からの配列を返す + if (values.length === 0 && result.identifiers.length === 0) { + return []; + } + + // 挿入するレコード数と挿入されたレコード数が一致しない場合はエラー + if (result.identifiers.length !== values.length) { + throw new Error('Failed to insert entities'); + } + const where: FindOptionsWhere[] = result.identifiers.map((i) => { + // idがnumber型でない場合はエラー + if (typeof i.id !== 'number') { + throw new Error('Failed to insert entities'); + } + return { id: i.id } as T; + }); + + // 結果をもとにセレクトする + const inserted = await repository.find({ + where, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + if (!inserted) { + throw new Error('Failed to insert entity'); + } + return inserted; +}; + +const updateEntity = async ( + repository: Repository, + criteria: + | string + | ((qb: UpdateQueryBuilder) => string) + | Brackets + | ObjectLiteral + | ObjectLiteral[], + values: QueryDeepPartialEntity, + isCommentOut: boolean, + context: Context, +): Promise => { + let query = repository.createQueryBuilder().update(); + if (isCommentOut) { + query = query.comment( + `${context.getTrackingId()}_${new Date().toUTCString()}`, + ); + } + return await query.set(values).where(criteria).execute(); +}; + +const deleteEntity = async ( + repository: Repository, + criteria: string | Brackets | ObjectLiteral | ObjectLiteral[], + isCommentOut: boolean, + context: Context, +): Promise => { + let query = repository.createQueryBuilder().delete(); + if (isCommentOut) { + query = query.comment( + `${context.getTrackingId()}_${new Date().toUTCString()}`, + ); + } + return await query.where(criteria).execute(); +}; + +export { insertEntity, insertEntities, updateEntity, deleteEntity }; diff --git a/data_migration_tools/server/src/common/types/role/index.ts b/data_migration_tools/server/src/common/types/role/index.ts new file mode 100644 index 0000000..d29201f --- /dev/null +++ b/data_migration_tools/server/src/common/types/role/index.ts @@ -0,0 +1,10 @@ +import { ADMIN_ROLES, USER_ROLES } from '../../../constants'; + +/** + * Token.roleに配置されうる文字列リテラル型 + */ +export type Roles = + | (typeof ADMIN_ROLES)[keyof typeof ADMIN_ROLES] + | (typeof USER_ROLES)[keyof typeof USER_ROLES]; + +export type UserRoles = (typeof USER_ROLES)[keyof typeof USER_ROLES]; diff --git a/data_migration_tools/server/src/common/types/sort/index.ts b/data_migration_tools/server/src/common/types/sort/index.ts new file mode 100644 index 0000000..b505e50 --- /dev/null +++ b/data_migration_tools/server/src/common/types/sort/index.ts @@ -0,0 +1,27 @@ +import { + TASK_LIST_SORTABLE_ATTRIBUTES, + SORT_DIRECTIONS, +} from '../../../constants'; + +export type TaskListSortableAttribute = + (typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number]; + +export type SortDirection = (typeof SORT_DIRECTIONS)[number]; + +export const isTaskListSortableAttribute = ( + arg: string, +): arg is TaskListSortableAttribute => { + const param = arg as TaskListSortableAttribute; + if (TASK_LIST_SORTABLE_ATTRIBUTES.includes(param)) { + return true; + } + return false; +}; + +export const isSortDirection = (arg: string): arg is SortDirection => { + const param = arg as SortDirection; + if (SORT_DIRECTIONS.includes(param)) { + return true; + } + return false; +}; diff --git a/data_migration_tools/server/src/common/types/sort/util.ts b/data_migration_tools/server/src/common/types/sort/util.ts new file mode 100644 index 0000000..2ccf33d --- /dev/null +++ b/data_migration_tools/server/src/common/types/sort/util.ts @@ -0,0 +1,11 @@ +import { SortDirection, TaskListSortableAttribute } from '.'; + +export const getDirection = (direction: SortDirection): SortDirection => { + return direction; +}; + +export const getTaskListSortableAttribute = ( + TaskListSortableAttribute: TaskListSortableAttribute, +): TaskListSortableAttribute => { + return TaskListSortableAttribute; +}; diff --git a/data_migration_tools/server/src/common/types/types.ts b/data_migration_tools/server/src/common/types/types.ts new file mode 100644 index 0000000..7a7854e --- /dev/null +++ b/data_migration_tools/server/src/common/types/types.ts @@ -0,0 +1,155 @@ +export class AccountsInputFile { + accountId: number; + type: number; + companyName: string; + country: string; + dealerAccountId?: number; + adminName: string; + adminMail: string; + userId: number; +} + +export class UsersInputFile { + accountId: number; + userId: number; + name: string; + role: string; + authorId: string; + email: string; +} + +export class LicensesInputFile { + expiry_date: string; + account_id: number; + type: string; + status: string; + allocated_user_id?: number; +} + +export class WorktypesInputFile { + account_id: number; + custom_worktype_id: string; +} + +export class CardLicensesInputFile { + license_id: number; + issue_id: number; + card_license_key: string; + activated_at?: string; + created_at?: string; + created_by?: string; + updated_at?: string; + updated_by?: string; +} + +export function isAccountsInputFileArray(obj: any): obj is AccountsInputFile[] { + return Array.isArray(obj) && obj.every((item) => isAccountsInputFile(item)); +} +export function isAccountsInputFile(obj: any): obj is AccountsInputFile { + return ( + typeof obj === "object" && + obj !== null && + "accountId" in obj && + typeof obj.accountId === "number" && + "type" in obj && + typeof obj.type === "number" && + "companyName" in obj && + typeof obj.companyName === "string" && + "country" in obj && + typeof obj.country === "string" && + ("dealerAccountId" in obj + ? typeof obj.dealerAccountId === "number" + : true) && + "adminName" in obj && + typeof obj.adminName === "string" && + "adminMail" in obj && + typeof obj.adminMail === "string" && + "userId" in obj && + typeof obj.userId === "number" + ); +} + +export function isUsersInputFileArray(obj: any): obj is UsersInputFile[] { + return Array.isArray(obj) && obj.every((item) => isUsersInputFile(item)); +} +export function isUsersInputFile(obj: any): obj is UsersInputFile { + return ( + typeof obj === "object" && + obj !== null && + "accountId" in obj && + "userId" in obj && + "name" in obj && + "role" in obj && + "authorId" in obj && + "email" in obj && + typeof obj.accountId === "number" && + typeof obj.userId === "number" && + typeof obj.name === "string" && + typeof obj.role === "string" && + typeof obj.authorId === "string" && + typeof obj.email === "string" + ); +} + +export function isLicensesInputFileArray(obj: any): obj is LicensesInputFile[] { + return Array.isArray(obj) && obj.every((item) => isLicensesInputFile(item)); +} +export function isLicensesInputFile(obj: any): obj is LicensesInputFile { + return ( + typeof obj === "object" && + obj !== null && + "expiry_date" in obj && + "account_id" in obj && + "type" in obj && + "status" in obj && + typeof obj.expiry_date === "string" && + typeof obj.account_id === "number" && + typeof obj.type === "string" && + typeof obj.status === "string" && + (obj.allocated_user_id === null || + typeof obj.allocated_user_id === "number") + ); +} + +export function isWorktypesInputFileArray( + obj: any +): obj is WorktypesInputFile[] { + return Array.isArray(obj) && obj.every((item) => isWorktypesInputFile(item)); +} +export function isWorktypesInputFile(obj: any): obj is WorktypesInputFile { + return ( + typeof obj === "object" && + obj !== null && + "account_id" in obj && + "custom_worktype_id" in obj && + typeof obj.account_id === "number" && + typeof obj.custom_worktype_id === "string" + ); +} + +export function isCardLicensesInputFileArray( + obj: any +): obj is CardLicensesInputFile[] { + return ( + Array.isArray(obj) && obj.every((item) => isCardLicensesInputFile(item)) + ); +} +export function isCardLicensesInputFile( + obj: any +): obj is CardLicensesInputFile { + return ( + typeof obj === "object" && + obj !== null && + "license_id" in obj && + "issue_id" in obj && + "card_license_key" in obj && + typeof obj.license_id === "number" && + typeof obj.issue_id === "number" && + typeof obj.card_license_key === "string" && + (obj.activated_at === null || typeof obj.activated_at === "string") && + (obj.created_at === null || typeof obj.created_at === "string") && + (obj.created_by === null || typeof obj.created_by === "string") && + (obj.updated_at === null || typeof obj.updated_at === "string") && + (obj.updated_by === null || typeof obj.updated_by === "string") + ); +} diff --git a/data_migration_tools/server/src/constants/index.ts b/data_migration_tools/server/src/constants/index.ts index e363afc..4acb160 100644 --- a/data_migration_tools/server/src/constants/index.ts +++ b/data_migration_tools/server/src/constants/index.ts @@ -1,3 +1,20 @@ +/** + * 階層 + * @const {number} + */ +export const TIERS = { + //OMDS東京 + TIER1: 1, + //OMDS現地法人 + TIER2: 2, + //代理店 + TIER3: 3, + //販売店 + TIER4: 4, + //エンドユーザー + TIER5: 5, +} as const; + /** * 音声ファイルをEast USに保存する国リスト * @const {number} @@ -52,6 +69,213 @@ export const BLOB_STORAGE_REGION_EU = [ "GB", ]; +/** + * 管理ロール + * @const {string[]} + */ +export const ADMIN_ROLES = { + ADMIN: "admin", + STANDARD: "standard", +} as const; + +/** + * ロール + * @const {string[]} + */ +export const USER_ROLES = { + NONE: "none", + AUTHOR: "author", + TYPIST: "typist", +} as const; + +/** + * ロールのソート順 + * @const {string[]} + */ +export const USER_ROLE_ORDERS = [ + USER_ROLES.AUTHOR, + USER_ROLES.TYPIST, + USER_ROLES.NONE, +] as string[]; + +/** + * ライセンス注文状態 + * @const {string[]} + */ +export const LICENSE_ISSUE_STATUS = { + ISSUE_REQUESTING: "Issue Requesting", + ISSUED: "Issued", + CANCELED: "Order Canceled", +}; + +/** + * ライセンス種別 + * @const {string[]} + */ +export const LICENSE_TYPE = { + TRIAL: "TRIAL", + NORMAL: "NORMAL", + CARD: "CARD", +} as const; +/** + * ライセンス状態 + * @const {string[]} + */ +export const LICENSE_ALLOCATED_STATUS = { + UNALLOCATED: "Unallocated", + ALLOCATED: "Allocated", + REUSABLE: "Reusable", + DELETED: "Deleted", +} as const; +/** + * 切り替え元種別 + * @const {string[]} + */ +export const SWITCH_FROM_TYPE = { + NONE: "NONE", + CARD: "CARD", + TRIAL: "TRIAL", +} as const; + +/** + * ライセンスの期限切れが近いと見なす日数のしきい値 + * @const {number} + */ +export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14; + +/** + * ライセンスの有効期間 + * @const {number} + */ +export const LICENSE_EXPIRATION_DAYS = 365; + +/** + * タイムゾーンを加味したライセンスの有効期間(8時間) + * @const {number} + */ +export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8; + +/** + * カードライセンスの桁数 + * @const {number} + */ +export const CARD_LICENSE_LENGTH = 20; + +/** + * 音声ファイルに紐づくオプションアイテムの数 + * @const {string} + */ +export const OPTION_ITEM_NUM = 10; + +/** + * 文字起こしタスクのステータス + * @const {string[]} + */ +export const TASK_STATUS = { + UPLOADED: "Uploaded", + PENDING: "Pending", + IN_PROGRESS: "InProgress", + FINISHED: "Finished", + BACKUP: "Backup", +} as const; + +/** + * タスク一覧でソート可能な属性の一覧 + */ +export const TASK_LIST_SORTABLE_ATTRIBUTES = [ + "JOB_NUMBER", + "STATUS", + "ENCRYPTION", + "AUTHOR_ID", + "WORK_TYPE", + "FILE_NAME", + "FILE_LENGTH", + "FILE_SIZE", + "RECORDING_STARTED_DATE", + "RECORDING_FINISHED_DATE", + "UPLOAD_DATE", + "TRANSCRIPTION_STARTED_DATE", + "TRANSCRIPTION_FINISHED_DATE", +] as const; + +/** + * タスク一覧のソート条件(昇順・降順) + */ +export const SORT_DIRECTIONS = ["ASC", "DESC"] as const; + +/** + * 通知タグの最大個数 + * NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで + * https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions + */ +export const TAG_MAX_COUNT = 20; + +/** + * 通知のプラットフォーム種別文字列 + */ +export const PNS = { + WNS: "wns", + APNS: "apns", +}; + +/** + * ユーザーのライセンスの有効期限の状態 + */ +export const USER_LICENSE_EXPIRY_STATUS = { + NORMAL: "Normal", + NO_LICENSE: "NoLicense", + ALERT: "Alert", + RENEW: "Renew", +}; + +/** + *トライアルライセンスの有効期限(日数) + * @const {number} + */ +export const TRIAL_LICENSE_EXPIRATION_DAYS = 30; + +/** + * ライセンスの発行数 + * @const {number} + */ +export const TRIAL_LICENSE_ISSUE_NUM = 100; + +/** + * worktypeの最大登録数 + * @const {number} + */ +export const WORKTYPE_MAX_COUNT = 20; + +/** + * worktypeのDefault値の取りうる値 + **/ +export const OPTION_ITEM_VALUE_TYPE = { + DEFAULT: "Default", + BLANK: "Blank", + LAST_INPUT: "LastInput", +} as const; + +/** + * オプションアイテムのタイプ文字列と数値の対応 + **/ +export const OPTION_ITEM_VALUE_TYPE_NUMBER: { + type: string; + value: number; +}[] = [ + { + type: OPTION_ITEM_VALUE_TYPE.BLANK, + value: 1, + }, + { + type: OPTION_ITEM_VALUE_TYPE.DEFAULT, + value: 2, + }, + { + type: OPTION_ITEM_VALUE_TYPE.LAST_INPUT, + value: 3, + }, +]; + /** * ADB2Cユーザのidentity.signInType * @const {string[]} @@ -60,8 +284,52 @@ export const ADB2C_SIGN_IN_TYPE = { EMAILADDRESS: "emailAddress", } as const; +/** + * MANUAL_RECOVERY_REQUIRED + * @const {string} + */ +export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]"; + +/** + * 利用規約種別 + * @const {string[]} + */ +export const TERM_TYPE = { + EULA: "EULA", + DPA: "DPA", + PRIVACY_NOTICE: "PrivacyNotice", +} as const; + +/** + * 音声ファイルのフォーマット + * @const {string} + */ +export const USER_AUDIO_FORMAT = "DS2(QP)"; + +/** + * ユニットテスト実行をしている場合のNODE_ENVの値 + * @const {string[]} + */ +export const NODE_ENV_TEST = "test"; + +/** + * ユーザに対するライセンスの状態 + * @const {string[]} + */ +export const USER_LICENSE_STATUS = { + UNALLOCATED: "unallocated", + ALLOCATED: "allocated", + EXPIRED: "expired", +} as const; + /** * AutoIncrementの初期値 * @const {number} */ export const AUTO_INCREMENT_START = 853211; + +/** + * 移行データ登録時のsleep間隔 + * @const {number} + */ +export const MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC = 13; diff --git a/data_migration_tools/server/src/features/accounts/accounts.controller.ts b/data_migration_tools/server/src/features/accounts/accounts.controller.ts new file mode 100644 index 0000000..97ae0b9 --- /dev/null +++ b/data_migration_tools/server/src/features/accounts/accounts.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Logger } from "@nestjs/common"; +import { ApiTags } from "@nestjs/swagger"; +import { AccountsService } from "./accounts.service"; + +@ApiTags("accounts") +@Controller("accounts") +export class AccountsController { + private readonly logger = new Logger(AccountsController.name); + constructor( + private readonly accountService: AccountsService //private readonly cryptoService: CryptoService, + ) {} +} diff --git a/data_migration_tools/server/src/features/accounts/accounts.module.ts b/data_migration_tools/server/src/features/accounts/accounts.module.ts new file mode 100644 index 0000000..cb6db2b --- /dev/null +++ b/data_migration_tools/server/src/features/accounts/accounts.module.ts @@ -0,0 +1,19 @@ +import { Module } from "@nestjs/common"; +import { UsersRepositoryModule } from "../../repositories/users/users.repository.module"; +import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module"; +import { AccountsController } from "./accounts.controller"; +import { AccountsService } from "./accounts.service"; +import { AdB2cModule } from "../../gateways/adb2c/adb2c.module"; +import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module"; + +@Module({ + imports: [ + AccountsRepositoryModule, + UsersRepositoryModule, + AdB2cModule, + BlobstorageModule, + ], + controllers: [AccountsController], + providers: [AccountsService], +}) +export class AccountsModule {} diff --git a/data_migration_tools/server/src/features/accounts/accounts.service.ts b/data_migration_tools/server/src/features/accounts/accounts.service.ts new file mode 100644 index 0000000..4a7dd65 --- /dev/null +++ b/data_migration_tools/server/src/features/accounts/accounts.service.ts @@ -0,0 +1,227 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service"; +import { + AdB2cService, + ConflictError, + isConflictError, +} from "../../gateways/adb2c/adb2c.service"; +import { Account } from "../../repositories/accounts/entity/account.entity"; +import { User } from "../../repositories/users/entity/user.entity"; +import { MANUAL_RECOVERY_REQUIRED } from "../../constants"; +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +import { Context } from "../../common/log"; +import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service"; + +@Injectable() +export class AccountsService { + constructor( + private readonly accountRepository: AccountsRepositoryService, + private readonly adB2cService: AdB2cService, + private readonly blobStorageService: BlobstorageService + ) {} + private readonly logger = new Logger(AccountsService.name); + + /** + * アカウント情報をDBに作成する + * @param companyName + * @param country + * @param [dealerAccountId] + * @returns account + */ + async createAccount( + context: Context, + companyName: string, + country: string, + dealerAccountId: number | undefined, + email: string, + password: string, + username: string, + role: string, + acceptedEulaVersion: string, + acceptedPrivacyNoticeVersion: string, + acceptedDpaVersion: string, + type: number, + accountId: number, + userId: number + ): Promise<{ accountId: number; userId: number; externalUserId: string }> { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.createAccount.name + } | params: { ` + + `dealerAccountId: ${dealerAccountId}, ` + + `role: ${role}, ` + + `acceptedEulaVersion: ${acceptedEulaVersion}, ` + + `acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` + + `acceptedDpaVersion: ${acceptedDpaVersion}, ` + + `type: ${type}, ` + + `accountId: ${accountId}, ` + + `userId: ${userId} };` + ); + try { + let externalUser: { sub: string } | ConflictError; + try { + // idpにユーザーを作成 + externalUser = await this.adB2cService.createUser( + context, + email, + password, + username + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + this.logger.error( + `[${context.getTrackingId()}] create externalUser failed` + ); + + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + // メールアドレス重複エラー + if (isConflictError(externalUser)) { + this.logger.error( + `[${context.getTrackingId()}] email conflict. externalUser: ${externalUser}` + ); + throw new HttpException( + makeErrorResponse("E010301"), + HttpStatus.BAD_REQUEST + ); + } + + let account: Account; + let user: User; + try { + // アカウントと管理者をセットで作成 + const { newAccount, adminUser } = + await this.accountRepository.createAccount( + context, + companyName, + country, + dealerAccountId, + type, + externalUser.sub, + role, + accountId, + userId, + acceptedEulaVersion, + acceptedPrivacyNoticeVersion, + acceptedDpaVersion + ); + account = newAccount; + user = adminUser; + this.logger.log( + `[${context.getTrackingId()}] adminUser.external_id: ${ + user.external_id + }` + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + this.logger.error(`[${context.getTrackingId()}] create account failed`); + //リカバリ処理 + // idpのユーザーを削除 + await this.deleteAdB2cUser(externalUser.sub, context); + + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + // 新規作成アカウント用のBlobコンテナを作成 + try { + await this.blobStorageService.createContainer( + context, + account.id, + country + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + this.logger.error( + `[${context.getTrackingId()}] create container failed` + ); + //リカバリ処理 + // idpのユーザーを削除 + await this.deleteAdB2cUser(externalUser.sub, context); + + // DBのアカウントを削除 + await this.deleteAccount(account.id, user.id, context); + + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + return { + accountId: account.id, + userId: user.id, + externalUserId: user.external_id, + }; + } catch (e) { + throw e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.createAccount.name}` + ); + } + } + + // AdB2cのユーザーを削除 + // TODO「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補 + private async deleteAdB2cUser( + externalUserId: string, + context: Context + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.createAccount.name + } | params: { ` + `externalUserId: ${externalUserId}};` + ); + try { + await this.adB2cService.deleteUser(externalUserId, context); + this.logger.log( + `[${context.getTrackingId()}] delete externalUser: ${externalUserId} | params: { ` + + `externalUserId: ${externalUserId}, };` + ); + } catch (error) { + this.logger.error(`[${context.getTrackingId()}] error=${error}`); + this.logger.error( + `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}` + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.deleteAdB2cUser.name}` + ); + } + } + + // DBのアカウントを削除 + private async deleteAccount( + accountId: number, + userId: number, + context: Context + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.deleteAccount.name + } | params: { accountId: ${accountId}, userId: ${userId} };` + ); + try { + await this.accountRepository.deleteAccount(context, accountId, userId); + this.logger.log( + `[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}` + ); + } catch (error) { + this.logger.error(`[${context.getTrackingId()}] error=${error}`); + this.logger.error( + `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete account: ${accountId}, user: ${userId}` + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.deleteAccount.name}` + ); + } + } +} diff --git a/data_migration_tools/server/src/features/register/register.controller.ts b/data_migration_tools/server/src/features/register/register.controller.ts new file mode 100644 index 0000000..c55d548 --- /dev/null +++ b/data_migration_tools/server/src/features/register/register.controller.ts @@ -0,0 +1,209 @@ +import { + Body, + Controller, + HttpStatus, + Post, + Req, + Logger, + HttpException, +} from "@nestjs/common"; +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +import fs from "fs"; +import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { Request } from "express"; +import { RegisterRequest, RegisterResponse } from "./types/types"; +import { RegisterService } from "./register.service"; +import { AccountsService } from "../accounts/accounts.service"; +import { UsersService } from "../users/users.service"; +import { makeContext } from "../../common/log"; +import { + isAccountsInputFileArray, + isUsersInputFileArray, + isLicensesInputFileArray, + isWorktypesInputFileArray, + isCardLicensesInputFileArray, +} from "../../common/types/types"; +import { makePassword } from "../../common/password/password"; +import { + USER_ROLES, + MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC, +} from "../../constants"; + +@ApiTags("register") +@Controller("register") +export class RegisterController { + private readonly logger = new Logger(RegisterController.name); + constructor( + private readonly registerService: RegisterService, + private readonly accountsService: AccountsService, + private readonly usersService: UsersService + ) {} + + @Post() + @ApiResponse({ + status: HttpStatus.OK, + type: RegisterResponse, + description: "成功時のレスポンス", + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: "想定外のサーバーエラー", + }) + @ApiOperation({ operationId: "dataRegist" }) + async dataRegist( + @Body() body: RegisterRequest, + @Req() req: Request + ): Promise { + const context = makeContext("iko", "register"); + + const inputFilePath = body.inputFilePath; + + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.dataRegist.name + } | params: { inputFilePath: ${inputFilePath}};` + ); + + try { + // 読み込みファイルのフルパス + const accouncsFileFullPath = inputFilePath + "accounts.json"; + const usersFileFullPath = inputFilePath + "users.json"; + const licensesFileFullPath = inputFilePath + "licenses.json"; + const worktypesFileFullPath = inputFilePath + "worktypes.json"; + const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json"; + + // ファイル存在チェックと読み込み + if ( + !fs.existsSync(accouncsFileFullPath) || + !fs.existsSync(usersFileFullPath) || + !fs.existsSync(licensesFileFullPath) || + !fs.existsSync(worktypesFileFullPath) || + !fs.existsSync(cardLicensesFileFullPath) + ) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + + // アカウントの登録用ファイル読み込み + const accountsObject = JSON.parse( + fs.readFileSync(accouncsFileFullPath, "utf8") + ); + + // 型ガード(account) + if (!isAccountsInputFileArray(accountsObject)) { + throw new Error("input file is not accountsInputFiles"); + } + + for (const accountsInputFile of accountsObject) { + // ランダムなパスワードを生成する + const ramdomPassword = makePassword(); + await this.accountsService.createAccount( + context, + accountsInputFile.companyName, + accountsInputFile.country, + accountsInputFile.dealerAccountId, + accountsInputFile.adminMail, + ramdomPassword, + accountsInputFile.adminName, + "none", + null, + null, + null, + accountsInputFile.type, + accountsInputFile.accountId, + accountsInputFile.userId + ); + + // ratelimit対応のためsleepを行う + await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC); + } + // const accountsInputFiles = accountsObject as AccountsInputFile[]; + + // ユーザの登録用ファイル読み込み + const usersObject = JSON.parse( + fs.readFileSync(usersFileFullPath, "utf8") + ); + + // 型ガード(user) + if (!isUsersInputFileArray(usersObject)) { + throw new Error("input file is not usersInputFiles"); + } + + for (const usersInputFile of usersObject) { + this.logger.log(usersInputFile.name); + await this.usersService.createUser( + context, + usersInputFile.name, + usersInputFile.role === USER_ROLES.AUTHOR + ? USER_ROLES.AUTHOR + : USER_ROLES.NONE, + usersInputFile.email, + true, + true, + usersInputFile.accountId, + usersInputFile.userId, + usersInputFile.authorId, + false, + null, + true + ); + // ratelimit対応のためsleepを行う + await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC); + } + + // ライセンスの登録用ファイル読み込み + const licensesObject = JSON.parse( + fs.readFileSync(licensesFileFullPath, "utf8") + ); + + // 型ガード(license) + if (!isLicensesInputFileArray(licensesObject)) { + throw new Error("input file is not licensesInputFiles"); + } + + // ワークタイプの登録用ファイル読み込み + const worktypesObject = JSON.parse( + fs.readFileSync(worktypesFileFullPath, "utf8") + ); + + // 型ガード(Worktypes) + if (!isWorktypesInputFileArray(worktypesObject)) { + throw new Error("input file is not WorktypesInputFiles"); + } + + // カードライセンスの登録用ファイル読み込み + const cardLicensesObject = JSON.parse( + fs.readFileSync(cardLicensesFileFullPath, "utf8") + ); + + // 型ガード(cardLicenses) + if (!isCardLicensesInputFileArray(cardLicensesObject)) { + throw new Error("input file is not cardLicensesInputFiles"); + } + + // ライセンス・ワークタイプ・カードライセンスの登録 + await this.registerService.registLicenseAndWorktypeData( + context, + licensesObject, + worktypesObject, + cardLicensesObject + ); + + return {}; + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.dataRegist.name}` + ); + } + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/data_migration_tools/server/src/features/register/register.module.ts b/data_migration_tools/server/src/features/register/register.module.ts new file mode 100644 index 0000000..10015dd --- /dev/null +++ b/data_migration_tools/server/src/features/register/register.module.ts @@ -0,0 +1,25 @@ +import { Module } from "@nestjs/common"; +import { RegisterController } from "./register.controller"; +import { RegisterService } from "./register.service"; +import { AccountsService } from "../accounts/accounts.service"; +import { UsersService } from "../users/users.service"; +import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module"; +import { WorktypesRepositoryModule } from "../../repositories/worktypes/worktypes.repository.module"; +import { UsersRepositoryModule } from "../../repositories/users/users.repository.module"; +import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module"; +import { AdB2cModule } from "../../gateways/adb2c/adb2c.module"; +import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module"; + +@Module({ + imports: [ + LicensesRepositoryModule, + WorktypesRepositoryModule, + AccountsRepositoryModule, + UsersRepositoryModule, + AdB2cModule, + BlobstorageModule, + ], + controllers: [RegisterController], + providers: [RegisterService, AccountsService, UsersService], +}) +export class RegisterModule {} diff --git a/data_migration_tools/server/src/features/register/register.service.ts b/data_migration_tools/server/src/features/register/register.service.ts new file mode 100644 index 0000000..6e42dc1 --- /dev/null +++ b/data_migration_tools/server/src/features/register/register.service.ts @@ -0,0 +1,68 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { Context } from "../../common/log"; +import { + LicensesInputFile, + WorktypesInputFile, + CardLicensesInputFile, +} from "../../common/types/types"; +import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service"; +import { WorktypesRepositoryService } from "../../repositories/worktypes/worktypes.repository.service"; +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +@Injectable() +export class RegisterService { + constructor( + private readonly licensesRepository: LicensesRepositoryService, + private readonly worktypesRepository: WorktypesRepositoryService + ) {} + private readonly logger = new Logger(RegisterService.name); + + /** + * Regist Data + * @param inputFilePath: string + */ + async registLicenseAndWorktypeData( + context: Context, + licensesInputFiles: LicensesInputFile[], + worktypesInputFiles: WorktypesInputFile[], + cardlicensesInputFiles: CardLicensesInputFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.registLicenseAndWorktypeData.name + }` + ); + + try { + this.logger.log("Licenses register start"); + await this.licensesRepository.insertLicenses(context, licensesInputFiles); + this.logger.log("Licenses register end"); + + this.logger.log("Worktypes register start"); + await this.worktypesRepository.createWorktype( + context, + worktypesInputFiles + ); + this.logger.log("Worktypes register end"); + + this.logger.log("CardLicenses register start"); + await this.licensesRepository.insertCardLicenses( + context, + cardlicensesInputFiles + ); + this.logger.log("CardLicenses register end"); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${ + this.registLicenseAndWorktypeData.name + }` + ); + } + } +} diff --git a/data_migration_tools/server/src/features/register/types/types.ts b/data_migration_tools/server/src/features/register/types/types.ts new file mode 100644 index 0000000..30fe678 --- /dev/null +++ b/data_migration_tools/server/src/features/register/types/types.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RegisterRequest { + @ApiProperty() + inputFilePath: string; + +} + +export class RegisterResponse {} + diff --git a/data_migration_tools/server/src/features/users/users.controller.ts b/data_migration_tools/server/src/features/users/users.controller.ts new file mode 100644 index 0000000..14c5cad --- /dev/null +++ b/data_migration_tools/server/src/features/users/users.controller.ts @@ -0,0 +1,10 @@ +import { Controller, Logger } from "@nestjs/common"; +import { ApiTags } from "@nestjs/swagger"; +import { UsersService } from "./users.service"; + +@ApiTags("users") +@Controller("users") +export class UsersController { + private readonly logger = new Logger(UsersController.name); + constructor(private readonly usersService: UsersService) {} +} diff --git a/data_migration_tools/server/src/features/users/users.module.ts b/data_migration_tools/server/src/features/users/users.module.ts new file mode 100644 index 0000000..3f0da57 --- /dev/null +++ b/data_migration_tools/server/src/features/users/users.module.ts @@ -0,0 +1,12 @@ +import { Module } from "@nestjs/common"; +import { AdB2cModule } from "../../gateways/adb2c/adb2c.module"; +import { UsersRepositoryModule } from "../../repositories/users/users.repository.module"; +import { UsersController } from "./users.controller"; +import { UsersService } from "./users.service"; + +@Module({ + imports: [UsersRepositoryModule, AdB2cModule], + controllers: [UsersController], + providers: [UsersService], +}) +export class UsersModule {} diff --git a/data_migration_tools/server/src/features/users/users.service.ts b/data_migration_tools/server/src/features/users/users.service.ts new file mode 100644 index 0000000..cb639cd --- /dev/null +++ b/data_migration_tools/server/src/features/users/users.service.ts @@ -0,0 +1,306 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { makeErrorResponse } from "../../common/error/makeErrorResponse"; +import { makePassword } from "../../common/password/password"; +import { + AdB2cService, + ConflictError, + isConflictError, +} from "../../gateways/adb2c/adb2c.service"; +import { + User as EntityUser, + newUser, +} from "../../repositories/users/entity/user.entity"; +import { UsersRepositoryService } from "../../repositories/users/users.repository.service"; +import { MANUAL_RECOVERY_REQUIRED, USER_ROLES } from "../../constants"; +import { Context } from "../../common/log"; +import { UserRoles } from "../../common/types/role"; + +@Injectable() +export class UsersService { + private readonly logger = new Logger(UsersService.name); + constructor( + private readonly usersRepository: UsersRepositoryService, + private readonly adB2cService: AdB2cService + ) {} + + /** + * Creates user + * @param context + * @param name + * @param role + * @param email + * @param autoRenew + * @param notification + * @param accountId + * @param userid + * @param [authorId] + * @param [encryption] + * @param [encryptionPassword] + * @param [prompt] + * @returns user + */ + async createUser( + context: Context, + name: string, + role: UserRoles, + email: string, + autoRenew: boolean, + notification: boolean, + accountId: number, + userid: number, + authorId?: string | undefined, + encryption?: boolean | undefined, + encryptionPassword?: string | undefined, + prompt?: boolean | undefined + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` + + `role: ${role}, ` + + `autoRenew: ${autoRenew}, ` + + `notification: ${notification}, ` + + `accountId: ${accountId}, ` + + `userid: ${userid}, ` + + `authorId: ${authorId}, ` + + `encryption: ${encryption}, ` + + `prompt: ${prompt} };` + ); + + //authorIdが重複していないかチェックする + if (authorId) { + let isAuthorIdDuplicated = false; + try { + isAuthorIdDuplicated = await this.usersRepository.existsAuthorId( + context, + accountId, + authorId + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + if (isAuthorIdDuplicated) { + throw new HttpException( + makeErrorResponse("E010302"), + HttpStatus.BAD_REQUEST + ); + } + } + + // ランダムなパスワードを生成する + const ramdomPassword = makePassword(); + + //Azure AD B2Cにユーザーを新規登録する + let externalUser: { sub: string } | ConflictError; + try { + this.logger.log(`name=${name}`); + // idpにユーザーを作成 + externalUser = await this.adB2cService.createUser( + context, + email, + ramdomPassword, + name + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + this.logger.error( + `[${context.getTrackingId()}] create externalUser failed` + ); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + // メールアドレス重複エラー + if (isConflictError(externalUser)) { + throw new HttpException( + makeErrorResponse("E010301"), + HttpStatus.BAD_REQUEST + ); + } + + //Azure AD B2Cに登録したユーザー情報のID(sub)と受け取った情報を使ってDBにユーザーを登録する + let newUser: EntityUser; + + try { + //roleに応じてユーザー情報を作成する + const newUserInfo = this.createNewUserInfo( + context, + userid, + role, + accountId, + externalUser.sub, + autoRenew, + notification, + authorId, + encryption, + encryptionPassword, + prompt + ); + // ユーザ作成 + newUser = await this.usersRepository.createNormalUser( + context, + newUserInfo + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + this.logger.error(`[${context.getTrackingId()}]create user failed`); + //リカバリー処理 + //Azure AD B2Cに登録したユーザー情報を削除する + await this.deleteB2cUser(externalUser.sub, context); + + switch (e.code) { + case "ER_DUP_ENTRY": + //AuthorID重複エラー + throw new HttpException( + makeErrorResponse("E010302"), + HttpStatus.BAD_REQUEST + ); + default: + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } + + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.createUser.name}` + ); + return; + } + + // Azure AD B2Cに登録したユーザー情報を削除する + // TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補 + private async deleteB2cUser(externalUserId: string, context: Context) { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.deleteB2cUser.name + } | params: { externalUserId: ${externalUserId} }` + ); + try { + await this.adB2cService.deleteUser(externalUserId, context); + this.logger.log( + `[${context.getTrackingId()}] delete externalUser: ${externalUserId}` + ); + } catch (error) { + this.logger.error(`[${context.getTrackingId()}] error=${error}`); + this.logger.error( + `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}` + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.deleteB2cUser.name}` + ); + } + } + + // DBに登録したユーザー情報を削除する + private async deleteUser(userId: number, context: Context) { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.deleteUser.name + } | params: { userId: ${userId} }` + ); + try { + await this.usersRepository.deleteNormalUser(context, userId); + this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`); + } catch (error) { + this.logger.error(`[${context.getTrackingId()}] error=${error}`); + this.logger.error( + `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}` + ); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.deleteUser.name}` + ); + } + } + + // roleを受け取って、roleに応じたnewUserを作成して返却する + private createNewUserInfo( + context: Context, + id: number, + role: UserRoles, + accountId: number, + externalId: string, + autoRenew: boolean, + notification: boolean, + authorId?: string | undefined, + encryption?: boolean | undefined, + encryptionPassword?: string | undefined, + prompt?: boolean | undefined + ): newUser { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.createNewUserInfo.name + } | params: { ` + + `id: ${id}, ` + + `role: ${role}, ` + + `accountId: ${accountId}, ` + + `authorId: ${authorId}, ` + + `externalId: ${externalId}, ` + + `autoRenew: ${autoRenew}, ` + + `notification: ${notification}, ` + + `authorId: ${authorId}, ` + + `encryption: ${encryption}, ` + + `prompt: ${prompt} };` + ); + try { + switch (role) { + case USER_ROLES.NONE: + case USER_ROLES.TYPIST: + return { + id, + account_id: accountId, + external_id: externalId, + auto_renew: autoRenew, + notification, + role, + accepted_dpa_version: null, + accepted_eula_version: null, + accepted_privacy_notice_version: null, + encryption: false, + encryption_password: null, + prompt: false, + author_id: null, + }; + case USER_ROLES.AUTHOR: + return { + id, + account_id: accountId, + external_id: externalId, + auto_renew: autoRenew, + notification, + role, + author_id: authorId ?? null, + encryption: encryption ?? false, + encryption_password: encryptionPassword ?? null, + prompt: prompt ?? false, + accepted_dpa_version: null, + accepted_eula_version: null, + accepted_privacy_notice_version: null, + }; + default: + //不正なroleが指定された場合はログを出力してエラーを返す + this.logger.error( + `[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}` + ); + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + return e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.createNewUserInfo.name}` + ); + } + } +} diff --git a/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts index 743fbf8..b561f42 100644 --- a/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts +++ b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts @@ -5,6 +5,8 @@ import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { AdB2cResponse, AdB2cUser } from "./types/types"; import { isPromiseRejectedResult } from "./utils/utils"; +import { Context } from "../../common/log"; +import { ADB2C_SIGN_IN_TYPE } from "../../constants"; export type ConflictError = { reason: "email"; @@ -27,9 +29,16 @@ export const isConflictError = (arg: unknown): arg is ConflictError => { @Injectable() export class AdB2cService { private readonly logger = new Logger(AdB2cService.name); + private readonly tenantName: string; + private readonly flowName: string; + private readonly ttl: number; private graphClient: Client; constructor(private readonly configService: ConfigService) { + this.tenantName = this.configService.getOrThrow("TENANT_NAME"); + this.flowName = this.configService.getOrThrow("SIGNIN_FLOW_NAME"); + this.ttl = this.configService.getOrThrow("ADB2C_CACHE_TTL"); + // ADB2Cへの認証情報 const credential = new ClientSecretCredential( this.configService.getOrThrow("ADB2C_TENANT_ID"), @@ -42,6 +51,61 @@ export class AdB2cService { this.graphClient = Client.initWithMiddleware({ authProvider }); } + + /** + * Creates user AzureADB2Cにユーザーを追加する + * @param email 管理ユーザーのメールアドレス + * @param password 管理ユーザーのパスワード + * @param username 管理ユーザーの名前 + * @returns user + */ + async createUser( + context: Context, + email: string, + password: string, + username: string + ): Promise<{ sub: string } | ConflictError> { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.createUser.name}` + ); + try { + // ユーザをADB2Cに登録 + const newUser = await this.graphClient.api("users/").post({ + accountEnabled: true, + displayName: username, + passwordPolicies: "DisableStrongPassword", + passwordProfile: { + forceChangePasswordNextSignIn: false, + password: password, + }, + identities: [ + { + signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + issuer: `${this.tenantName}.onmicrosoft.com`, + issuerAssignedId: email, + }, + ], + }); + return { sub: newUser.id }; + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + if (e?.statusCode === 400 && e?.body) { + const error = JSON.parse(e.body); + + // エラーが競合エラーである場合は、メールアドレス重複としてエラーを返す + if (error?.details?.find((x) => x.code === "ObjectConflict")) { + return { reason: "email", message: "ObjectConflict" }; + } + } + + throw e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.createUser.name}` + ); + } + } + /** * Gets users * @param externalIds @@ -71,6 +135,44 @@ export class AdB2cService { } } + /** + * Azure AD B2Cからユーザ情報を削除する + * @param externalId 外部ユーザーID + * @param context コンテキスト + */ + async deleteUser(externalId: string, context: Context): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.deleteUser.name + } | params: { externalId: ${externalId} };` + ); + + try { + // https://learn.microsoft.com/en-us/graph/api/user-delete?view=graph-rest-1.0&tabs=javascript#example + await this.graphClient.api(`users/${externalId}`).delete(); + this.logger.log( + `[${context.getTrackingId()}] [ADB2C DELETE] externalId: ${externalId}` + ); + + // キャッシュからも削除する + // 移行ツール特別対応:キャッシュ登録は行わないので削除も不要 + /* + try { + await this.redisService.del(context, makeADB2CKey(externalId)); + } catch (e) { + // キャッシュからの削除に失敗しても、ADB2Cからの削除は成功しているため例外はスローしない + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + } + */ + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.deleteUser.name}` + ); + } + } /** * Azure AD B2Cからユーザ情報を削除する(複数) * @param externalIds 外部ユーザーID diff --git a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts index 68e5a80..7c26388 100644 --- a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts +++ b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts @@ -1,10 +1,16 @@ import { Injectable, Logger } from "@nestjs/common"; import { + ContainerClient, BlobServiceClient, StorageSharedKeyCredential, } from "@azure/storage-blob"; +import { + BLOB_STORAGE_REGION_AU, + BLOB_STORAGE_REGION_EU, + BLOB_STORAGE_REGION_US, +} from "../../constants"; import { ConfigService } from "@nestjs/config"; - +import { Context } from "../../common/log"; @Injectable() export class BlobstorageService { private readonly logger = new Logger(BlobstorageService.name); @@ -41,6 +47,44 @@ export class BlobstorageService { ); } + /** + * Creates container + * @param context + * @param accountId + * @param country + * @returns container + */ + async createContainer( + context: Context, + accountId: number, + country: string + ): Promise { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.createContainer.name + } | params: { ` + `accountId: ${accountId} };` + ); + + // 国に応じたリージョンでコンテナ名を指定してClientを取得 + const containerClient = this.getContainerClient( + context, + accountId, + country + ); + + try { + // コンテナ作成 + await containerClient.create(); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + throw e; + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.createContainer.name}` + ); + } + } + /** * すべてのコンテナを削除します。 * @returns containers @@ -80,4 +124,32 @@ export class BlobstorageService { this.logger.log(`[OUT] ${this.deleteContainers.name}`); } } + + /** + * Gets container client + * @param companyName + * @returns container client + */ + private getContainerClient( + context: Context, + accountId: number, + country: string + ): ContainerClient { + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.getContainerClient.name + } | params: { ` + `accountId: ${accountId} };` + ); + + const containerName = `account-${accountId}`; + if (BLOB_STORAGE_REGION_US.includes(country)) { + return this.blobServiceClientUS.getContainerClient(containerName); + } else if (BLOB_STORAGE_REGION_AU.includes(country)) { + return this.blobServiceClientAU.getContainerClient(containerName); + } else if (BLOB_STORAGE_REGION_EU.includes(country)) { + return this.blobServiceClientEU.getContainerClient(containerName); + } else { + throw new Error("invalid country"); + } + } } diff --git a/data_migration_tools/server/src/repositories/accounts/accounts.repository.module.ts b/data_migration_tools/server/src/repositories/accounts/accounts.repository.module.ts new file mode 100644 index 0000000..ddd0efd --- /dev/null +++ b/data_migration_tools/server/src/repositories/accounts/accounts.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Account } from './entity/account.entity'; +import { AccountsRepositoryService } from './accounts.repository.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Account])], + providers: [AccountsRepositoryService], + exports: [AccountsRepositoryService], +}) +export class AccountsRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts b/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts new file mode 100644 index 0000000..ec24808 --- /dev/null +++ b/data_migration_tools/server/src/repositories/accounts/accounts.repository.service.ts @@ -0,0 +1,164 @@ +import { Injectable } from '@nestjs/common'; +import { + DataSource, +} from 'typeorm'; +import { User } from '../users/entity/user.entity'; +import { Account } from './entity/account.entity'; +import { + getDirection, + getTaskListSortableAttribute, +} from '../../common/types/sort/util'; +import { SortCriteria } from "../sort_criteria/entity/sort_criteria.entity"; +import { + insertEntity, + updateEntity, + deleteEntity, +} from '../../common/repository'; +import { Context } from '../../common/log'; + +@Injectable() +export class AccountsRepositoryService { + // クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== "local"; + constructor(private dataSource: DataSource) {} + + /** + * プライマリ管理者とアカウント、ソート条件を同時に作成する + * @param companyName + * @param country + * @param dealerAccountId + * @param tier + * @param adminExternalUserId + * @param adminUserRole + * @param accountId + * @param userId + * @param adminUserAcceptedEulaVersion + * @param adminUserAcceptedPrivacyNoticeVersion + * @param adminUserAcceptedDpaVersion + * @returns account/admin user + */ + async createAccount( + context: Context, + companyName: string, + country: string, + dealerAccountId: number | undefined, + tier: number, + adminExternalUserId: string, + adminUserRole: string, + accountId: number, + userId: number, + adminUserAcceptedEulaVersion?: string, + adminUserAcceptedPrivacyNoticeVersion?: string, + adminUserAcceptedDpaVersion?: string + ): Promise<{ newAccount: Account; adminUser: User }> { + return await this.dataSource.transaction(async (entityManager) => { + const account = new Account(); + { + account.id = accountId; + account.parent_account_id = dealerAccountId ?? null; + account.company_name = companyName; + account.country = country; + account.tier = tier; + } + const accountsRepo = entityManager.getRepository(Account); + const newAccount = accountsRepo.create(account); + const persistedAccount = await insertEntity( + Account, + accountsRepo, + newAccount, + this.isCommentOut, + context + ); + + // 作成されたAccountのIDを使用してユーザーを作成 + const user = new User(); + { + user.id = userId; + user.account_id = persistedAccount.id; + user.external_id = adminExternalUserId; + user.role = adminUserRole; + user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null; + user.accepted_privacy_notice_version = + adminUserAcceptedPrivacyNoticeVersion ?? null; + user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null; + } + const usersRepo = entityManager.getRepository(User); + const newUser = usersRepo.create(user); + const persistedUser = await insertEntity( + User, + usersRepo, + newUser, + this.isCommentOut, + context + ); + + // アカウントに管理者を設定して更新 + persistedAccount.primary_admin_user_id = persistedUser.id; + + const result = await updateEntity( + accountsRepo, + { id: persistedAccount.id }, + persistedAccount, + this.isCommentOut, + context + ); + + // 想定外の更新が行われた場合はロールバックを行った上でエラー送出 + if (result.affected !== 1) { + throw new Error(`invalid update. result.affected=${result.affected}`); + } + + // ユーザーのタスクソート条件を作成 + const sortCriteria = new SortCriteria(); + { + sortCriteria.parameter = getTaskListSortableAttribute("JOB_NUMBER"); + sortCriteria.direction = getDirection("ASC"); + sortCriteria.user_id = persistedUser.id; + } + const sortCriteriaRepo = entityManager.getRepository(SortCriteria); + const newSortCriteria = sortCriteriaRepo.create(sortCriteria); + await insertEntity( + SortCriteria, + sortCriteriaRepo, + newSortCriteria, + this.isCommentOut, + context + ); + + return { newAccount: persistedAccount, adminUser: persistedUser }; + }); + } + + /** + * プライマリ管理者とアカウント、ソート条件を同時に削除する + * @param accountId + * @returns delete + */ + async deleteAccount( + context: Context, + accountId: number, + userId: number + ): Promise { + await this.dataSource.transaction(async (entityManager) => { + const accountsRepo = entityManager.getRepository(Account); + const usersRepo = entityManager.getRepository(User); + const sortCriteriaRepo = entityManager.getRepository(SortCriteria); + // ソート条件を削除 + await deleteEntity( + sortCriteriaRepo, + { user_id: userId }, + this.isCommentOut, + context + ); + // プライマリ管理者を削除 + await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context); + // アカウントを削除 + await deleteEntity( + accountsRepo, + { id: accountId }, + this.isCommentOut, + context + ); + }); + } +} diff --git a/data_migration_tools/server/src/repositories/accounts/entity/account.entity.ts b/data_migration_tools/server/src/repositories/accounts/entity/account.entity.ts new file mode 100644 index 0000000..3c40a03 --- /dev/null +++ b/data_migration_tools/server/src/repositories/accounts/entity/account.entity.ts @@ -0,0 +1,70 @@ +import { bigintTransformer } from '../../../common/entity'; +import { User } from '../../../repositories/users/entity/user.entity'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, +} from 'typeorm'; + +@Entity({ name: 'accounts' }) +export class Account { + @PrimaryGeneratedColumn() + id: number; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + parent_account_id: number | null; + + @Column() + tier: number; + + @Column() + country: string; + + @Column({ default: false }) + delegation_permission: boolean; + + @Column({ default: false }) + locked: boolean; + + @Column() + company_name: string; + + @Column({ default: false }) + verified: boolean; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + primary_admin_user_id: number | null; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + secondary_admin_user_id: number | null; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + active_worktype_id: number | null; + + @Column({ nullable: true, type: 'datetime' }) + deleted_at: Date | null; + + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at: Date; + + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at: Date; + + @OneToMany(() => User, (user) => user.id) + user: User[] | null; +} diff --git a/data_migration_tools/server/src/repositories/accounts/errors/types.ts b/data_migration_tools/server/src/repositories/accounts/errors/types.ts new file mode 100644 index 0000000..826700c --- /dev/null +++ b/data_migration_tools/server/src/repositories/accounts/errors/types.ts @@ -0,0 +1,28 @@ +// アカウント未発見エラー +export class AccountNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'AccountNotFoundError'; + } +} +// ディーラーアカウント未存在エラー +export class DealerAccountNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'DealerAccountNotFoundError'; + } +} +// 管理者ユーザ未存在エラー +export class AdminUserNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'AdminUserNotFoundError'; + } +} +// アカウントロックエラー +export class AccountLockedError extends Error { + constructor(message: string) { + super(message); + this.name = 'AccountLockedError'; + } +} diff --git a/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts b/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts new file mode 100644 index 0000000..90715ae --- /dev/null +++ b/data_migration_tools/server/src/repositories/licenses/entity/license.entity.ts @@ -0,0 +1,137 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; +import { bigintTransformer } from '../../../common/entity'; + + +@Entity({ name: 'licenses' }) +export class License { + @PrimaryGeneratedColumn() + id: number; + + @Column({ nullable: true, type: 'datetime' }) + expiry_date: Date | null; + + @Column() + account_id: number; + + @Column() + type: string; + + @Column() + status: string; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + allocated_user_id: number | null; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + order_id: number | null; + + @Column({ nullable: true, type: 'datetime' }) + deleted_at: Date | null; + + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) + delete_order_id: number | null; + + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) + created_at: Date; + + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) + updated_at: Date; +} + +@Entity({ name: 'license_allocation_history' }) +export class LicenseAllocationHistory { + @PrimaryGeneratedColumn() + id: number; + + @Column() + user_id: number; + + @Column() + license_id: number; + + @Column() + is_allocated: boolean; + + @Column() + account_id: number; + + @Column() + executed_at: Date; + + @Column() + switch_from_type: string; + + @Column({ nullable: true, type: 'datetime' }) + deleted_at: Date | null; + + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) + created_at: Date; + + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) + updated_at: Date; + +} + + +@Entity({ name: "card_licenses" }) +export class CardLicense { + @PrimaryGeneratedColumn() + license_id: number; + + @Column() + issue_id: number; + + @Column() + card_license_key: string; + + @Column({ nullable: true, type: "datetime" }) + activated_at: Date | null; + + @Column({ nullable: true, type: "datetime" }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + created_at: Date; + + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + updated_at: Date; +} \ No newline at end of file diff --git a/data_migration_tools/server/src/repositories/licenses/errors/types.ts b/data_migration_tools/server/src/repositories/licenses/errors/types.ts new file mode 100644 index 0000000..12b34d1 --- /dev/null +++ b/data_migration_tools/server/src/repositories/licenses/errors/types.ts @@ -0,0 +1,108 @@ +// POナンバーがすでに存在するエラー +export class PoNumberAlreadyExistError extends Error { + constructor(message: string) { + super(message); + this.name = 'PoNumberAlreadyExistError'; + } +} + +// 取り込むカードライセンスが存在しないエラー +export class LicenseNotExistError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseNotExistError'; + } +} + +// 取り込むライセンスが既に取り込み済みのエラー +export class LicenseKeyAlreadyActivatedError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseKeyAlreadyActivatedError'; + } +} + +// 注文不在エラー +export class OrderNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'OrderNotFoundError'; + } +} +// 注文発行済エラー +export class AlreadyIssuedError extends Error { + constructor(message: string) { + super(message); + this.name = 'AlreadyIssuedError'; + } +} +// ライセンス不足エラー +export class LicensesShortageError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicensesShortageError'; + } +} + +// ライセンス有効期限切れエラー +export class LicenseExpiredError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseExpiredError'; + } +} +// ライセンス割り当て不可エラー +export class LicenseUnavailableError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseUnavailableError'; + } +} + +// ライセンス割り当て解除済みエラー +export class LicenseAlreadyDeallocatedError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseAlreadyDeallocatedError'; + } +} + +// 注文キャンセル失敗エラー +export class CancelOrderFailedError extends Error { + constructor(message: string) { + super(message); + this.name = 'CancelOrderFailedError'; + } +} + +// ライセンス発行キャンセル不可エラー(ステータスが変えられている場合) +export class AlreadyLicenseStatusChangedError extends Error { + constructor(message: string) { + super(message); + this.name = 'AlreadyLicenseStatusChangedError'; + } +} + +// ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合) +export class CancellationPeriodExpiredError extends Error { + constructor(message: string) { + super(message); + this.name = 'CancellationPeriodExpiredError'; + } +} + +// ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合) +export class AlreadyLicenseAllocatedError extends Error { + constructor(message: string) { + super(message); + this.name = 'AlreadyLicenseAllocatedError'; + } +} + +// ライセンス未割当エラー +export class LicenseNotAllocatedError extends Error { + constructor(message: string) { + super(message); + this.name = 'LicenseNotAllocatedError'; + } +} diff --git a/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts b/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts new file mode 100644 index 0000000..e3e3d0c --- /dev/null +++ b/data_migration_tools/server/src/repositories/licenses/licenses.repository.module.ts @@ -0,0 +1,17 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { + CardLicense, + License, + LicenseAllocationHistory, +} from "./entity/license.entity"; +import { LicensesRepositoryService } from "./licenses.repository.service"; + +@Module({ + imports: [ + TypeOrmModule.forFeature([License, CardLicense, LicenseAllocationHistory]), + ], + providers: [LicensesRepositoryService], + exports: [LicensesRepositoryService], +}) +export class LicensesRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts b/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts new file mode 100644 index 0000000..52e13b5 --- /dev/null +++ b/data_migration_tools/server/src/repositories/licenses/licenses.repository.service.ts @@ -0,0 +1,130 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { DataSource, In } from "typeorm"; +import { + License, + LicenseAllocationHistory, + CardLicense, +} from "./entity/license.entity"; +import { insertEntities } from "../../common/repository"; +import { Context } from "../../common/log"; +import { + LicensesInputFile, + CardLicensesInputFile, +} from "../../common/types/types"; + +@Injectable() +export class LicensesRepositoryService { + //クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== "local"; + constructor(private dataSource: DataSource) {} + private readonly logger = new Logger(LicensesRepositoryService.name); + + /** + * ライセンスを登録する + * @context Context + * @param licensesInputFiles + */ + async insertLicenses( + context: Context, + licensesInputFiles: LicensesInputFile[] + ): Promise<{}> { + const nowDate = new Date(); + return await this.dataSource.transaction(async (entityManager) => { + const licenseRepo = entityManager.getRepository(License); + + let newLicenses: License[] = []; + licensesInputFiles.forEach((licensesInputFile) => { + const license = new License(); + license.account_id = licensesInputFile.account_id; + license.status = licensesInputFile.status; + license.type = licensesInputFile.type; + license.expiry_date = (licensesInputFile.expiry_date) ? new Date(licensesInputFile.expiry_date) : null; + if (licensesInputFile.allocated_user_id) { + license.allocated_user_id = licensesInputFile.allocated_user_id; + } + newLicenses.push(license); + }); + + // ライセンステーブルを登録 + const insertedlicenses = await insertEntities( + License, + licenseRepo, + newLicenses, + this.isCommentOut, + context + ); + + const licenseAllocationHistoryRepo = entityManager.getRepository( + LicenseAllocationHistory + ); + // ユーザに割り当てた場合はライセンス割り当て履歴にも登録 + let newLicenseAllocationHistories: LicenseAllocationHistory[] = []; + insertedlicenses.forEach((insertedlicense) => { + if (insertedlicense.allocated_user_id) { + const licenseAllocationHistory = new LicenseAllocationHistory(); + licenseAllocationHistory.user_id = insertedlicense.allocated_user_id; + licenseAllocationHistory.license_id = insertedlicense.id; + licenseAllocationHistory.is_allocated = true; + licenseAllocationHistory.account_id = insertedlicense.account_id; + licenseAllocationHistory.switch_from_type = "NONE"; + licenseAllocationHistory.executed_at = insertedlicense.created_at; + + newLicenseAllocationHistories.push(licenseAllocationHistory); + } + }); + + // ライセンス割り当てテーブルを登録 + await insertEntities( + LicenseAllocationHistory, + licenseAllocationHistoryRepo, + newLicenseAllocationHistories, + this.isCommentOut, + context + ); + + return {}; + }); + } + + /** + * カードライセンスを登録する + * @context Context + * @param cardLicensesInputFiles + */ + async insertCardLicenses( + context: Context, + cardLicensesInputFiles: CardLicensesInputFile[] + ): Promise<{}> { + return await this.dataSource.transaction(async (entityManager) => { + const cardLicenseRepo = entityManager.getRepository(CardLicense); + + + let newCardLicenses: CardLicense[] = []; + cardLicensesInputFiles.forEach((cardLicensesInputFile) => { + const cardLicense = new CardLicense(); + cardLicense.license_id = cardLicensesInputFile.license_id; + cardLicense.issue_id = cardLicensesInputFile.issue_id; + cardLicense.card_license_key = cardLicensesInputFile.card_license_key; + cardLicense.activated_at = (cardLicensesInputFile.activated_at) ? new Date(cardLicensesInputFile.activated_at) : null; + cardLicense.created_at = (cardLicensesInputFile.created_at) ? new Date(cardLicensesInputFile.created_at) : null; + cardLicense.created_by = cardLicensesInputFile.created_by; + cardLicense.updated_at = (cardLicensesInputFile.updated_at) ? new Date(cardLicensesInputFile.updated_at) : null; + cardLicense.updated_by = cardLicensesInputFile.updated_by; + + newCardLicenses.push(cardLicense); + }); + + const query = cardLicenseRepo + .createQueryBuilder() + .insert() + .into(CardLicense); + if (this.isCommentOut) { + query.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`); + } + query.values(newCardLicenses).execute(); + + return {}; + }); + } + +} diff --git a/data_migration_tools/server/src/repositories/sort_criteria/entity/sort_criteria.entity.ts b/data_migration_tools/server/src/repositories/sort_criteria/entity/sort_criteria.entity.ts new file mode 100644 index 0000000..260c9a9 --- /dev/null +++ b/data_migration_tools/server/src/repositories/sort_criteria/entity/sort_criteria.entity.ts @@ -0,0 +1,16 @@ +import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity({ name: 'sort_criteria' }) +export class SortCriteria { + @PrimaryGeneratedColumn() + id: number; + + @Column() + user_id: number; + + @Column() + parameter: string; + + @Column() + direction: string; +} diff --git a/data_migration_tools/server/src/repositories/sort_criteria/sort_criteria.repository.module.ts b/data_migration_tools/server/src/repositories/sort_criteria/sort_criteria.repository.module.ts new file mode 100644 index 0000000..8a46b37 --- /dev/null +++ b/data_migration_tools/server/src/repositories/sort_criteria/sort_criteria.repository.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SortCriteria } from './entity/sort_criteria.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([SortCriteria])], +}) +export class SortCriteriaRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/users/entity/user.entity.ts b/data_migration_tools/server/src/repositories/users/entity/user.entity.ts new file mode 100644 index 0000000..9ae5eb6 --- /dev/null +++ b/data_migration_tools/server/src/repositories/users/entity/user.entity.ts @@ -0,0 +1,97 @@ +import { Account } from '../../../repositories/accounts/entity/account.entity'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; + +@Entity({ name: 'users' }) +export class User { + @PrimaryGeneratedColumn() + id: number; + + @Column() + external_id: string; + + @Column() + account_id: number; + + @Column() + role: string; + + @Column({ nullable: true, type: 'varchar' }) + author_id: string | null; + + @Column({ nullable: true, type: 'varchar' }) + accepted_eula_version: string | null; + + @Column({ nullable: true, type: 'varchar' }) + accepted_privacy_notice_version: string | null; + + @Column({ nullable: true, type: 'varchar' }) + accepted_dpa_version: string | null; + + @Column({ default: false }) + email_verified: boolean; + + @Column({ default: true }) + auto_renew: boolean; + + @Column({ default: true }) + notification: boolean; + + @Column({ default: false }) + encryption: boolean; + + @Column({ nullable: true, type: 'varchar' }) + encryption_password: string | null; + + @Column({ default: false }) + prompt: boolean; + + @Column({ nullable: true, type: 'datetime' }) + deleted_at: Date | null; + + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at: Date; + + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at: Date; + + @ManyToOne(() => Account, (account) => account.user, { + createForeignKeyConstraints: false, + }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @JoinColumn({ name: 'account_id' }) + account: Account | null; + +} + + +export type newUser = Omit< + User, + | 'deleted_at' + | 'created_at' + | 'updated_at' + | 'updated_by' + | 'created_by' + | 'account' + | 'license' + | 'userGroupMembers' + | 'email_verified' +>; diff --git a/data_migration_tools/server/src/repositories/users/errors/types.ts b/data_migration_tools/server/src/repositories/users/errors/types.ts new file mode 100644 index 0000000..6f90ede --- /dev/null +++ b/data_migration_tools/server/src/repositories/users/errors/types.ts @@ -0,0 +1,56 @@ +// Email検証済みエラー +export class EmailAlreadyVerifiedError extends Error { + constructor(message: string) { + super(message); + this.name = 'EmailAlreadyVerifiedError'; + } +} +// ユーザー未発見エラー +export class UserNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'UserNotFoundError'; + } +} +// AuthorID重複エラー +export class AuthorIdAlreadyExistsError extends Error { + constructor(message: string) { + super(message); + this.name = 'AuthorIdAlreadyExistsError'; + } +} +// 不正なRole変更エラー +export class InvalidRoleChangeError extends Error { + constructor(message: string) { + super(message); + this.name = 'InvalidRoleChangeError'; + } +} +// 暗号化パスワード不足エラー +export class EncryptionPasswordNeedError extends Error { + constructor(message: string) { + super(message); + this.name = 'EncryptionPasswordNeedError'; + } +} +// 利用規約バージョン情報不在エラー +export class TermInfoNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'TermInfoNotFoundError'; + } +} +// 利用規約バージョンパラメータ不在エラー +export class UpdateTermsVersionNotSetError extends Error { + constructor(message: string) { + super(message); + this.name = 'UpdateTermsVersionNotSetError'; + } +} +// 代行操作不許可エラー +export class DelegationNotAllowedError extends Error { + constructor(message: string) { + super(message); + this.name = 'DelegationNotAllowedError'; + } +} diff --git a/data_migration_tools/server/src/repositories/users/users.repository.module.ts b/data_migration_tools/server/src/repositories/users/users.repository.module.ts new file mode 100644 index 0000000..79be43a --- /dev/null +++ b/data_migration_tools/server/src/repositories/users/users.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './entity/user.entity'; +import { UsersRepositoryService } from './users.repository.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([User])], + providers: [UsersRepositoryService], + exports: [UsersRepositoryService], +}) +export class UsersRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/users/users.repository.service.ts b/data_migration_tools/server/src/repositories/users/users.repository.service.ts new file mode 100644 index 0000000..ebb4dc3 --- /dev/null +++ b/data_migration_tools/server/src/repositories/users/users.repository.service.ts @@ -0,0 +1,141 @@ +import { Injectable } from '@nestjs/common'; +import { User, newUser } from './entity/user.entity'; +import { + DataSource, +} from 'typeorm'; +import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity'; +import { + getDirection, + getTaskListSortableAttribute, +} from '../../common/types/sort/util'; +import { Context } from '../../common/log'; +import { + insertEntity, + deleteEntity, +} from '../../common/repository'; + +@Injectable() +export class UsersRepositoryService { + // クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== "local"; + + constructor(private dataSource: DataSource) {} + + /** + * 一般ユーザーを作成する + * @param user + * @returns User + */ + async createNormalUser(context: Context, user: newUser): Promise { + const { + id, + account_id: accountId, + external_id: externalUserId, + role, + auto_renew, + notification, + author_id, + accepted_eula_version, + accepted_dpa_version, + encryption, + encryption_password: encryptionPassword, + prompt, + } = user; + const userEntity = new User(); + + userEntity.id = id; + userEntity.role = role; + userEntity.account_id = accountId; + userEntity.external_id = externalUserId; + userEntity.auto_renew = auto_renew; + userEntity.notification = notification; + userEntity.author_id = author_id; + userEntity.accepted_eula_version = accepted_eula_version; + userEntity.accepted_dpa_version = accepted_dpa_version; + userEntity.encryption = encryption; + userEntity.encryption_password = encryptionPassword; + userEntity.prompt = prompt; + userEntity.email_verified = true; + + const createdEntity = await this.dataSource.transaction( + async (entityManager) => { + const repo = entityManager.getRepository(User); + const newUser = repo.create(userEntity); + const persisted = await insertEntity( + User, + repo, + newUser, + this.isCommentOut, + context + ); + + // ユーザーのタスクソート条件を作成 + const sortCriteria = new SortCriteria(); + { + sortCriteria.parameter = getTaskListSortableAttribute("JOB_NUMBER"); + sortCriteria.direction = getDirection("ASC"); + sortCriteria.user_id = persisted.id; + } + const sortCriteriaRepo = entityManager.getRepository(SortCriteria); + const newSortCriteria = sortCriteriaRepo.create(sortCriteria); + await insertEntity( + SortCriteria, + sortCriteriaRepo, + newSortCriteria, + this.isCommentOut, + context + ); + + return persisted; + } + ); + return createdEntity; + } + + /** + * AuthorIdが既に存在するか確認する + * @param user + * @returns 存在する:true 存在しない:false + */ + async existsAuthorId( + context: Context, + accountId: number, + authorId: string + ): Promise { + const user = await this.dataSource.getRepository(User).findOne({ + where: [ + { + account_id: accountId, + author_id: authorId, + }, + ], + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + }); + + if (user) { + return true; + } + return false; + } + + /** + * UserID指定のユーザーとソート条件を同時に削除する + * @param userId + * @returns delete + */ + async deleteNormalUser(context: Context, userId: number): Promise { + await this.dataSource.transaction(async (entityManager) => { + const usersRepo = entityManager.getRepository(User); + const sortCriteriaRepo = entityManager.getRepository(SortCriteria); + // ソート条件を削除 + await deleteEntity( + sortCriteriaRepo, + { user_id: userId }, + this.isCommentOut, + context + ); + // プライマリ管理者を削除 + await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context); + }); + } +} diff --git a/data_migration_tools/server/src/repositories/worktypes/entity/option_item.entity.ts b/data_migration_tools/server/src/repositories/worktypes/entity/option_item.entity.ts new file mode 100644 index 0000000..f9e7ac4 --- /dev/null +++ b/data_migration_tools/server/src/repositories/worktypes/entity/option_item.entity.ts @@ -0,0 +1,42 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + UpdateDateColumn, + CreateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { Worktype } from './worktype.entity'; + +@Entity({ name: 'option_items' }) +export class OptionItem { + @PrimaryGeneratedColumn() + id: number; + @Column() + worktype_id: number; + @Column() + item_label: string; + @Column() + default_value_type: string; + @Column() + initial_value: string; + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at: Date | null; + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at: Date | null; + + @ManyToOne(() => Worktype, (worktype) => worktype.id) + @JoinColumn({ name: 'worktype_id' }) + worktype: Worktype; +} diff --git a/data_migration_tools/server/src/repositories/worktypes/entity/worktype.entity.ts b/data_migration_tools/server/src/repositories/worktypes/entity/worktype.entity.ts new file mode 100644 index 0000000..6419cbd --- /dev/null +++ b/data_migration_tools/server/src/repositories/worktypes/entity/worktype.entity.ts @@ -0,0 +1,44 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity({ name: 'worktypes' }) +export class Worktype { + @PrimaryGeneratedColumn() + id: number; + + @Column() + account_id: number; + + @Column() + custom_worktype_id: string; + + @Column({ nullable: true, type: 'varchar' }) + description: string | null; + + @Column({ nullable: true, type: 'datetime' }) + deleted_at: Date | null; + + @Column({ nullable: true, type: 'datetime' }) + created_by: string | null; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + created_at: Date; + + @Column({ nullable: true, type: 'datetime' }) + updated_by: string | null; + + @UpdateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: 'datetime', + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + updated_at: Date; + +} diff --git a/data_migration_tools/server/src/repositories/worktypes/errors/types.ts b/data_migration_tools/server/src/repositories/worktypes/errors/types.ts new file mode 100644 index 0000000..fb5db2d --- /dev/null +++ b/data_migration_tools/server/src/repositories/worktypes/errors/types.ts @@ -0,0 +1,28 @@ +// WorktypeID重複エラー +export class WorktypeIdAlreadyExistsError extends Error { + constructor(message: string) { + super(message); + this.name = 'WorktypeIdAlreadyExistsError'; + } +} +// WorktypeID登録上限エラー +export class WorktypeIdMaxCountError extends Error { + constructor(message: string) { + super(message); + this.name = 'WorktypeIdMaxCountError'; + } +} +// WorktypeID不在エラー +export class WorktypeIdNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'WorktypeIdNotFoundError'; + } +} +// WorktypeID使用中エラー +export class WorktypeIdInUseError extends Error { + constructor(message: string) { + super(message); + this.name = 'WorktypeIdInUseError'; + } +} diff --git a/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.module.ts b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.module.ts new file mode 100644 index 0000000..1ae34d1 --- /dev/null +++ b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Worktype } from './entity/worktype.entity'; +import { WorktypesRepositoryService } from './worktypes.repository.service'; +import { OptionItem } from "./entity/option_item.entity"; + +@Module({ + imports: [TypeOrmModule.forFeature([Worktype, OptionItem])], + providers: [WorktypesRepositoryService], + exports: [WorktypesRepositoryService], +}) +export class WorktypesRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts new file mode 100644 index 0000000..9f7f2ff --- /dev/null +++ b/data_migration_tools/server/src/repositories/worktypes/worktypes.repository.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from "@nestjs/common"; +import { DataSource, Not } from "typeorm"; +import { Worktype } from "./entity/worktype.entity"; +import { + OPTION_ITEM_NUM, + OPTION_ITEM_VALUE_TYPE, + WORKTYPE_MAX_COUNT, +} from "../../constants"; +import { + WorktypeIdAlreadyExistsError, + WorktypeIdMaxCountError, +} from "./errors/types"; +import { OptionItem } from "./entity/option_item.entity"; +import { insertEntities, insertEntity } from "../../common/repository"; +import { Context } from "../../common/log"; +import { WorktypesInputFile } from "../../common/types/types"; + +@Injectable() +export class WorktypesRepositoryService { + // クエリログにコメントを出力するかどうか + private readonly isCommentOut = process.env.STAGE !== "local"; + + constructor(private dataSource: DataSource) {} + + /** + * ワークタイプを作成する + * @param accountId + * @param worktypeId + * @param [description] + */ + async createWorktype( + context: Context, + worktypesInputFiles: WorktypesInputFile[] + ): Promise { + await this.dataSource.transaction(async (entityManager) => { + const worktypeRepo = entityManager.getRepository(Worktype); + const optionItemRepo = entityManager.getRepository(OptionItem); + + for (const worktypesInputFile of worktypesInputFiles) { + const accountId = worktypesInputFile.account_id; + const worktypeId = worktypesInputFile.custom_worktype_id; + const description = null; + + const duplicatedWorktype = await worktypeRepo.findOne({ + where: { account_id: accountId, custom_worktype_id: worktypeId }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: "pessimistic_write" }, + }); + + // ワークタイプIDが重複している場合はエラー + if (duplicatedWorktype) { + throw new WorktypeIdAlreadyExistsError( + `WorktypeID is already exists. WorktypeID: ${worktypeId}` + ); + } + + const worktypeCount = await worktypeRepo.count({ + where: { account_id: accountId }, + comment: `${context.getTrackingId()}_${new Date().toUTCString()}`, + lock: { mode: "pessimistic_write" }, + }); + + // ワークタイプの登録数が上限に達している場合はエラー + if (worktypeCount >= WORKTYPE_MAX_COUNT) { + throw new WorktypeIdMaxCountError( + `Number of worktype is exceeded the limit. MAX_COUNT: ${WORKTYPE_MAX_COUNT}, currentCount: ${worktypeCount}` + ); + } + + // ワークタイプを作成 + const worktype = await insertEntity( + Worktype, + worktypeRepo, + { + account_id: accountId, + custom_worktype_id: worktypeId, + description: description ?? null, + }, + this.isCommentOut, + context + ); + + // ワークタイプに紐づくオプションアイテムを10件作成 + const newOptionItems = Array.from({ length: OPTION_ITEM_NUM }, () => { + const optionItem = new OptionItem(); + optionItem.worktype_id = worktype.id; + optionItem.item_label = ""; + optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT; + optionItem.initial_value = ""; + + return optionItem; + }); + + await insertEntities( + OptionItem, + optionItemRepo, + newOptionItems, + this.isCommentOut, + context + ); + } + }); + } +}