diff --git a/data_migration_tools/.gitignore b/data_migration_tools/.gitignore new file mode 100644 index 0000000..df9b574 --- /dev/null +++ b/data_migration_tools/.gitignore @@ -0,0 +1 @@ +/tool* \ No newline at end of file diff --git a/data_migration_tools/baseNode.zip b/data_migration_tools/baseNode.zip new file mode 100644 index 0000000..60e180f Binary files /dev/null and b/data_migration_tools/baseNode.zip differ diff --git a/data_migration_tools/buildTool.ps1 b/data_migration_tools/buildTool.ps1 new file mode 100644 index 0000000..57879d5 --- /dev/null +++ b/data_migration_tools/buildTool.ps1 @@ -0,0 +1,8 @@ +# 移行ツールをビルドする +# docker ps + +$clientContainerName = "client_devcontainer-client-1" +$serverContainerName = "server_devcontainer-server-1" + +docker exec -t $clientContainerName sudo npm run build:local +docker exec -t $serverContainerName npm run build:exe \ No newline at end of file diff --git a/data_migration_tools/client/codegen.sh b/data_migration_tools/client/codegen.sh new file mode 100644 index 0000000..ee8c0bb --- /dev/null +++ b/data_migration_tools/client/codegen.sh @@ -0,0 +1,2 @@ +npx openapi-generator-cli version-manager set 7.1.0 +npx openapi-generator-cli generate -g typescript-axios -i /app/data_migration_tools/server/src/api/odms/openapi.json -o /app/data_migration_tools/client/src/api/ diff --git a/data_migration_tools/client/openapitools.json b/data_migration_tools/client/openapitools.json new file mode 100644 index 0000000..15fef60 --- /dev/null +++ b/data_migration_tools/client/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.1.0" + } +} diff --git a/data_migration_tools/client/src/AppRouter.tsx b/data_migration_tools/client/src/AppRouter.tsx index a4365c0..777b68c 100644 --- a/data_migration_tools/client/src/AppRouter.tsx +++ b/data_migration_tools/client/src/AppRouter.tsx @@ -1,8 +1,11 @@ import { Route, Routes } from "react-router-dom"; +import TopPage from "./pages/topPage"; +import DeletePage from "./pages/deletePage"; const AppRouter: React.FC = () => ( - } /> + } /> + } /> ); diff --git a/data_migration_tools/client/src/api/.gitignore b/data_migration_tools/client/src/api/.gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/data_migration_tools/client/src/api/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/data_migration_tools/client/src/api/.npmignore b/data_migration_tools/client/src/api/.npmignore new file mode 100644 index 0000000..999d88d --- /dev/null +++ b/data_migration_tools/client/src/api/.npmignore @@ -0,0 +1 @@ +# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/data_migration_tools/client/src/api/.openapi-generator-ignore b/data_migration_tools/client/src/api/.openapi-generator-ignore new file mode 100644 index 0000000..7484ee5 --- /dev/null +++ b/data_migration_tools/client/src/api/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/data_migration_tools/client/src/api/.openapi-generator/FILES b/data_migration_tools/client/src/api/.openapi-generator/FILES new file mode 100644 index 0000000..16b445e --- /dev/null +++ b/data_migration_tools/client/src/api/.openapi-generator/FILES @@ -0,0 +1,9 @@ +.gitignore +.npmignore +.openapi-generator-ignore +api.ts +base.ts +common.ts +configuration.ts +git_push.sh +index.ts diff --git a/data_migration_tools/client/src/api/.openapi-generator/VERSION b/data_migration_tools/client/src/api/.openapi-generator/VERSION new file mode 100644 index 0000000..3769235 --- /dev/null +++ b/data_migration_tools/client/src/api/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.1.0 \ No newline at end of file diff --git a/data_migration_tools/client/src/api/api.ts b/data_migration_tools/client/src/api/api.ts new file mode 100644 index 0000000..944557f --- /dev/null +++ b/data_migration_tools/client/src/api/api.ts @@ -0,0 +1,146 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ODMSOpenAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base'; + +/** + * + * @export + * @interface ErrorResponse + */ +export interface ErrorResponse { + /** + * + * @type {string} + * @memberof ErrorResponse + */ + 'message': string; + /** + * + * @type {string} + * @memberof ErrorResponse + */ + 'code': string; +} + +/** + * DeleteApi - axios parameter creator + * @export + */ +export const DeleteApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * すべてのデータを削除します + * @summary + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteData: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/delete`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DeleteApi - functional programming interface + * @export + */ +export const DeleteApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DeleteApiAxiosParamCreator(configuration) + return { + /** + * すべてのデータを削除します + * @summary + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteData(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteData(options); + const index = configuration?.serverIndex ?? 0; + const operationBasePath = operationServerMap['DeleteApi.deleteData']?.[index]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); + }, + } +}; + +/** + * DeleteApi - factory interface + * @export + */ +export const DeleteApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DeleteApiFp(configuration) + return { + /** + * すべてのデータを削除します + * @summary + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteData(options?: any): AxiosPromise { + return localVarFp.deleteData(options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DeleteApi - object-oriented interface + * @export + * @class DeleteApi + * @extends {BaseAPI} + */ +export class DeleteApi extends BaseAPI { + /** + * すべてのデータを削除します + * @summary + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DeleteApi + */ + public deleteData(options?: AxiosRequestConfig) { + return DeleteApiFp(this.configuration).deleteData(options).then((request) => request(this.axios, this.basePath)); + } +} + + + diff --git a/data_migration_tools/client/src/api/base.ts b/data_migration_tools/client/src/api/base.ts new file mode 100644 index 0000000..4e44af5 --- /dev/null +++ b/data_migration_tools/client/src/api/base.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ODMSOpenAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: AxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath ?? basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} + +interface ServerMap { + [key: string]: { + url: string, + description: string, + }[]; +} + +/** + * + * @export + */ +export const operationServerMap: ServerMap = { +} diff --git a/data_migration_tools/client/src/api/common.ts b/data_migration_tools/client/src/api/common.ts new file mode 100644 index 0000000..0e1c39c --- /dev/null +++ b/data_migration_tools/client/src/api/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ODMSOpenAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/data_migration_tools/client/src/api/configuration.ts b/data_migration_tools/client/src/api/configuration.ts new file mode 100644 index 0000000..9941122 --- /dev/null +++ b/data_migration_tools/client/src/api/configuration.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ODMSOpenAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + serverIndex?: number; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * override server index + * + * @type {number} + * @memberof Configuration + */ + serverIndex?: number; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.serverIndex = param.serverIndex; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/data_migration_tools/client/src/api/git_push.sh b/data_migration_tools/client/src/api/git_push.sh new file mode 100644 index 0000000..f53a75d --- /dev/null +++ b/data_migration_tools/client/src/api/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/data_migration_tools/client/src/api/index.ts b/data_migration_tools/client/src/api/index.ts new file mode 100644 index 0000000..c982723 --- /dev/null +++ b/data_migration_tools/client/src/api/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ODMSOpenAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/data_migration_tools/client/src/app/store.ts b/data_migration_tools/client/src/app/store.ts index 7727f4a..d34f6a0 100644 --- a/data_migration_tools/client/src/app/store.ts +++ b/data_migration_tools/client/src/app/store.ts @@ -1,7 +1,11 @@ import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"; +import auth from "features/auth/authSlice"; +import ui from "features/ui/uiSlice"; export const store = configureStore({ reducer: { + auth, + ui, }, }); diff --git a/data_migration_tools/client/src/common/error/code.ts b/data_migration_tools/client/src/common/error/code.ts new file mode 100644 index 0000000..3c488da --- /dev/null +++ b/data_migration_tools/client/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/client/src/common/error/makeErrorResponse.ts b/data_migration_tools/client/src/common/error/makeErrorResponse.ts new file mode 100644 index 0000000..0a677b4 --- /dev/null +++ b/data_migration_tools/client/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/client/src/common/error/message.ts b/data_migration_tools/client/src/common/error/message.ts new file mode 100644 index 0000000..9383694 --- /dev/null +++ b/data_migration_tools/client/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/client/src/common/error/types/types.ts b/data_migration_tools/client/src/common/error/types/types.ts new file mode 100644 index 0000000..8746924 --- /dev/null +++ b/data_migration_tools/client/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/client/src/common/errors/code.ts b/data_migration_tools/client/src/common/errors/code.ts new file mode 100644 index 0000000..6237762 --- /dev/null +++ b/data_migration_tools/client/src/common/errors/code.ts @@ -0,0 +1,17 @@ +/* +エラーコード作成方針 +E+6桁(数字)で構成する。 +- 1~2桁目の値は種類(業務エラー、システムエラー...) +- 3~4桁目の値は原因箇所(トークン、DB、...) +- 5~6桁目の値は任意の重複しない値 +ex) +E00XXXX : システムエラー(通信エラーやDB接続失敗など) +E01XXXX : 業務エラー +EXX00XX : 内部エラー(内部プログラムのエラー) +EXX01XX : トークンエラー(トークン認証関連) +EXX02XX : DBエラー(DB関連) +EXX03XX : ADB2Cエラー(DB関連) +*/ +export const errorCodes = [ + "E009999", // 汎用エラー +] as const; diff --git a/data_migration_tools/client/src/common/errors/index.ts b/data_migration_tools/client/src/common/errors/index.ts new file mode 100644 index 0000000..1543e4c --- /dev/null +++ b/data_migration_tools/client/src/common/errors/index.ts @@ -0,0 +1,3 @@ +export * from "./code"; +export * from "./types"; +export * from "./utils"; diff --git a/data_migration_tools/client/src/common/errors/types.ts b/data_migration_tools/client/src/common/errors/types.ts new file mode 100644 index 0000000..f4a42b4 --- /dev/null +++ b/data_migration_tools/client/src/common/errors/types.ts @@ -0,0 +1,9 @@ +import { errorCodes } from "./code"; + +export type ErrorObject = { + message: string; + code: ErrorCodeType; + statusCode?: number; +}; + +export type ErrorCodeType = (typeof errorCodes)[number]; diff --git a/data_migration_tools/client/src/common/errors/utils.ts b/data_migration_tools/client/src/common/errors/utils.ts new file mode 100644 index 0000000..3dd2410 --- /dev/null +++ b/data_migration_tools/client/src/common/errors/utils.ts @@ -0,0 +1,101 @@ +import { AxiosError } from "axios"; +import { isError } from "lodash"; +import { ErrorResponse } from "../../api"; +import { errorCodes } from "./code"; +import { ErrorCodeType, ErrorObject } from "./types"; + +export const createErrorObject = (error: unknown): ErrorObject => { + // 最低限通常のエラーかを判定 + // Error以外のものがthrowされた場合 + // 基本的にないはずだがプログラム上あるので拾う + if (!isError(error)) { + return { + message: "not error type.", + code: "E009999", + }; + } + + // Axiosエラー 通信してのエラーであるかを判定 + if (!isAxiosError(error)) { + return { + message: "not axios error.", + code: "E009999", + }; + } + + const errorResponse = error.response; + if (!errorResponse) { + return { + message: error.message, + code: "E009999", + statusCode: errorResponse, + }; + } + + const { data } = errorResponse; + + // 想定しているエラーレスポンスの型か判定 + if (!isErrorResponse(data)) { + return { + message: error.message, + code: "E009999", + statusCode: errorResponse.status, + }; + } + + const { message, code } = data; + + // 想定しているエラーコードかを判定 + if (!isErrorCode(code)) { + return { + message, + code: "E009999", + statusCode: errorResponse.status, + }; + } + + return { + message, + code, + statusCode: errorResponse.status, + }; +}; + +const isAxiosError = (e: unknown): e is AxiosError => { + const error = e as AxiosError; + return error?.isAxiosError ?? false; +}; + +const isErrorResponse = (error: unknown): error is ErrorResponse => { + const errorResponse = error as ErrorResponse; + if ( + errorResponse === undefined || + errorResponse.message === undefined || + errorResponse.code === undefined + ) { + return false; + } + + return true; +}; + +const isErrorCode = (errorCode: string): errorCode is ErrorCodeType => + errorCodes.includes(errorCode as ErrorCodeType); + +export const isErrorObject = ( + data: unknown +): data is { error: ErrorObject } => { + if ( + data && + typeof data === "object" && + "error" in data && + typeof (data as { error: ErrorObject }).error === "object" && + typeof (data as { error: ErrorObject }).error.message === "string" && + typeof (data as { error: ErrorObject }).error.code === "string" && + (typeof (data as { error: ErrorObject }).error.statusCode === "number" || + (data as { error: ErrorObject }).error.statusCode === undefined) + ) { + return true; + } + return false; +}; diff --git a/data_migration_tools/client/src/common/getBasePath.ts b/data_migration_tools/client/src/common/getBasePath.ts index f1053d4..f94fdff 100644 --- a/data_migration_tools/client/src/common/getBasePath.ts +++ b/data_migration_tools/client/src/common/getBasePath.ts @@ -1,6 +1,6 @@ export const getBasePath = () => { if (import.meta.env.VITE_STAGE === "local") { - return "http://localhost:8180"; + return "http://localhost:8280"; } return window.location.origin; }; diff --git a/data_migration_tools/client/src/features/auth/authSlice.ts b/data_migration_tools/client/src/features/auth/authSlice.ts new file mode 100644 index 0000000..cb014fa --- /dev/null +++ b/data_migration_tools/client/src/features/auth/authSlice.ts @@ -0,0 +1,15 @@ +import { createSlice } from "@reduxjs/toolkit"; +import type { AuthState } from "./state"; +import { initialConfig } from "./utils"; + +const initialState: AuthState = { + configuration: initialConfig(), +}; + +export const authSlice = createSlice({ + name: "auth", + initialState, + reducers: {}, +}); + +export default authSlice.reducer; diff --git a/data_migration_tools/client/src/features/auth/index.ts b/data_migration_tools/client/src/features/auth/index.ts new file mode 100644 index 0000000..9dcef78 --- /dev/null +++ b/data_migration_tools/client/src/features/auth/index.ts @@ -0,0 +1,3 @@ +export * from "./authSlice"; +export * from "./state"; +export * from "./utils"; diff --git a/data_migration_tools/client/src/features/auth/state.ts b/data_migration_tools/client/src/features/auth/state.ts new file mode 100644 index 0000000..e95c05f --- /dev/null +++ b/data_migration_tools/client/src/features/auth/state.ts @@ -0,0 +1,5 @@ +import { ConfigurationParameters } from "../../api"; + +export interface AuthState { + configuration: ConfigurationParameters; +} diff --git a/data_migration_tools/client/src/features/auth/utils.ts b/data_migration_tools/client/src/features/auth/utils.ts new file mode 100644 index 0000000..764dbb1 --- /dev/null +++ b/data_migration_tools/client/src/features/auth/utils.ts @@ -0,0 +1,13 @@ +import { ConfigurationParameters } from "../../api"; + +// 初期状態のAPI Config +export const initialConfig = (): ConfigurationParameters => { + const config: ConfigurationParameters = {}; + if (import.meta.env.VITE_STAGE === "local") { + config.basePath = "http://localhost:8280"; + } else { + config.basePath = `${window.location.origin}/dictation/api`; + } + + return config; +}; diff --git a/data_migration_tools/client/src/features/delete/deleteSlice.ts b/data_migration_tools/client/src/features/delete/deleteSlice.ts new file mode 100644 index 0000000..c89eac9 --- /dev/null +++ b/data_migration_tools/client/src/features/delete/deleteSlice.ts @@ -0,0 +1,25 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { DeleteState } from "./state"; +import { deleteDataAsync } from "./operations"; + +// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars +const initialState: DeleteState = {}; + +export const deleteSlice = createSlice({ + name: "detete", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(deleteDataAsync.pending, () => { + /* Empty Object */ + }); + builder.addCase(deleteDataAsync.fulfilled, () => { + /* Empty Object */ + }); + builder.addCase(deleteDataAsync.rejected, () => { + /* Empty Object */ + }); + }, +}); + +export default deleteSlice.reducer; diff --git a/data_migration_tools/client/src/features/delete/index.ts b/data_migration_tools/client/src/features/delete/index.ts new file mode 100644 index 0000000..b705514 --- /dev/null +++ b/data_migration_tools/client/src/features/delete/index.ts @@ -0,0 +1,3 @@ +export * from "./state"; +export * from "./deleteSlice"; +export * from "./operations"; diff --git a/data_migration_tools/client/src/features/delete/operations.ts b/data_migration_tools/client/src/features/delete/operations.ts new file mode 100644 index 0000000..7c275cc --- /dev/null +++ b/data_migration_tools/client/src/features/delete/operations.ts @@ -0,0 +1,47 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { openSnackbar } from "../ui/uiSlice"; +import type { RootState } from "../../app/store"; +import { ErrorObject, createErrorObject } from "../../common/errors"; +import { DeleteApi } from "../../api/api"; +import { Configuration } from "../../api/configuration"; + +export const deleteDataAsync = createAsyncThunk< + { + /* Empty Object */ + }, + void, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("delete/deleteDataAsync", async (args, thunkApi) => { + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration } = state.auth; + const config = new Configuration(configuration); + const deleteApi = new DeleteApi(config); + + try { + await deleteApi.deleteData(); + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: "削除しました。", + }) + ); + return {}; + } catch (e) { + const error = createErrorObject(e); + thunkApi.dispatch( + openSnackbar({ + level: "error", + message: "削除に失敗しました。", + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/data_migration_tools/client/src/features/delete/state.ts b/data_migration_tools/client/src/features/delete/state.ts new file mode 100644 index 0000000..3ac9dbd --- /dev/null +++ b/data_migration_tools/client/src/features/delete/state.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DeleteState {} diff --git a/data_migration_tools/client/src/features/ui/constants.ts b/data_migration_tools/client/src/features/ui/constants.ts new file mode 100644 index 0000000..9374dea --- /dev/null +++ b/data_migration_tools/client/src/features/ui/constants.ts @@ -0,0 +1,2 @@ +// 標準のスナックバー表示時間(ミリ秒) +export const DEFAULT_SNACKBAR_DURATION = 3000; diff --git a/data_migration_tools/client/src/features/ui/index.ts b/data_migration_tools/client/src/features/ui/index.ts new file mode 100644 index 0000000..464ecc2 --- /dev/null +++ b/data_migration_tools/client/src/features/ui/index.ts @@ -0,0 +1,5 @@ +export * from "./constants"; +export * from "./selectors"; +export * from "./state"; +export * from "./uiSlice"; +export * from "./types"; diff --git a/data_migration_tools/client/src/features/ui/selectors.ts b/data_migration_tools/client/src/features/ui/selectors.ts new file mode 100644 index 0000000..3fa47c0 --- /dev/null +++ b/data_migration_tools/client/src/features/ui/selectors.ts @@ -0,0 +1,15 @@ +import type { RootState } from "app/store"; +import { SnackbarLevel } from "./types"; + +export const selectSnackber = ( + state: RootState +): { + isOpen: boolean; + level: SnackbarLevel; + message: string; + duration?: number; +} => { + const { isOpen, level, message, duration } = state.ui; + + return { isOpen, level, message, duration }; +}; diff --git a/data_migration_tools/client/src/features/ui/state.ts b/data_migration_tools/client/src/features/ui/state.ts new file mode 100644 index 0000000..c9bb660 --- /dev/null +++ b/data_migration_tools/client/src/features/ui/state.ts @@ -0,0 +1,8 @@ +import { SnackbarLevel } from "./types"; + +export interface UIState { + isOpen: boolean; + level: SnackbarLevel; + message: string; + duration?: number; +} diff --git a/data_migration_tools/client/src/features/ui/types.ts b/data_migration_tools/client/src/features/ui/types.ts new file mode 100644 index 0000000..cacf182 --- /dev/null +++ b/data_migration_tools/client/src/features/ui/types.ts @@ -0,0 +1 @@ +export type SnackbarLevel = "info" | "error"; diff --git a/data_migration_tools/client/src/features/ui/uiSlice.ts b/data_migration_tools/client/src/features/ui/uiSlice.ts new file mode 100644 index 0000000..dd84534 --- /dev/null +++ b/data_migration_tools/client/src/features/ui/uiSlice.ts @@ -0,0 +1,38 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { SnackbarLevel } from "./types"; +import { UIState } from "./state"; +import { DEFAULT_SNACKBAR_DURATION } from "./constants"; + +const initialState: UIState = { + isOpen: false, + level: "error", + message: "", +}; + +export const uiSlice = createSlice({ + name: "ui", + initialState, + reducers: { + openSnackbar: ( + state, + action: PayloadAction<{ + level: SnackbarLevel; + message: string; + duration?: number; + }> + ) => { + const { level, message, duration } = action.payload; + state.isOpen = true; + state.level = level; + state.message = message; + state.duration = + level === "error" ? undefined : duration ?? DEFAULT_SNACKBAR_DURATION; + }, + closeSnackbar: (state) => { + state.isOpen = false; + }, + }, +}); +export const { openSnackbar, closeSnackbar } = uiSlice.actions; + +export default uiSlice.reducer; diff --git a/data_migration_tools/client/src/pages/deletePage/index.tsx b/data_migration_tools/client/src/pages/deletePage/index.tsx new file mode 100644 index 0000000..2643d87 --- /dev/null +++ b/data_migration_tools/client/src/pages/deletePage/index.tsx @@ -0,0 +1,32 @@ +import { AppDispatch } from "app/store"; +import { deleteDataAsync } from "features/delete"; +import React, { useCallback } from "react"; +import { useDispatch } from "react-redux"; +import { Link } from "react-router-dom"; + +const DeletePage = (): JSX.Element => { + const dispatch: AppDispatch = useDispatch(); + + const onDelete = useCallback(() => { + if ( + /* eslint-disable-next-line no-alert */ + !window.confirm("本当に削除しますか?") + ) { + return; + } + dispatch(deleteDataAsync()); + }, [dispatch]); + + return ( +
+

データ削除ツール

+ +
+ return to TopPage +
+ ); +}; + +export default DeletePage; diff --git a/data_migration_tools/client/src/pages/topPage/index.tsx b/data_migration_tools/client/src/pages/topPage/index.tsx new file mode 100644 index 0000000..9d244b0 --- /dev/null +++ b/data_migration_tools/client/src/pages/topPage/index.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +const TopPage = (): JSX.Element => { + console.log("DeletePage"); + return ( +
+ データ削除ツール +
+ return to TopPage +
+ ); +}; + +export default TopPage; diff --git a/data_migration_tools/package-lock.json b/data_migration_tools/package-lock.json new file mode 100644 index 0000000..343f405 --- /dev/null +++ b/data_migration_tools/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "data_migration_tools", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "author": "", + "description": "", + "license": "UNLICENSED", + "private": true, + "packages": {}, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/multer": "^1.4.7", + "@types/node": "^20.2.3", + "eslint": "^8.0.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "28.0.3", + "license-checker": "^25.0.1", + "prettier": "^2.3.2", + "source-map-support": "^0.5.20", + "supertest": "^6.1.3", + "swagger-ui-express": "^4.5.0", + "ts-jest": "28.0.1", + "ts-loader": "^9.2.3", + "ts-node": "^10.0.0", + "tsconfig-paths": "4.0.0", + "typescript": "^4.3.5" + }, + "jest": { + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testEnvironment": "node", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } + }, + "scripts": { + "build": "nest build && cp -r build dist", + "build:exe": "nest build && cp -r build dist && sh ./buildTool.sh", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"", + "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "prebuild": "rimraf dist", + "start": "nest start", + "start:debug": "nest start --debug --watch", + "start:dev": "nest start --watch", + "start:prod": "node dist/main", + "test": "jest", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:watch": "jest --watch" + } +} diff --git a/data_migration_tools/server/.devcontainer/docker-compose.yml b/data_migration_tools/server/.devcontainer/docker-compose.yml index 547e6db..9842f96 100644 --- a/data_migration_tools/server/.devcontainer/docker-compose.yml +++ b/data_migration_tools/server/.devcontainer/docker-compose.yml @@ -14,5 +14,11 @@ services: - "8280" environment: - CHOKIDAR_USEPOLLING=true + networks: + - external +networks: + external: + name: omds_network + external: true volumes: data_migration_tools_server_node_modules: diff --git a/data_migration_tools/server/.env b/data_migration_tools/server/.env index e69de29..c104e29 100644 --- a/data_migration_tools/server/.env +++ b/data_migration_tools/server/.env @@ -0,0 +1,5 @@ +DB_HOST=omds-mysql +DB_PORT=3306 +DB_NAME=omds +DB_USERNAME=omdsdbuser +DB_PASSWORD=omdsdbpass 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/buildTool.sh b/data_migration_tools/server/buildTool.sh new file mode 100644 index 0000000..6a8930f --- /dev/null +++ b/data_migration_tools/server/buildTool.sh @@ -0,0 +1,12 @@ +if [ ! -d "../tool" ]; then + mkdir "../tool" + echo "フォルダが作成されました。" +else + rm -f ../tool/ODMS_DataTool.exe +fi +unzip ../baseNode.zip -d ../tool +ncc build dist/main.js -o dist_single -a +npx -y postject ../tool/ODMS_DataTool.exe NODE_JS_CODE dist_single/index.js --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite +cp -r build ../tool +cp .env ../tool +cp .env.local.example ../tool \ 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 bc5a253..5987ef1 100644 --- a/data_migration_tools/server/package-lock.json +++ b/data_migration_tools/server/package-lock.json @@ -1,15 +1,17 @@ { - "name": "server", + "name": "data_migration_tool", "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "server", + "name": "data_migration_tool", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@azure/identity": "^4.0.1", "@azure/storage-blob": "^12.14.0", + "@microsoft/microsoft-graph-client": "^3.0.7", "@nestjs/common": "^9.3.9", "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.3.9", @@ -17,6 +19,7 @@ "@nestjs/serve-static": "^3.0.1", "@nestjs/swagger": "^6.2.1", "@nestjs/testing": "^9.3.9", + "@nestjs/typeorm": "^10.0.2", "@openapitools/openapi-generator-cli": "^2.5.2", "@vercel/ncc": "^0.36.1", "axios": "^1.3.4", @@ -24,9 +27,11 @@ "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", "multer": "^1.4.5-lts.1", + "mysql2": "^2.3.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "swagger-cli": "^4.0.4" + "swagger-cli": "^4.0.4", + "typeorm": "^0.3.20" }, "devDependencies": { "@types/express": "^4.17.17", @@ -239,11 +244,62 @@ } }, "node_modules/@azure/core-auth": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", - "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.6.0.tgz", + "integrity": "sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth/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/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==", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/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/core-client/node_modules/@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==", "dependencies": { - "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" }, "engines": { @@ -307,6 +363,46 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.14.0.tgz", + "integrity": "sha512-Tp4M6NsjCmn9L5p7HsW98eSOS7A0ibl3e5ntZglozT0XuD/0y6i36iW829ZbBq0qihlGgfaeFpkLjZ418KDm1Q==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/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/core-rest-pipeline/node_modules/@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==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@azure/core-tracing": { "version": "1.0.0-preview.13", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", @@ -331,6 +427,41 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/identity": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz", + "integrity": "sha512-yRdgF03SFLqUMZZ1gKWt0cs0fvrDIkq2bJ6Oidqcoo5uM85YMBnXWMzYKK30XqIT76lkFyAaoAAy5knXhrG4Lw==", + "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.3.0", + "@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": ">=18.0.0" + } + }, + "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", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@azure/logger": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", @@ -342,6 +473,46 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/msal-browser": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.10.0.tgz", + "integrity": "sha512-mnmi8dCXVNZI+AGRq0jKQ3YiodlIC4W9npr6FCB9WN6NQT+6rq+cIlxgUb//BjLyzKsnYo+i4LROGeMyU+6v1A==", + "dependencies": { + "@azure/msal-common": "14.7.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "14.7.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.7.1.tgz", + "integrity": "sha512-v96btzjM7KrAu4NSEdOkhQSTGOuNUIIsUdB8wlyB9cdgl5KqEKnTonHUZ8+khvZ6Ap542FCErbnTyDWl8lZ2rA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.4.tgz", + "integrity": "sha512-nNvEPx009/80UATCToF+29NZYocn01uKrB91xtFr7bSqkqO1PuQGXRyYwryWRztUrYZ1YsSbw9A+LmwOhpVvcg==", + "dependencies": { + "@azure/msal-common": "14.7.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@azure/storage-blob": { "version": "12.14.0", "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz", @@ -1026,7 +1197,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1038,7 +1209,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1173,6 +1344,95 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "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==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1588,7 +1848,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.0.0" } @@ -1632,7 +1892,7 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "devOptional": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", @@ -1657,6 +1917,32 @@ "node": ">=8" } }, + "node_modules/@microsoft/microsoft-graph-client": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.7.tgz", + "integrity": "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependenciesMeta": { + "@azure/identity": { + "optional": true + }, + "@azure/msal-browser": { + "optional": true + }, + "buffer": { + "optional": true + }, + "stream-browserify": { + "optional": true + } + } + }, "node_modules/@nestjs/axios": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", @@ -1968,6 +2254,33 @@ } } }, + "node_modules/@nestjs/typeorm": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", + "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", + "dependencies": { + "uuid": "9.0.1" + }, + "peerDependencies": { + "@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", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@nestjs/typeorm/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2243,6 +2556,15 @@ "node": ">=8.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -2267,29 +2589,42 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true + "devOptional": true }, "node_modules/@types/babel__core": { "version": "7.20.0", @@ -2775,7 +3110,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -2806,11 +3141,22 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -2886,6 +3232,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2899,6 +3250,14 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "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==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -2908,7 +3267,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/argparse": { "version": "1.0.10", @@ -3220,6 +3579,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3380,6 +3744,26 @@ "node": ">=8" } }, + "node_modules/cli-highlight": { + "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==", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -3650,13 +4034,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3681,11 +4064,15 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -3747,6 +4134,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3755,6 +4150,14 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3795,7 +4198,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -3839,6 +4242,11 @@ "node": ">=12" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "node_modules/easy-table": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", @@ -3847,6 +4255,14 @@ "wcwidth": ">=1.0.1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4598,6 +5014,32 @@ } } }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "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", @@ -4680,6 +5122,14 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4822,6 +5272,14 @@ "node": ">=8" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -4849,6 +5307,31 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5019,6 +5502,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5083,6 +5580,11 @@ "node": ">=8" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5106,6 +5608,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5114,8 +5627,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", @@ -5209,6 +5721,23 @@ "node": ">=6" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "28.0.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.0.3.tgz", @@ -6238,6 +6767,65 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6420,6 +7008,36 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -6432,6 +7050,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -6447,6 +7070,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6484,7 +7112,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -6589,6 +7217,14 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -6603,8 +7239,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multer": { "version": "1.4.5-lts.1", @@ -6628,6 +7263,80 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/mysql2": { + "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", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mysql2/node_modules/lru-cache": { + "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==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6790,6 +7499,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi-types": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", @@ -6930,6 +7655,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "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==", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6958,7 +7701,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6969,6 +7711,29 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -7575,7 +8340,6 @@ "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -7590,7 +8354,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7601,8 +8364,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { "version": "0.18.0", @@ -7645,6 +8407,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -7679,11 +8446,22 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7695,7 +8473,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -7831,6 +8608,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -7860,6 +8645,15 @@ "node": ">= 0.8" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -7907,6 +8701,20 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -7918,6 +8726,18 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8217,6 +9037,25 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "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==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -8356,7 +9195,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -8478,11 +9317,239 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/typeorm": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", + "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "chalk": "^4.1.2", + "cli-highlight": "^2.1.11", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.3.10", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.2.1", + "sha.js": "^2.4.11", + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0", + "mssql": "^9.1.1 || ^10.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/cliui": { + "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==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "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==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/typeorm/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8583,7 +9650,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -8732,7 +9799,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8773,6 +9839,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", @@ -8862,7 +9945,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -9032,12 +10115,55 @@ } }, "@azure/core-auth": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", - "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.6.0.tgz", + "integrity": "sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==", "requires": { - "@azure/abort-controller": "^1.0.0", + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.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/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" + } + }, + "@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/core-http": { @@ -9087,6 +10213,39 @@ "tslib": "^2.2.0" } }, + "@azure/core-rest-pipeline": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.14.0.tgz", + "integrity": "sha512-Tp4M6NsjCmn9L5p7HsW98eSOS7A0ibl3e5ntZglozT0XuD/0y6i36iW829ZbBq0qihlGgfaeFpkLjZ418KDm1Q==", + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.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/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/core-tracing": { "version": "1.0.0-preview.13", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", @@ -9105,6 +10264,37 @@ "tslib": "^2.2.0" } }, + "@azure/identity": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz", + "integrity": "sha512-yRdgF03SFLqUMZZ1gKWt0cs0fvrDIkq2bJ6Oidqcoo5uM85YMBnXWMzYKK30XqIT76lkFyAaoAAy5knXhrG4Lw==", + "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.3.0", + "@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/logger": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", @@ -9113,6 +10303,36 @@ "tslib": "^2.2.0" } }, + "@azure/msal-browser": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.10.0.tgz", + "integrity": "sha512-mnmi8dCXVNZI+AGRq0jKQ3YiodlIC4W9npr6FCB9WN6NQT+6rq+cIlxgUb//BjLyzKsnYo+i4LROGeMyU+6v1A==", + "requires": { + "@azure/msal-common": "14.7.1" + } + }, + "@azure/msal-common": { + "version": "14.7.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.7.1.tgz", + "integrity": "sha512-v96btzjM7KrAu4NSEdOkhQSTGOuNUIIsUdB8wlyB9cdgl5KqEKnTonHUZ8+khvZ6Ap542FCErbnTyDWl8lZ2rA==" + }, + "@azure/msal-node": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.4.tgz", + "integrity": "sha512-nNvEPx009/80UATCToF+29NZYocn01uKrB91xtFr7bSqkqO1PuQGXRyYwryWRztUrYZ1YsSbw9A+LmwOhpVvcg==", + "requires": { + "@azure/msal-common": "14.7.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, "@azure/storage-blob": { "version": "12.14.0", "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz", @@ -9640,7 +10860,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -9649,7 +10869,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -9753,6 +10973,64 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -10078,7 +11356,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true + "devOptional": true }, "@jridgewell/set-array": { "version": "1.1.2", @@ -10115,7 +11393,7 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "devOptional": true }, "@jridgewell/trace-mapping": { "version": "0.3.17", @@ -10137,6 +11415,15 @@ "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.0.1.tgz", "integrity": "sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==" }, + "@microsoft/microsoft-graph-client": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.7.tgz", + "integrity": "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw==", + "requires": { + "@babel/runtime": "^7.12.5", + "tslib": "^2.2.0" + } + }, "@nestjs/axios": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", @@ -10309,6 +11596,21 @@ "tslib": "2.5.0" } }, + "@nestjs/typeorm": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", + "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", + "requires": { + "uuid": "9.0.1" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -10493,6 +11795,12 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==" }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true + }, "@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -10517,29 +11825,39 @@ "@sinonjs/commons": "^1.7.0" } }, + "@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "devOptional": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true + "devOptional": true }, "@types/babel__core": { "version": "7.20.0", @@ -11018,7 +12336,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true + "devOptional": true }, "acorn-import-assertions": { "version": "1.8.0", @@ -11039,7 +12357,15 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "devOptional": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } }, "ajv": { "version": "8.12.0", @@ -11086,6 +12412,11 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -11096,6 +12427,11 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "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==" + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -11105,7 +12441,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "argparse": { "version": "1.0.10", @@ -11340,6 +12676,11 @@ "ieee754": "^1.1.13" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -11451,6 +12792,19 @@ "restore-cursor": "^3.1.0" } }, + "cli-highlight": { + "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==", + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + } + }, "cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -11661,13 +13015,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -11682,11 +13035,15 @@ "@babel/runtime": "^7.21.0" } }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -11728,11 +13085,21 @@ "clone": "^1.0.2" } }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -11763,7 +13130,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "devOptional": true }, "diff-sequences": { "version": "27.5.1", @@ -11792,6 +13159,11 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "easy-table": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", @@ -11800,6 +13172,14 @@ "wcwidth": ">=1.0.1" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12382,6 +13762,22 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -12445,6 +13841,14 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -12542,6 +13946,11 @@ "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -12566,6 +13975,25 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -12685,6 +14113,11 @@ "has": "^1.0.3" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -12728,6 +14161,11 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -12739,6 +14177,14 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -12747,8 +14193,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -12822,6 +14267,15 @@ "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" }, + "jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jest": { "version": "28.0.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.0.3.tgz", @@ -13622,6 +15076,63 @@ "universalify": "^2.0.0" } }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + } + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -13767,6 +15278,36 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13779,6 +15320,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -13788,6 +15334,11 @@ "is-unicode-supported": "^0.1.0" } }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -13818,7 +15369,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "makeerror": { "version": "1.0.12", @@ -13896,6 +15447,11 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, + "minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" + }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -13907,8 +15463,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multer": { "version": "1.4.5-lts.1", @@ -13929,6 +15484,69 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "mysql2": { + "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", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "lru-cache": { + "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==" + } + } + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "requires": { + "lru-cache": "^7.14.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + } + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -14052,6 +15670,16 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "openapi-types": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", @@ -14153,6 +15781,26 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "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==", + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -14171,8 +15819,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -14180,6 +15827,22 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" + } + } + }, "path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -14622,7 +16285,6 @@ "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, "requires": { "lru-cache": "^6.0.0" }, @@ -14631,7 +16293,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -14639,8 +16300,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, @@ -14686,6 +16346,11 @@ } } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -14717,11 +16382,19 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -14729,8 +16402,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "side-channel": { "version": "1.0.4", @@ -14853,6 +16525,11 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -14875,6 +16552,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -14915,6 +16597,16 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14923,6 +16615,14 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -15126,6 +16826,22 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "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==", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -15213,7 +16929,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, + "devOptional": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -15294,11 +17010,111 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "typeorm": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", + "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", + "requires": { + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "chalk": "^4.1.2", + "cli-highlight": "^2.1.11", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.3.10", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.2.1", + "sha.js": "^2.4.11", + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "cliui": { + "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==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==" + }, + "reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } + } + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "devOptional": true }, "uid": { "version": "2.0.1", @@ -15361,7 +17177,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "v8-to-istanbul": { "version": "9.1.0", @@ -15480,7 +17296,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -15506,6 +17321,16 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -15574,7 +17399,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "devOptional": true }, "yocto-queue": { "version": "0.1.0", diff --git a/data_migration_tools/server/package.json b/data_migration_tools/server/package.json index 6135ae6..a2299cb 100644 --- a/data_migration_tools/server/package.json +++ b/data_migration_tools/server/package.json @@ -19,10 +19,13 @@ "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand" + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "apigen": "ts-node src/api/generate.ts && prettier --write \"src/api/odms/*.json\"" }, "dependencies": { + "@azure/identity": "^4.0.1", "@azure/storage-blob": "^12.14.0", + "@microsoft/microsoft-graph-client": "^3.0.7", "@nestjs/common": "^9.3.9", "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.3.9", @@ -30,6 +33,7 @@ "@nestjs/serve-static": "^3.0.1", "@nestjs/swagger": "^6.2.1", "@nestjs/testing": "^9.3.9", + "@nestjs/typeorm": "^10.0.2", "@openapitools/openapi-generator-cli": "^2.5.2", "@vercel/ncc": "^0.36.1", "axios": "^1.3.4", @@ -37,9 +41,11 @@ "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", "multer": "^1.4.5-lts.1", + "mysql2": "^2.3.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", - "swagger-cli": "^4.0.4" + "swagger-cli": "^4.0.4", + "typeorm": "^0.3.20" }, "devDependencies": { "@types/express": "^4.17.17", diff --git a/data_migration_tools/server/src/api/generate.ts b/data_migration_tools/server/src/api/generate.ts new file mode 100644 index 0000000..5482a98 --- /dev/null +++ b/data_migration_tools/server/src/api/generate.ts @@ -0,0 +1,25 @@ +import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; +import { AppModule } from "../app.module"; +import { promises as fs } from "fs"; +import { NestFactory } from "@nestjs/core"; +async function bootstrap(): Promise { + const app = await NestFactory.create(AppModule, { + preview: true, + }); + + const options = new DocumentBuilder() + .setTitle("ODMSOpenAPI") + .setVersion("1.0.0") + .addBearerAuth({ + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + }) + .build(); + const document = SwaggerModule.createDocument(app, options); + await fs.writeFile( + "src/api/odms/openapi.json", + JSON.stringify(document, null, 0) + ); +} +bootstrap(); diff --git a/data_migration_tools/server/src/api/odms/openapi.json b/data_migration_tools/server/src/api/odms/openapi.json new file mode 100644 index 0000000..b783c09 --- /dev/null +++ b/data_migration_tools/server/src/api/odms/openapi.json @@ -0,0 +1,56 @@ +{ + "openapi": "3.0.0", + "paths": { + "/delete": { + "post": { + "operationId": "deleteData", + "summary": "", + "description": "すべてのデータを削除します", + "parameters": [], + "responses": { + "200": { + "description": "成功時のレスポンス", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/DeleteResponse" } + } + } + }, + "500": { + "description": "想定外のサーバーエラー", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ErrorResponse" } + } + } + } + }, + "tags": ["delete"] + } + } + }, + "info": { + "title": "ODMSOpenAPI", + "description": "", + "version": "1.0.0", + "contact": {} + }, + "tags": [], + "servers": [], + "components": { + "securitySchemes": { + "bearer": { "scheme": "bearer", "bearerFormat": "JWT", "type": "http" } + }, + "schemas": { + "DeleteResponse": { "type": "object", "properties": {} }, + "ErrorResponse": { + "type": "object", + "properties": { + "message": { "type": "string" }, + "code": { "type": "string" } + }, + "required": ["message", "code"] + } + } + } +} diff --git a/data_migration_tools/server/src/app.module.ts b/data_migration_tools/server/src/app.module.ts index 9e2dfe3..8089b78 100644 --- a/data_migration_tools/server/src/app.module.ts +++ b/data_migration_tools/server/src/app.module.ts @@ -1,8 +1,29 @@ import { MiddlewareConsumer, Module } from "@nestjs/common"; import { ServeStaticModule } from "@nestjs/serve-static"; -import { ConfigModule } from "@nestjs/config"; +import { ConfigModule, ConfigService } from "@nestjs/config"; +import { TypeOrmModule } from "@nestjs/typeorm"; import { join } from "path"; import { LoggerMiddleware } from "./common/loggerMiddleware"; +import { AdB2cModule } from "./gateways/adb2c/adb2c.module"; +import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module"; +import { RegisterController } from "./features/register/register.controller"; +import { RegisterService } from "./features/register/register.service"; +import { RegisterModule } from "./features/register/register.module"; +import { AccountsRepositoryModule } from "./repositories/accounts/accounts.repository.module"; +import { UsersRepositoryModule } from "./repositories/users/users.repository.module"; +import { SortCriteriaRepositoryModule } from "./repositories/sort_criteria/sort_criteria.repository.module"; +import { LicensesRepositoryModule } from "./repositories/licenses/licenses.repository.module"; +import { WorktypesRepositoryModule } from "./repositories/worktypes/worktypes.repository.module"; +import { AccountsController } from "./features/accounts/accounts.controller"; +import { AccountsService } from "./features/accounts/accounts.service"; +import { AccountsModule } from "./features/accounts/accounts.module"; +import { UsersController } from "./features/users/users.controller"; +import { UsersService } from "./features/users/users.service"; +import { UsersModule } from "./features/users/users.module"; +import { DeleteModule } from "./features/delete/delete.module"; +import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module"; +import { DeleteController } from "./features/delete/delete.controller"; +import { DeleteService } from "./features/delete/delete.service"; @Module({ imports: [ @@ -13,9 +34,35 @@ import { LoggerMiddleware } from "./common/loggerMiddleware"; 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) => ({ + type: "mysql", + host: configService.get("DB_HOST"), + port: configService.get("DB_PORT"), + username: configService.get("DB_USERNAME"), + password: configService.get("DB_PASSWORD"), + database: configService.get("DB_NAME"), + autoLoadEntities: true, // forFeature()で登録されたEntityを自動的にロード + synchronize: false, // trueにすると自動的にmigrationが行われるため注意 + }), + inject: [ConfigService], + }), ], - controllers: [], - providers: [], + 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/entity/bigintTransformer.spec.ts b/data_migration_tools/server/src/common/entity/bigintTransformer.spec.ts new file mode 100644 index 0000000..48c20d0 --- /dev/null +++ b/data_migration_tools/server/src/common/entity/bigintTransformer.spec.ts @@ -0,0 +1,61 @@ +import { bigintTransformer } from '.'; + +describe('bigintTransformer', () => { + describe('to', () => { + it('number型を整数を表す文字列に変換できる', () => { + expect(bigintTransformer.to(0)).toBe('0'); + expect(bigintTransformer.to(1)).toBe('1'); + expect(bigintTransformer.to(1234567890)).toBe('1234567890'); + expect(bigintTransformer.to(9007199254740991)).toBe('9007199254740991'); + expect(bigintTransformer.to(-1)).toBe('-1'); + }); + it('少数点以下がある場合はエラーとなる', () => { + expect(() => bigintTransformer.to(1.1)).toThrowError( + '1.1 is not integer.', + ); + }); + it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => { + expect(() => bigintTransformer.to(9007199254740992)).toThrowError( + 'value is greater than 9007199254740991.', + ); + expect(() => bigintTransformer.to(9223372036854775807)).toThrowError( + 'value is greater than 9007199254740991.', + ); + }); + }); + describe('from', () => { + it('bigint型の文字列をnumber型に変換できる', () => { + expect(bigintTransformer.from('0')).toBe(0); + expect(bigintTransformer.from('1')).toBe(1); + expect(bigintTransformer.from('1234567890')).toBe(1234567890); + expect(bigintTransformer.from('-1')).toBe(-1); + }); + it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => { + expect(() => bigintTransformer.from('9007199254740992')).toThrowError( + '9007199254740992 is greater than 9007199254740991.', + ); + expect(() => bigintTransformer.from('9223372036854775807')).toThrowError( + '9223372036854775807 is greater than 9007199254740991.', + ); + }); + it('number型の場合はそのまま返す', () => { + expect(bigintTransformer.from(0)).toBe(0); + expect(bigintTransformer.from(1)).toBe(1); + expect(bigintTransformer.from(1234567890)).toBe(1234567890); + expect(bigintTransformer.from(-1)).toBe(-1); + }); + it('nullの場合はそのまま返す', () => { + expect(bigintTransformer.from(null)).toBe(null); + }); + it('number型に変換できない場合はエラーとなる', () => { + expect(() => bigintTransformer.from('a')).toThrowError('a is not int.'); + expect(() => bigintTransformer.from('')).toThrowError(' is not int.'); + expect(() => bigintTransformer.from(undefined)).toThrowError( + 'undefined is not string.', + ); + expect(() => bigintTransformer.from({})).toThrowError( + '[object Object] is not string.', + ); + }); + }); +}); diff --git a/data_migration_tools/server/src/common/entity/index.ts b/data_migration_tools/server/src/common/entity/index.ts new file mode 100644 index 0000000..26f5d5b --- /dev/null +++ b/data_migration_tools/server/src/common/entity/index.ts @@ -0,0 +1,57 @@ +import { ValueTransformer } from 'typeorm'; + +// DBのbigint型をnumber型に変換するためのtransformer +// DBのBigInt型をそのまま扱うと、JSのNumber型の最大値を超えると誤差が発生するため、本来はNumber型に変換すべきではないが、 +// 影響範囲を最小限に抑えるため、Number型に変換する。使用するのはAutoIncrementされるIDのみの想定のため、 +// Number.MAX_SAFE_INTEGERより大きい値は現実的には発生しない想定で変換する。 +export const bigintTransformer: ValueTransformer = { + from: (value: any): number | null => { + // valueがnullであればそのまま返す + if (value === null) { + return value; + } + // valueがnumber型かどうかを判定 + // 利用DBによってはbigint型であってもnumber型で返ってくる場合があるため、number型の場合はそのまま返す(sqliteの場合) + if (typeof value === 'number') { + return value; + } + // valueが文字列かどうかを判定 + if (typeof value !== 'string') { + throw new Error(`${value} is not string.`); + } + // 数値に変換可能な文字列かどうかを判定 + if (Number.isNaN(parseInt(value))) { + throw new Error(`${value} is not int.`); + } + + // 文字列ならbigintに変換 + // valueが整数でない場合は値が丸められてしまうが、TypeORMのEntityの定義上、整数を表す文字列以外はありえないため、少数点は考慮しない + const bigIntValue = BigInt(value); + // bigIntValueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定 + if (bigIntValue > Number.MAX_SAFE_INTEGER) { + throw new Error(`${value} is greater than ${Number.MAX_SAFE_INTEGER}.`); + } + // number型で表現できる整数であればnumber型に変換して返す + return Number(bigIntValue); + }, + to: (value: any): string | null | undefined => { + // valueがnullまたはundefinedであればそのまま返す + if (value === null || value === undefined) { + return value; + } + // valueがnumber型かどうかを判定 + if (typeof value !== 'number') { + throw new Error(`${value} is not number.`); + } + + // valueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定 + if (value > Number.MAX_SAFE_INTEGER) { + throw new Error(`value is greater than ${Number.MAX_SAFE_INTEGER}.`); + } + // valueが整数かどうかを判定 + if (!Number.isInteger(value)) { + throw new Error(`${value} is not integer.`); + } + return value.toString(); + }, +}; 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/errors/code.ts b/data_migration_tools/server/src/common/errors/code.ts new file mode 100644 index 0000000..f16dd2a --- /dev/null +++ b/data_migration_tools/server/src/common/errors/code.ts @@ -0,0 +1,17 @@ +/* +エラーコード作成方針 +E+6桁(数字)で構成する。 +- 1~2桁目の値は種類(業務エラー、システムエラー...) +- 3~4桁目の値は原因箇所(トークン、DB、...) +- 5~6桁目の値は任意の重複しない値 +ex) +E00XXXX : システムエラー(通信エラーやDB接続失敗など) +E01XXXX : 業務エラー +EXX00XX : 内部エラー(内部プログラムのエラー) +EXX01XX : トークンエラー(トークン認証関連) +EXX02XX : DBエラー(DB関連) +EXX03XX : ADB2Cエラー(DB関連) +*/ +export const ErrorCodes = [ + "E009999", // 汎用エラー +] as const; diff --git a/data_migration_tools/server/src/common/errors/makeErrorResponse.ts b/data_migration_tools/server/src/common/errors/makeErrorResponse.ts new file mode 100644 index 0000000..cd83472 --- /dev/null +++ b/data_migration_tools/server/src/common/errors/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/errors/message.ts b/data_migration_tools/server/src/common/errors/message.ts new file mode 100644 index 0000000..f3d2fc1 --- /dev/null +++ b/data_migration_tools/server/src/common/errors/message.ts @@ -0,0 +1,6 @@ +import { Errors } from "./types/types"; + +// エラーコードとメッセージ対応表 +export const errors: Errors = { + E009999: "Internal Server Error.", +}; diff --git a/data_migration_tools/server/src/common/errors/types/types.ts b/data_migration_tools/server/src/common/errors/types/types.ts new file mode 100644 index 0000000..f68eda7 --- /dev/null +++ b/data_migration_tools/server/src/common/errors/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..386f9cd --- /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..1ba44a2 --- /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..6fbe071 --- /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..660b942 --- /dev/null +++ b/data_migration_tools/server/src/common/types/types.ts @@ -0,0 +1,231 @@ +export class csvInputFile { + type: string; + account_id: string; + parent_id: string; + email: string; + company_name: string; + first_name: string; + last_name: string; + country: string; + state: string; + start_date: Date; + expired_date: Date; + user_email: string; + author_id: string; + recording_mode: string; + wt1: string; + wt2: string; + wt3: string; + wt4: string; + wt5: string; + wt6: string; + wt7: string; + wt8: string; + wt9: string; + wt10: string; + wt11: string; + wt12: string; + wt13: string; + wt14: string; + wt15: string; + wt16: string; + wt17: string; + wt18: string; + wt19: string; + wt20: string; +} +export class AccountsOutputFileStep1 { + accountId: number; + type: string; + companyName: string; + country: string; + dealerAccountId?: number; + adminName: string; + adminMail: string; + userId: number; +} + +export class AccountsOutputFile { + accountId: number; + type: number; + companyName: string; + country: string; + dealerAccountId?: number; + adminName: string; + adminMail: string; + userId: number; +} +export class AccountsInputFile { + accountId: number; + type: number; + companyName: string; + country: string; + dealerAccountId?: number; + adminName: string; + adminMail: string; + userId: number; +} +export class UsersOutputFile { + accountId: number; + userId: number; + name: string; + role: string; + authorId: string; + email: string; +} + +export class UsersInputFile { + accountId: number; + userId: number; + name: string; + role: string; + authorId: string; + email: string; +} + +export class LicensesOutputFile { + expiry_date: string; + account_id: number; + type: string; + status: string; + allocated_user_id?: number; +} +export class LicensesInputFile { + expiry_date: string; + account_id: number; + type: string; + status: string; + allocated_user_id?: number; +} + +export class WorktypesOutputFile { + account_id: number; + custom_worktype_id: string; +} +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/common/validators/admin.validator.ts b/data_migration_tools/server/src/common/validators/admin.validator.ts new file mode 100644 index 0000000..d904154 --- /dev/null +++ b/data_migration_tools/server/src/common/validators/admin.validator.ts @@ -0,0 +1,39 @@ +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from "class-validator"; + +@ValidatorConstraint() +export class IsAdminPassword implements ValidatorConstraintInterface { + validate(value: string): boolean { + // 8文字~64文字でなければ早期に不合格 + const minLength = 8; + const maxLength = 64; + if (value.length < minLength || value.length > maxLength) { + return false; + } + + // 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!)から2種類以上組み合わせ + const charaTypePattern = + /^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/; + return new RegExp(charaTypePattern).test(value); + } + defaultMessage(): string { + return "Admin password rule not satisfied"; + } +} + +export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => { + return (object: any, propertyName: string) => { + registerDecorator({ + name: "IsAdminPasswordvalid", + target: object.constructor, + propertyName: propertyName, + constraints: [], + options: validationOptions, + validator: IsAdminPassword, + }); + }; +}; diff --git a/data_migration_tools/server/src/constants/index.ts b/data_migration_tools/server/src/constants/index.ts new file mode 100644 index 0000000..4330bb6 --- /dev/null +++ b/data_migration_tools/server/src/constants/index.ts @@ -0,0 +1,406 @@ +/** + * 階層 + * @const {number} + */ +export const TIERS = { + //OMDS東京 + TIER1: 1, + //OMDS現地法人 + TIER2: 2, + //代理店 + TIER3: 3, + //販売店 + TIER4: 4, + //エンドユーザー + TIER5: 5, +} as const; + +/** + * 音声ファイルをEast USに保存する国リスト + * @const {number} + */ +export const BLOB_STORAGE_REGION_US = ["CA", "KY", "US"]; + +/** + * 音声ファイルをAustralia Eastに保存する国リスト + * @const {number} + */ +export const BLOB_STORAGE_REGION_AU = ["AU", "NZ"]; + +/** + * 音声ファイルをNorth Europeに保存する国リスト + * @const {number} + */ +export const BLOB_STORAGE_REGION_EU = [ + "AT", + "BE", + "BG", + "HR", + "CY", + "CZ", + "DK", + "EE", + "FI", + "FR", + "DE", + "GR", + "HU", + "IS", + "IE", + "IT", + "LV", + "LI", + "LT", + "LU", + "MT", + "NL", + "NO", + "PL", + "PT", + "RO", + "RS", + "SK", + "SI", + "ZA", + "ES", + "SE", + "CH", + "TR", + "GB", +]; + +/** + * 管理ロール + * @const {string[]} + */ +export const ADMIN_ROLES = { + ADMIN: "admin", + STANDARD: "standard", +} as const; + +/** + * ロール + * @const {string[]} + */ +export const USER_ROLES = { + NONE: "none", + AUTHOR: "author", + TYPIST: "typist", +} as const; + +/** + * ロールのソート順 + * @const {string[]} + */ +export const USER_ROLE_ORDERS = [ + USER_ROLES.AUTHOR, + USER_ROLES.TYPIST, + USER_ROLES.NONE, +] as string[]; + +/** + * ライセンス注文状態 + * @const {string[]} + */ +export const LICENSE_ISSUE_STATUS = { + ISSUE_REQUESTING: "Issue Requesting", + ISSUED: "Issued", + CANCELED: "Order Canceled", +}; + +/** + * ライセンス種別 + * @const {string[]} + */ +export const LICENSE_TYPE = { + TRIAL: "TRIAL", + NORMAL: "NORMAL", + CARD: "CARD", +} as const; +/** + * ライセンス状態 + * @const {string[]} + */ +export const LICENSE_ALLOCATED_STATUS = { + UNALLOCATED: "Unallocated", + ALLOCATED: "Allocated", + REUSABLE: "Reusable", + DELETED: "Deleted", +} as const; +/** + * 切り替え元種別 + * @const {string[]} + */ +export const SWITCH_FROM_TYPE = { + NONE: "NONE", + CARD: "CARD", + TRIAL: "TRIAL", +} as const; + +/** + * ライセンスの期限切れが近いと見なす日数のしきい値 + * @const {number} + */ +export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14; + +/** + * ライセンスの有効期間 + * @const {number} + */ +export const LICENSE_EXPIRATION_DAYS = 365; + +/** + * タイムゾーンを加味したライセンスの有効期間(8時間) + * @const {number} + */ +export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8; + +/** + * カードライセンスの桁数 + * @const {number} + */ +export const CARD_LICENSE_LENGTH = 20; + +/** + * 音声ファイルに紐づくオプションアイテムの数 + * @const {string} + */ +export const OPTION_ITEM_NUM = 10; + +/** + * 文字起こしタスクのステータス + * @const {string[]} + */ +export const TASK_STATUS = { + UPLOADED: "Uploaded", + PENDING: "Pending", + IN_PROGRESS: "InProgress", + FINISHED: "Finished", + BACKUP: "Backup", +} as const; + +/** + * タスク一覧でソート可能な属性の一覧 + */ +export const TASK_LIST_SORTABLE_ATTRIBUTES = [ + "JOB_NUMBER", + "STATUS", + "ENCRYPTION", + "AUTHOR_ID", + "WORK_TYPE", + "FILE_NAME", + "FILE_LENGTH", + "FILE_SIZE", + "RECORDING_STARTED_DATE", + "RECORDING_FINISHED_DATE", + "UPLOAD_DATE", + "TRANSCRIPTION_STARTED_DATE", + "TRANSCRIPTION_FINISHED_DATE", +] as const; + +/** + * タスク一覧のソート条件(昇順・降順) + */ +export const SORT_DIRECTIONS = ["ASC", "DESC"] as const; + +/** + * 通知タグの最大個数 + * NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで + * https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions + */ +export const TAG_MAX_COUNT = 20; + +/** + * 通知のプラットフォーム種別文字列 + */ +export const PNS = { + WNS: "wns", + APNS: "apns", +}; + +/** + * ユーザーのライセンスの有効期限の状態 + */ +export const USER_LICENSE_EXPIRY_STATUS = { + NORMAL: "Normal", + NO_LICENSE: "NoLicense", + ALERT: "Alert", + RENEW: "Renew", +}; + +/** + *トライアルライセンスの有効期限(日数) + * @const {number} + */ +export const TRIAL_LICENSE_EXPIRATION_DAYS = 30; + +/** + * ライセンスの発行数 + * @const {number} + */ +export const TRIAL_LICENSE_ISSUE_NUM = 100; + +/** + * worktypeの最大登録数 + * @const {number} + */ +export const WORKTYPE_MAX_COUNT = 20; + +/** + * worktypeのDefault値の取りうる値 + **/ +export const OPTION_ITEM_VALUE_TYPE = { + DEFAULT: "Default", + BLANK: "Blank", + LAST_INPUT: "LastInput", +} as const; + +/** + * オプションアイテムのタイプ文字列と数値の対応 + **/ +export const OPTION_ITEM_VALUE_TYPE_NUMBER: { + type: string; + value: number; +}[] = [ + { + type: OPTION_ITEM_VALUE_TYPE.BLANK, + value: 1, + }, + { + type: OPTION_ITEM_VALUE_TYPE.DEFAULT, + value: 2, + }, + { + type: OPTION_ITEM_VALUE_TYPE.LAST_INPUT, + value: 3, + }, +]; + +/** + * ADB2Cユーザのidentity.signInType + * @const {string[]} + */ +export const ADB2C_SIGN_IN_TYPE = { + EMAILADDRESS: "emailAddress", +} as const; + +/** + * MANUAL_RECOVERY_REQUIRED + * @const {string} + */ +export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]"; + +/** + * 利用規約種別 + * @const {string[]} + */ +export const TERM_TYPE = { + EULA: "EULA", + DPA: "DPA", + PRIVACY_NOTICE: "PrivacyNotice", +} as const; + +/** + * 音声ファイルのフォーマット + * @const {string} + */ +export const USER_AUDIO_FORMAT = "DS2(QP)"; + +/** + * ユニットテスト実行をしている場合のNODE_ENVの値 + * @const {string[]} + */ +export const NODE_ENV_TEST = "test"; + +/** + * ユーザに対するライセンスの状態 + * @const {string[]} + */ +export const USER_LICENSE_STATUS = { + UNALLOCATED: "unallocated", + ALLOCATED: "allocated", + EXPIRED: "expired", +} as const; + +/** + * typeの取りうる値(移行元CSV) + * @const {string[]} + */ +export const MIGRATION_TYPE = { + ADMINISTRATOR: "Administrator", + BC: "BC", + COUNTRY: "Country", + CUSTOMER: "Customer", + DEALER: "Dealer", + DISTRIBUTOR: "Distributor", + USER: "USER", +} as const; + +/** + * 移行先の名称と移行元の値 + * @const {string[]} + */ +export const COUNTRY_LIST = [ + { value: "CA", label: "Canada" }, + { value: "KY", label: "Cayman Islands" }, + { value: "US", label: "U.S.A." }, + { value: "AU", label: "Australia" }, + { value: "NZ", label: "New Zealand" }, + { value: "AT", label: "Austria" }, + { value: "BE", label: "Belgium" }, + { value: "BG", label: "Bulgaria" }, + { value: "HR", label: "Croatia" }, + { value: "CY", label: "Cyprus" }, + { value: "CZ", label: "Czech Republic" }, + { value: "DK", label: "Denmark" }, + { value: "EE", label: "Estonia" }, + { value: "FI", label: "Finland" }, + { value: "FR", label: "France" }, + { value: "DE", label: "Germany" }, + { value: "GR", label: "Greece" }, + { value: "HU", label: "Hungary" }, + { value: "IS", label: "Iceland" }, + { value: "IE", label: "Ireland" }, + { value: "IT", label: "Italy" }, + { value: "LV", label: "Latvia" }, + { value: "LI", label: "Liechtenstein" }, + { value: "LT", label: "Lithuania" }, + { value: "LU", label: "Luxembourg" }, + { value: "MT", label: "Malta" }, + { value: "NL", label: "Netherlands" }, + { value: "NO", label: "Norway" }, + { value: "PL", label: "Poland" }, + { value: "PT", label: "Portugal" }, + { value: "RO", label: "Romania" }, + { value: "RS", label: "Serbia" }, + { value: "SK", label: "Slovakia" }, + { value: "SI", label: "Slovenia" }, + { value: "ZA", label: "South Africa" }, + { value: "ES", label: "Spain" }, + { value: "SE", label: "Sweden" }, + { value: "CH", label: "Switzerland" }, + { value: "TR", label: "Turkey" }, + { value: "GB", label: "United Kingdom" }, +]; + +/** + * recording_modeの取りうる値(移行元CSV) + * @const {string[]} + */ +export const RECORDING_MODE = { + DS2_QP: "DS2 (QP)", + DS2_SP: "DS2 (SP)", + DSS: "DSS", +} as const; + +/** + * AutoIncrementの初期値 + * @const {number} + */ +export const AUTO_INCREMENT_START = 853211; + +/** + * 移行データ登録時のsleep間隔 + * @const {number} + */ +export const MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC = 13; 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/delete/delete.controller.spec.ts b/data_migration_tools/server/src/features/delete/delete.controller.spec.ts new file mode 100644 index 0000000..ad763f0 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/delete.controller.spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { ConfigModule } from "@nestjs/config"; +import { DeleteService } from "./delete.service"; +import { DeleteController } from "./delete.controller"; + +describe("DeleteController", () => { + let controller: DeleteController; + const mockTemplatesService = {}; + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + envFilePath: [".env.test", ".env"], + isGlobal: true, + }), + ], + controllers: [DeleteController], + providers: [DeleteService], + }) + .overrideProvider(DeleteService) + .useValue(mockTemplatesService) + .compile(); + + controller = module.get(DeleteController); + }); + + it("should be defined", () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/data_migration_tools/server/src/features/delete/delete.controller.ts b/data_migration_tools/server/src/features/delete/delete.controller.ts new file mode 100644 index 0000000..94567ca --- /dev/null +++ b/data_migration_tools/server/src/features/delete/delete.controller.ts @@ -0,0 +1,39 @@ +import { + Controller, + HttpException, + HttpStatus, + Logger, + Post, + Req, +} from "@nestjs/common"; +import { ErrorResponse } from "../../common/errors/types/types"; +import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { Request } from "express"; +import { DeleteService } from "./delete.service"; +import { DeleteResponse } from "./types/types"; + +@ApiTags("delete") +@Controller("delete") +export class DeleteController { + constructor(private readonly deleteService: DeleteService) {} + + @ApiResponse({ + status: HttpStatus.OK, + type: DeleteResponse, + description: "成功時のレスポンス", + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: "想定外のサーバーエラー", + type: ErrorResponse, + }) + @ApiOperation({ + operationId: "deleteData", + description: "すべてのデータを削除します", + }) + @Post() + async deleteData(): Promise<{}> { + await this.deleteService.deleteData(); + return {}; + } +} diff --git a/data_migration_tools/server/src/features/delete/delete.module.ts b/data_migration_tools/server/src/features/delete/delete.module.ts new file mode 100644 index 0000000..ce27643 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/delete.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { DeleteRepositoryModule } from "../../repositories/delete/delete.repository.module"; +import { DeleteController } from "./delete.controller"; +import { DeleteService } from "./delete.service"; +import { AdB2cModule } from "../../gateways/adb2c/adb2c.module"; +import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module"; + +@Module({ + imports: [DeleteRepositoryModule, AdB2cModule, BlobstorageModule], + providers: [DeleteService], + controllers: [DeleteController], +}) +export class DeleteModule {} diff --git a/data_migration_tools/server/src/features/delete/delete.service.spec.ts b/data_migration_tools/server/src/features/delete/delete.service.spec.ts new file mode 100644 index 0000000..3257293 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/delete.service.spec.ts @@ -0,0 +1,29 @@ +import { DataSource } from "typeorm"; +import { ConfigModule } from "@nestjs/config"; +import { DeleteService } from "./delete.service"; +import { Test, TestingModule } from "@nestjs/testing"; + +describe("DeleteController", () => { + let service: DeleteService; + const mockTemplatesService = {}; + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + envFilePath: [".env.test", ".env"], + isGlobal: true, + }), + ], + providers: [DeleteService], + }) + .overrideProvider(DeleteService) + .useValue(mockTemplatesService) + .compile(); + + service = module.get(DeleteService); + }); + + it("should be defined", () => { + expect(service).toBeDefined(); + }); +}); diff --git a/data_migration_tools/server/src/features/delete/delete.service.ts b/data_migration_tools/server/src/features/delete/delete.service.ts new file mode 100644 index 0000000..9ade573 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/delete.service.ts @@ -0,0 +1,54 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { DeleteRepositoryService } from "../../repositories/delete/delete.repository.service"; +import { makeErrorResponse } from "../../common/errors/makeErrorResponse"; +import { AdB2cService } from "../../gateways/adb2c/adb2c.service"; +import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service"; + +@Injectable() +export class DeleteService { + private readonly logger = new Logger(DeleteService.name); + constructor( + private readonly deleteRepositoryService: DeleteRepositoryService, + private readonly blobstorageService: BlobstorageService, + private readonly adB2cService: AdB2cService + ) {} + + /** + * データを削除する + * @returns data + */ + async deleteData(): Promise { + this.logger.log(`[IN] ${this.deleteData.name}`); + try { + // BlobStorageからデータを削除する + await this.blobstorageService.deleteContainers(); + + // ADB2Cからユーザ情報を取得する + const users = await this.adB2cService.getUsers(); + const externalIds = users.map((user) => user.id); + await this.adB2cService.deleteUsers(externalIds); + + // データベースからデータを削除する + await this.deleteRepositoryService.deleteData(); + // AutoIncrementの値をリセットする + await this.deleteRepositoryService.resetAutoIncrement(); + } catch (e) { + this.logger.error(`error=${e}`); + if (e instanceof Error) { + switch (e.constructor) { + default: + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + } + throw new HttpException( + makeErrorResponse("E009999"), + HttpStatus.INTERNAL_SERVER_ERROR + ); + } finally { + this.logger.log(`[OUT] ${this.deleteData.name}`); + } + } +} diff --git a/data_migration_tools/server/src/features/delete/test/utility.ts b/data_migration_tools/server/src/features/delete/test/utility.ts new file mode 100644 index 0000000..e185d27 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/test/utility.ts @@ -0,0 +1 @@ +import { DataSource } from "typeorm"; diff --git a/data_migration_tools/server/src/features/delete/types/types.ts b/data_migration_tools/server/src/features/delete/types/types.ts new file mode 100644 index 0000000..1042758 --- /dev/null +++ b/data_migration_tools/server/src/features/delete/types/types.ts @@ -0,0 +1 @@ +export class DeleteResponse {} 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/transfer/transfer.controller.ts b/data_migration_tools/server/src/features/transfer/transfer.controller.ts new file mode 100644 index 0000000..8ac9923 --- /dev/null +++ b/data_migration_tools/server/src/features/transfer/transfer.controller.ts @@ -0,0 +1,174 @@ +import { + Body, + Controller, + HttpStatus, + Post, + Req, + HttpException, + Logger, +} from "@nestjs/common"; +import fs from "fs"; +import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { Request } from "express"; +import { transferRequest, transferResponse } from "./types/types"; +import { transferService } from "./transfer.service"; +import { makeContext } from "../../common/log"; +import { csvInputFile } from "../../common/types/types"; +import { makeErrorResponse } from "src/common/errors/makeErrorResponse"; +import { + COUNTRY_LIST, + MIGRATION_TYPE, + TIERS, + WORKTYPE_MAX_COUNT, + RECORDING_MODE, + LICENSE_ALLOCATED_STATUS, + USER_ROLES, + AUTO_INCREMENT_START, +} from "../../../src/constants"; +@ApiTags("transfer") +@Controller("transfer") +export class transferController { + private readonly logger = new Logger(transferController.name); + constructor(private readonly transferService: transferService) {} + + @Post() + @ApiResponse({ + status: HttpStatus.OK, + type: transferResponse, + description: "成功時のレスポンス", + }) + @ApiResponse({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + description: "想定外のサーバーエラー", + }) + @ApiOperation({ operationId: "dataRegist" }) + async dataRegist( + @Body() body: transferRequest, + @Req() req: Request + ): Promise { + const context = makeContext("iko", "transfer"); + + const inputFilePath = body.inputFilePath; + + this.logger.log( + `[IN] [${context.getTrackingId()}] ${ + this.dataRegist.name + } | params: { inputFilePath: ${inputFilePath}};` + ); + try { + // 読み込みファイルのフルパス + const csvFileFullPath = inputFilePath + ".csv"; + + // ファイル存在チェックと読み込み + if (!fs.existsSync(csvFileFullPath)) { + this.logger.error(`file not exists from ${inputFilePath}`); + throw new Error(`file not exists from ${inputFilePath}`); + } + + // CSVファイルを全行読み込む + const inputFile = fs.readFileSync(csvFileFullPath, "utf-8"); + + // レコードごとに分割 + const csvInputFileLines = inputFile.split("\n"); + + // ヘッダー行を削除 + csvInputFileLines.shift(); + + // 項目ごとに切り分ける + let csvInputFile: csvInputFile[] = []; + csvInputFileLines.forEach((line) => { + const data = line.split(","); + csvInputFile.push({ + type: data[0], + account_id: data[1], + parent_id: data[2], + email: data[3], + company_name: data[4], + first_name: data[5], + last_name: data[6], + country: data[7], + state: data[8], + start_date: new Date(data[9]), + expired_date: new Date(data[10]), + user_email: data[11], + author_id: data[12], + recording_mode: data[13], + wt1: data[14], + wt2: data[15], + wt3: data[16], + wt4: data[17], + wt5: data[18], + wt6: data[19], + wt7: data[20], + wt8: data[21], + wt9: data[22], + wt10: data[23], + wt11: data[24], + wt12: data[25], + wt13: data[26], + wt14: data[27], + wt15: data[28], + wt16: data[29], + wt17: data[30], + wt18: data[31], + wt19: data[32], + wt20: data[33], + }); + }); + + // 各データのバリデーションチェック + await this.transferService.validateInputData(context, csvInputFile); + + // account_idを通番に変換し、変換前account_id: 変換後accountId配列を作成する。 + const accountIdList = csvInputFile.map((line) => line.account_id); + const accountIdListSet = new Set(accountIdList); + const accountIdListArray = Array.from(accountIdListSet); + const accountIdMap = new Map(); + accountIdListArray.forEach((accountId, index) => { + accountIdMap.set(accountId, index + AUTO_INCREMENT_START); + }); + // CSVファイルの変換 + const transferResponse = await this.transferService.registInputData( + context, + csvInputFile, + accountIdMap + ); + + // countryを除いた階層の再配置 + const accountsOutputFileStep1Lines = + transferResponse.accountsOutputFileStep1Lines; + const accountsOutputFile = await this.transferService.relocateHierarchy( + context, + accountsOutputFileStep1Lines + ); + // メールアドレスの重複を削除 + // デモライセンスの削除 + // いったんこのままコミットしてテストを実施する + + // transferResponseを4つのJSONファイルの出力する(出力先はinputと同じにする) + const outputFilePath = body.inputFilePath; + const usersOutputFile = transferResponse.usersOutputFileLines; + const licensesOutputFile = transferResponse.licensesOutputFileLines; + const worktypesOutputFile = transferResponse.worktypesOutputFileLines; + this.transferService.outputJsonFile( + context, + outputFilePath, + accountsOutputFile, + usersOutputFile, + licensesOutputFile, + worktypesOutputFile + ); + 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}` + ); + } + } +} diff --git a/data_migration_tools/server/src/features/transfer/transfer.module.ts b/data_migration_tools/server/src/features/transfer/transfer.module.ts new file mode 100644 index 0000000..b2a7893 --- /dev/null +++ b/data_migration_tools/server/src/features/transfer/transfer.module.ts @@ -0,0 +1,9 @@ +import { Module } from "@nestjs/common"; +import { transferController } from "./transfer.controller"; +import { transferService } from "./transfer.service"; +@Module({ + imports: [], + controllers: [transferController], + providers: [transferService], +}) +export class transferModule {} diff --git a/data_migration_tools/server/src/features/transfer/transfer.service.ts b/data_migration_tools/server/src/features/transfer/transfer.service.ts new file mode 100644 index 0000000..70b5d26 --- /dev/null +++ b/data_migration_tools/server/src/features/transfer/transfer.service.ts @@ -0,0 +1,357 @@ +import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common"; +import { Context } from "../../common/log"; +import { + AccountsOutputFileStep1, + UsersOutputFile, + LicensesOutputFile, + WorktypesOutputFile, + csvInputFile, + AccountsOutputFile, +} from "../../common/types/types"; +import { + COUNTRY_LIST, + MIGRATION_TYPE, + TIERS, + WORKTYPE_MAX_COUNT, + RECORDING_MODE, + LICENSE_ALLOCATED_STATUS, + USER_ROLES, + SWITCH_FROM_TYPE, +} from "src/constants"; +import { registInputDataResponse } from "./types/types"; +import fs from "fs"; + +@Injectable() +export class transferService { + constructor() {} + private readonly logger = new Logger(transferService.name); + + /** + * Regist Data + * @param OutputFilePath: string + * @param csvInputFile: csvInputFile[] + */ + async registInputData( + context: Context, + csvInputFile: csvInputFile[], + accountIdMap: Map + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.registInputData.name}` + ); + + try { + let accountsOutputFileStep1Lines: AccountsOutputFileStep1[] = []; + let usersOutputFileLines: UsersOutputFile[] = []; + let licensesOutputFileLines: LicensesOutputFile[] = []; + let worktypesOutputFileLines: WorktypesOutputFile[] = []; + + let userIdIndex = 0; + // csvInputFileを一行読み込みする + csvInputFile.forEach((line) => { + // typeが"USER"以外の場合、アカウントデータの作成を行う + if (line.type !== MIGRATION_TYPE.USER) { + // userIdのインクリメント + userIdIndex = userIdIndex + 1; + // line.countryの値を読み込みCOUNTRY_LISTのlabelからvalueに変換する + const country = COUNTRY_LIST.find( + (country) => country.label === line.country + )?.value; + // adminNameの変換(last_name + " "+ first_name) + const adminName = `${line.last_name} ${line.first_name}`; + + // ランダムパスワードの生成(データ登録ツール側で行うのでやらない) + // common/password/password.tsのmakePasswordを使用 + // const autoGeneratedPassword = makePassword(); + + // parentAccountIdの設定 + // parent_idが存在する場合、accountIdMapを参照し、accountIdに変換する + let parentAccountId: number | null = null; + if (line.parent_id) { + parentAccountId = accountIdMap.get(line.parent_id); + } + // AccountsOutputFile配列にPush + accountsOutputFileStep1Lines.push({ + // accountIdはaccountIdMapから取得する + accountId: accountIdMap.get(line.account_id), + type: line.type, + companyName: line.company_name, + country: country, + dealerAccountId: parentAccountId, + adminName: adminName, + adminMail: line.email, + userId: userIdIndex, + }); + } else { + // typeが"USER"の場合、ユーザデータの作成を行う + // userIdのインクリメント + userIdIndex = userIdIndex + 1; + // nameの変換 + // もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする + // 存在する場合は、last_name + " " + first_name + let name = line.email; + if (line.last_name && line.first_name) { + name = `${line.last_name} ${line.first_name}`; + } + // roleの変換 + // authorIdが設定されてる場合はauthor、されていない場合は移行しないので次の行に進む + if (line.author_id) { + usersOutputFileLines.push({ + accountId: accountIdMap.get(line.account_id), + userId: userIdIndex, + name: name, + role: USER_ROLES.AUTHOR, + authorId: line.author_id, + email: line.user_email, + }); + } else { + return; + } + // ライセンスのデータの作成を行う + // authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID + // されていない場合、statusは"reusable"、allocated_user_idはnull + licensesOutputFileLines.push({ + expiry_date: line.expired_date.toISOString(), + account_id: accountIdMap.get(line.account_id), + type: SWITCH_FROM_TYPE.NONE, + status: line.author_id + ? LICENSE_ALLOCATED_STATUS.ALLOCATED + : LICENSE_ALLOCATED_STATUS.REUSABLE, + allocated_user_id: line.author_id ? userIdIndex : null, + }); + // WorktypesOutputFileの作成 + // wt1~wt20まで読み込み、account単位で作成する + // 作成したWorktypesOutputFileを配列にPush + for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) { + const wt = `wt${i}`; + if (line[wt]) { + // 既に存在する場合は、作成しない + if ( + worktypesOutputFileLines.find( + (worktype) => + worktype.account_id === accountIdMap.get(line.account_id) && + worktype.custom_worktype_id === line[wt].toString() + ) + ) { + continue; + } + } + } + } + // つぎの行に進む + }); + return { + accountsOutputFileStep1Lines, + usersOutputFileLines, + licensesOutputFileLines, + worktypesOutputFileLines, + }; + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.registInputData.name}` + ); + } + } + + /** + * 階層の付け替えを行う + * @param accountsOutputFileStep1: AccountsOutputFileStep1[] + * @returns AccountsOutputFile[] + */ + async relocateHierarchy( + context: Context, + accountsOutputFileStep1: AccountsOutputFileStep1[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.relocateHierarchy.name}` + ); + + try { + // dealerAccountIdを検索し、typeがCountryの場合 + accountsOutputFileStep1.forEach((account) => { + if (account.type === MIGRATION_TYPE.COUNTRY) { + // そのacccountIdをdealerAccountIdにもつアカウント(Distributor)を検索する + const distributor = accountsOutputFileStep1.find( + (distributor) => + account.type === MIGRATION_TYPE.DISTRIBUTOR && + distributor.dealerAccountId === account.accountId + ); + // DistributorのdealerAccountIdをBC(Countryの親)に付け替える + distributor.dealerAccountId = account.dealerAccountId; + } + }); + // typeがCountryのアカウントを取り除く + accountsOutputFileStep1 = accountsOutputFileStep1.filter( + (account) => account.type !== MIGRATION_TYPE.COUNTRY + ); + + // typeをtierに変換し、AccountsOutputFileに変換する + let accountsOutputFile: AccountsOutputFile[] = []; + accountsOutputFileStep1.forEach((account) => { + let tier = 0; + switch (account.type) { + case MIGRATION_TYPE.ADMINISTRATOR: + tier = TIERS.TIER1; + break; + case MIGRATION_TYPE.BC: + tier = TIERS.TIER2; + break; + case MIGRATION_TYPE.DISTRIBUTOR: + tier = TIERS.TIER3; + break; + case MIGRATION_TYPE.DEALER: + tier = TIERS.TIER4; + break; + case MIGRATION_TYPE.CUSTOMER: + tier = TIERS.TIER5; + break; + } + accountsOutputFile.push({ + accountId: account.accountId, + type: tier, + companyName: account.companyName, + country: account.country, + dealerAccountId: account.dealerAccountId, + adminName: account.adminName, + adminMail: account.adminMail, + userId: account.userId, + }); + }); + return accountsOutputFile; + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.relocateHierarchy.name}` + ); + } + } + + /** + * JSONファイルの出力 + * @param outputFilePath: string + * @param accountsOutputFile: AccountsOutputFile[] + * @param usersOutputFile: UsersOutputFile[] + * @param licensesOutputFile: LicensesOutputFile[] + * @param worktypesOutputFile: WorktypesOutputFile[] + */ + async outputJsonFile( + context: Context, + outputFilePath: string, + accountsOutputFile: AccountsOutputFile[], + usersOutputFile: UsersOutputFile[], + licensesOutputFile: LicensesOutputFile[], + worktypesOutputFile: WorktypesOutputFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.outputJsonFile.name}` + ); + + try { + // JSONファイルの出力を行う + // accountsOutputFile配列の出力 + const accountsOutputFileJson = JSON.stringify(accountsOutputFile); + fs.writeFileSync( + `${outputFilePath}_accounts.json`, + accountsOutputFileJson + ); + // usersOutputFile + const usersOutputFileJson = JSON.stringify(usersOutputFile); + fs.writeFileSync(`${outputFilePath}_users.json`, usersOutputFileJson); + // licensesOutputFile + const licensesOutputFileJson = JSON.stringify(licensesOutputFile); + fs.writeFileSync( + `${outputFilePath}_licenses.json`, + licensesOutputFileJson + ); + // worktypesOutputFile + const worktypesOutputFileJson = JSON.stringify(worktypesOutputFile); + fs.writeFileSync( + `${outputFilePath}_worktypes.json`, + worktypesOutputFileJson + ); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.outputJsonFile.name}` + ); + } + } + + /** + * データのバリデーションチェック + * @param csvInputFile: csvInputFile[] + */ + async validateInputData( + context: Context, + csvInputFile: csvInputFile[] + ): Promise { + // パラメータ内容が長大なのでログには出さない + this.logger.log( + `[IN] [${context.getTrackingId()}] ${this.validateInputData.name}` + ); + + try { + // csvInputFileのバリデーションチェックを行う + csvInputFile.forEach((line, index) => { + // typeのバリデーションチェック + if ( + line.type !== MIGRATION_TYPE.ADMINISTRATOR && + line.type !== MIGRATION_TYPE.BC && + line.type !== MIGRATION_TYPE.DISTRIBUTOR && + line.type !== MIGRATION_TYPE.DEALER && + line.type !== MIGRATION_TYPE.CUSTOMER && + line.type !== MIGRATION_TYPE.USER + ) { + throw new HttpException( + `type is invalid. index=${index} type=${line.type}`, + HttpStatus.BAD_REQUEST + ); + } + // countryのバリデーションチェック + if (!COUNTRY_LIST.find((country) => country.label === line.country)) { + throw new HttpException( + `country is invalid. index=${index} country=${line.country}`, + HttpStatus.BAD_REQUEST + ); + } + // mailのバリデーションチェック + // メールアドレスの形式が正しいかどうかのチェック + const mailRegExp = new RegExp( + /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/ + ); + if (!mailRegExp.test(line.email)) { + throw new HttpException( + `email is invalid. index=${index} email=${line.email}`, + HttpStatus.BAD_REQUEST + ); + } + // recording_modeのバリデーションチェック + // RECORDING_MODEに存在するかどうかのチェック + if ( + line.recording_mode !== RECORDING_MODE.DS2_QP && + line.recording_mode !== RECORDING_MODE.DS2_SP && + line.recording_mode !== RECORDING_MODE.DSS && + line.recording_mode !== null + ) { + throw new HttpException( + `recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`, + HttpStatus.BAD_REQUEST + ); + } + }); + } catch (e) { + this.logger.error(`[${context.getTrackingId()}] error=${e}`); + } finally { + this.logger.log( + `[OUT] [${context.getTrackingId()}] ${this.validateInputData.name}` + ); + } + } +} diff --git a/data_migration_tools/server/src/features/transfer/types/types.ts b/data_migration_tools/server/src/features/transfer/types/types.ts new file mode 100644 index 0000000..63eb3ab --- /dev/null +++ b/data_migration_tools/server/src/features/transfer/types/types.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { + AccountsOutputFileStep1, + LicensesOutputFile, + UsersOutputFile, + WorktypesOutputFile, +} from "src/common/types/types"; + +export class transferRequest { + @ApiProperty() + inputFilePath: string; +} + +export class transferResponse {} + +export class registInputDataResponse { + @ApiProperty() + accountsOutputFileStep1Lines: AccountsOutputFileStep1[]; + @ApiProperty() + usersOutputFileLines: UsersOutputFile[]; + @ApiProperty() + licensesOutputFileLines: LicensesOutputFile[]; + @ApiProperty() + worktypesOutputFileLines: WorktypesOutputFile[]; +} 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.module.ts b/data_migration_tools/server/src/gateways/adb2c/adb2c.module.ts new file mode 100644 index 0000000..f54582b --- /dev/null +++ b/data_migration_tools/server/src/gateways/adb2c/adb2c.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { ConfigModule } from "@nestjs/config"; +import { AdB2cService } from "./adb2c.service"; + +@Module({ + imports: [ConfigModule], + exports: [AdB2cService], + providers: [AdB2cService], +}) +export class AdB2cModule {} diff --git a/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts new file mode 100644 index 0000000..b561f42 --- /dev/null +++ b/data_migration_tools/server/src/gateways/adb2c/adb2c.service.ts @@ -0,0 +1,218 @@ +import { ClientSecretCredential } from "@azure/identity"; +import { Client } from "@microsoft/microsoft-graph-client"; +import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; +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"; + message: string; +}; + +export class Adb2cTooManyRequestsError extends Error {} + +export const isConflictError = (arg: unknown): arg is ConflictError => { + const value = arg as ConflictError; + if (value.message === undefined) { + return false; + } + if (value.reason === "email") { + return true; + } + return false; +}; + +@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"), + this.configService.getOrThrow("ADB2C_CLIENT_ID"), + this.configService.getOrThrow("ADB2C_CLIENT_SECRET") + ); + const authProvider = new TokenCredentialAuthenticationProvider(credential, { + scopes: ["https://graph.microsoft.com/.default"], + }); + + 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 + * @returns users + */ + async getUsers(): Promise { + this.logger.log(`[IN] ${this.getUsers.name}`); + + try { + const res: AdB2cResponse = await this.graphClient + .api(`users/`) + .select(["id", "displayName", "identities"]) + .filter(`creationType eq 'LocalAccount'`) + .get(); + + return res.value; + } catch (e) { + this.logger.error(`error=${e}`); + const { statusCode } = e; + if (statusCode === 429) { + throw new Adb2cTooManyRequestsError(); + } + + throw e; + } finally { + this.logger.log(`[OUT] ${this.getUsers.name}`); + } + } + + /** + * 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 + */ + async deleteUsers(externalIds: string[]): Promise { + this.logger.log( + `[IN]${this.deleteUsers.name} | params: { externalIds: ${externalIds} };` + ); + + try { + // 複数ユーザーを一括削除する方法がないため、1人ずつで削除を行う + const results = await Promise.allSettled( + externalIds.map(async (externalId) => { + await this.graphClient.api(`users/${externalId}`).delete(); + await new Promise((resolve) => setTimeout(resolve, 15)); // 15ms待つ + this.logger.log(`[[ADB2C DELETE] externalId: ${externalId}`); + }) + ); + + // 失敗したプロミスのエラーをログに記録 + results.forEach((result, index) => { + // statusがrejectedでない場合は、エラーが発生していないためログに記録しない + if (result.status !== "rejected") { + return; + } + + const failedId = externalIds[index]; + if (isPromiseRejectedResult(result)) { + const error = result.reason.toString(); + + this.logger.error(`Failed to delete user ${failedId}: ${error}`); + } else { + this.logger.error(`Failed to delete user ${failedId}`); + } + }); + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.deleteUsers.name}`); + } + } +} diff --git a/data_migration_tools/server/src/gateways/adb2c/types/types.ts b/data_migration_tools/server/src/gateways/adb2c/types/types.ts new file mode 100644 index 0000000..a7261ef --- /dev/null +++ b/data_migration_tools/server/src/gateways/adb2c/types/types.ts @@ -0,0 +1,15 @@ +export type AdB2cResponse = { + '@odata.context': string; + value: AdB2cUser[]; +}; +export type AdB2cUser = { + id: string; + displayName: string; + identities?: UserIdentity[]; +}; + +export type UserIdentity = { + signInType: string; + issuer: string; + issuerAssignedId: string; +}; diff --git a/data_migration_tools/server/src/gateways/adb2c/utils/utils.ts b/data_migration_tools/server/src/gateways/adb2c/utils/utils.ts new file mode 100644 index 0000000..e1b640e --- /dev/null +++ b/data_migration_tools/server/src/gateways/adb2c/utils/utils.ts @@ -0,0 +1,22 @@ +import { ADB2C_SIGN_IN_TYPE } from '../../../constants'; +import { AdB2cUser } from '../types/types'; + +export const isPromiseRejectedResult = ( + data: unknown, +): data is PromiseRejectedResult => { + return ( + data !== null && + typeof data === 'object' && + 'status' in data && + 'reason' in data + ); +}; + +// 生のAdB2cUserのレスポンスから表示名とメールアドレスを取得する +export const getUserNameAndMailAddress = (user: AdB2cUser) => { + const { displayName, identities } = user; + const emailAddress = identities?.find( + (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS, + )?.issuerAssignedId; + return { displayName, emailAddress }; +}; diff --git a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.module.ts b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.module.ts new file mode 100644 index 0000000..bc4f4a7 --- /dev/null +++ b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { BlobstorageService } from './blobstorage.service'; +import { ConfigModule } from '@nestjs/config'; + +@Module({ + exports: [BlobstorageService], + imports: [ConfigModule], + providers: [BlobstorageService], +}) +export class BlobstorageModule {} diff --git a/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts new file mode 100644 index 0000000..7c26388 --- /dev/null +++ b/data_migration_tools/server/src/gateways/blobstorage/blobstorage.service.ts @@ -0,0 +1,155 @@ +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); + private readonly blobServiceClientUS: BlobServiceClient; + private readonly blobServiceClientEU: BlobServiceClient; + private readonly blobServiceClientAU: BlobServiceClient; + private readonly sharedKeyCredentialUS: StorageSharedKeyCredential; + private readonly sharedKeyCredentialAU: StorageSharedKeyCredential; + private readonly sharedKeyCredentialEU: StorageSharedKeyCredential; + constructor(private readonly configService: ConfigService) { + this.sharedKeyCredentialUS = new StorageSharedKeyCredential( + this.configService.getOrThrow("STORAGE_ACCOUNT_NAME_US"), + this.configService.getOrThrow("STORAGE_ACCOUNT_KEY_US") + ); + this.sharedKeyCredentialAU = new StorageSharedKeyCredential( + this.configService.getOrThrow("STORAGE_ACCOUNT_NAME_AU"), + this.configService.getOrThrow("STORAGE_ACCOUNT_KEY_AU") + ); + this.sharedKeyCredentialEU = new StorageSharedKeyCredential( + this.configService.getOrThrow("STORAGE_ACCOUNT_NAME_EU"), + this.configService.getOrThrow("STORAGE_ACCOUNT_KEY_EU") + ); + this.blobServiceClientUS = new BlobServiceClient( + this.configService.getOrThrow("STORAGE_ACCOUNT_ENDPOINT_US"), + this.sharedKeyCredentialUS + ); + this.blobServiceClientAU = new BlobServiceClient( + this.configService.getOrThrow("STORAGE_ACCOUNT_ENDPOINT_AU"), + this.sharedKeyCredentialAU + ); + this.blobServiceClientEU = new BlobServiceClient( + this.configService.getOrThrow("STORAGE_ACCOUNT_ENDPOINT_EU"), + this.sharedKeyCredentialEU + ); + } + + /** + * 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 + */ + async deleteContainers(): Promise { + this.logger.log(`[IN] ${this.deleteContainers.name}`); + + try { + for await (const container of this.blobServiceClientAU.listContainers({ + prefix: "account-", + })) { + const client = this.blobServiceClientAU.getContainerClient( + container.name + ); + await client.deleteIfExists(); + } + for await (const container of this.blobServiceClientEU.listContainers({ + prefix: "account-", + })) { + const client = this.blobServiceClientEU.getContainerClient( + container.name + ); + await client.deleteIfExists(); + } + for await (const container of this.blobServiceClientUS.listContainers({ + prefix: "account-", + })) { + const client = this.blobServiceClientUS.getContainerClient( + container.name + ); + await client.deleteIfExists(); + } + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + 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/main.ts b/data_migration_tools/server/src/main.ts index 39d5e39..218ab27 100644 --- a/data_migration_tools/server/src/main.ts +++ b/data_migration_tools/server/src/main.ts @@ -1,35 +1,35 @@ -import { NestFactory } from "@nestjs/core"; -import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; -import { AppModule } from "./app.module"; -import { ValidationPipe } from "@nestjs/common"; -import { LoggerMiddleware } from "./common/loggerMiddleware"; -import cookieParser from "cookie-parser"; +import { NestFactory } from '@nestjs/core'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { LoggerMiddleware } from './common/loggerMiddleware'; +import cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule, { - cors: process.env.CORS === "TRUE", + cors: process.env.CORS === 'TRUE', }); app.use(new LoggerMiddleware(), cookieParser()); // バリデーター(+型の自動変換機能)を適用 app.useGlobalPipes( - new ValidationPipe({ transform: true, forbidUnknownValues: false }) + new ValidationPipe({ transform: true, forbidUnknownValues: false }), ); - if (process.env.STAGE === "local") { + if (process.env.STAGE === 'local') { const options = new DocumentBuilder() - .setTitle("data_migration_toolsOpenAPI") - .setVersion("1.0.0") + .setTitle('data_migration_toolsOpenAPI') + .setVersion('1.0.0') .addBearerAuth({ - type: "http", - scheme: "bearer", - bearerFormat: "JWT", + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', }) .build(); const document = SwaggerModule.createDocument(app, options); - SwaggerModule.setup("api", app, document); + SwaggerModule.setup('api', app, document); } - await app.listen(process.env.PORT || 8180); + await app.listen(process.env.PORT || 8280); } bootstrap(); 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/delete/delete.repository.module.ts b/data_migration_tools/server/src/repositories/delete/delete.repository.module.ts new file mode 100644 index 0000000..e1797fc --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/delete.repository.module.ts @@ -0,0 +1,60 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { DeleteRepositoryService } from "./delete.repository.service"; +import { Account } from "./entity/account.entity"; +import { AudioFile } from "./entity/audio_file.entity"; +import { AudioOptionItem } from "./entity/audio_option_item.entity"; +import { CheckoutPermission } from "./entity/checkout_permission.entity"; +import { + CardLicense, + CardLicenseIssue, + License, + LicenseAllocationHistory, + LicenseAllocationHistoryArchive, + LicenseArchive, + LicenseOrder, +} from "./entity/license.entity"; +import { OptionItem } from "./entity/option_item.entity"; +import { SortCriteria } from "./entity/sort_criteria.entity"; +import { Task } from "./entity/task.entity"; +import { TemplateFile } from "./entity/template_file.entity"; +import { Term } from "./entity/term.entity"; +import { UserGroupMember } from "./entity/user_group_member.entity"; +import { UserGroup } from "./entity/user_group.entity"; +import { User, UserArchive } from "./entity/user.entity"; +import { WorkflowTypist } from "./entity/workflow_typists.entity"; +import { Workflow } from "./entity/workflow.entity"; +import { Worktype } from "./entity/worktype.entity"; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Account, + AudioFile, + AudioOptionItem, + CheckoutPermission, + License, + LicenseOrder, + CardLicense, + CardLicenseIssue, + LicenseArchive, + LicenseAllocationHistory, + LicenseAllocationHistoryArchive, + OptionItem, + SortCriteria, + Task, + TemplateFile, + Term, + UserGroupMember, + UserGroup, + User, + UserArchive, + WorkflowTypist, + Workflow, + Worktype, + ]), + ], + providers: [DeleteRepositoryService], + exports: [DeleteRepositoryService], +}) +export class DeleteRepositoryModule {} diff --git a/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts b/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts new file mode 100644 index 0000000..52539c0 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/delete.repository.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from "@nestjs/common"; +import { DataSource } from "typeorm"; +import { logger } from "@azure/identity"; +import { Account } from "./entity/account.entity"; +import { AUTO_INCREMENT_START } from "../../constants"; + +@Injectable() +export class DeleteRepositoryService { + constructor(private dataSource: DataSource) {} + + /** + * 全テーブルをTrancateする + * @returns data + */ + async deleteData(): Promise { + const entities = this.dataSource.entityMetadatas; + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.startTransaction(); + await queryRunner.query("SET FOREIGN_KEY_CHECKS=0"); + for (const entity of entities) { + await queryRunner.query(`TRUNCATE TABLE \`${entity.tableName}\``); + } + await queryRunner.query("SET FOREIGN_KEY_CHECKS=1"); + await queryRunner.commitTransaction(); + } catch (err) { + await queryRunner.rollbackTransaction(); + logger.error(err); + throw err; + } finally { + await queryRunner.release(); + } + } + + /** + * AutoIncrementの値をリセットする + * @returns data + */ + async resetAutoIncrement(): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.startTransaction(); + await queryRunner.query( + `ALTER TABLE accounts AUTO_INCREMENT = ${AUTO_INCREMENT_START}` + ); + await queryRunner.commitTransaction(); + } catch (err) { + await queryRunner.rollbackTransaction(); + logger.error(err); + throw err; + } finally { + await queryRunner.release(); + } + } +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/account.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/account.entity.ts new file mode 100644 index 0000000..de92919 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/account.entity.ts @@ -0,0 +1,70 @@ +import { bigintTransformer } from "../../../common/entity"; +import { User } from "./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/delete/entity/audio_file.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/audio_file.entity.ts new file mode 100644 index 0000000..e606af1 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/audio_file.entity.ts @@ -0,0 +1,43 @@ +import { Task } from "./task.entity"; +import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"; + +@Entity({ name: "audio_files" }) +export class AudioFile { + @PrimaryGeneratedColumn() + id: number; + + @Column() + account_id: number; + @Column() + owner_user_id: number; + @Column() + url: string; + @Column() + file_name: string; + @Column() + author_id: string; + @Column() + work_type_id: string; + @Column() + started_at: Date; + @Column({ type: "time" }) + duration: string; + @Column() + finished_at: Date; + @Column() + uploaded_at: Date; + @Column() + file_size: number; + @Column() + priority: string; + @Column() + audio_format: string; + @Column({ nullable: true, type: "varchar" }) + comment: string | null; + @Column({ nullable: true, type: "datetime" }) + deleted_at: Date | null; + @Column() + is_encrypted: boolean; + @OneToOne(() => Task, (task) => task.file) + task: Task | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/audio_option_item.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/audio_option_item.entity.ts new file mode 100644 index 0000000..51e65ff --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/audio_option_item.entity.ts @@ -0,0 +1,23 @@ +import { Task } from "./task.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + ManyToOne, + JoinColumn, +} from "typeorm"; + +@Entity({ name: "audio_option_items" }) +export class AudioOptionItem { + @PrimaryGeneratedColumn() + id: number; + @Column() + audio_file_id: number; + @Column() + label: string; + @Column() + value: string; + @ManyToOne(() => Task, (task) => task.audio_file_id) + @JoinColumn({ name: "audio_file_id", referencedColumnName: "audio_file_id" }) + task: Task | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/checkout_permission.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/checkout_permission.entity.ts new file mode 100644 index 0000000..ecd0781 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/checkout_permission.entity.ts @@ -0,0 +1,38 @@ +import { bigintTransformer } from "../../../common/entity"; +import { Task } from "./task.entity"; +import { UserGroup } from "./user_group.entity"; +import { User } from "./user.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + JoinColumn, + ManyToOne, +} from "typeorm"; + +@Entity({ name: "checkout_permission" }) +export class CheckoutPermission { + @PrimaryGeneratedColumn() + id: number; + + @Column({}) + task_id: number; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + user_id: number | null; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + user_group_id: number | null; + + @ManyToOne(() => User, (user) => user.id) + @JoinColumn({ name: "user_id" }) + user: User | null; + + @ManyToOne(() => UserGroup, (group) => group.id) + @JoinColumn({ name: "user_group_id" }) + user_group: UserGroup | null; + + @ManyToOne(() => Task, (task) => task.id) + @JoinColumn({ name: "task_id" }) + task: Task | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/license.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/license.entity.ts new file mode 100644 index 0000000..2ce6edf --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/license.entity.ts @@ -0,0 +1,322 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToOne, + JoinColumn, + ManyToOne, + PrimaryColumn, +} from "typeorm"; +import { User } from "./user.entity"; +import { bigintTransformer } from "../../../common/entity"; + +@Entity({ name: "license_orders" }) +export class LicenseOrder { + @PrimaryGeneratedColumn() + id: number; + + @Column() + po_number: string; + + @Column() + from_account_id: number; + + @Column() + to_account_id: number; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + ordered_at: Date; + + @Column({ nullable: true, type: "datetime" }) + issued_at: Date | null; + + @Column() + quantity: number; + + @Column() + status: string; + + @Column({ nullable: true, type: "datetime" }) + canceled_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: "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; + + @OneToOne(() => User, (user) => user.license, { + createForeignKeyConstraints: false, + }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @JoinColumn({ name: "allocated_user_id" }) + user: User | null; +} + +@Entity({ name: "card_license_issue" }) +export class CardLicenseIssue { + @PrimaryGeneratedColumn() + id: number; + + @Column() + issued_at: Date; + + @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; +} + +@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; + + @ManyToOne(() => License, (licenses) => licenses.id, { + createForeignKeyConstraints: false, + }) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定 + @JoinColumn({ name: "license_id" }) + license: License | null; +} + +@Entity({ name: "licenses_archive" }) +export class LicenseArchive { + @PrimaryColumn() + 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; + + @Column() + created_at: Date; + + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + + @Column() + updated_at: Date; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + archived_at: Date; +} + +@Entity({ name: "license_allocation_history_archive" }) +export class LicenseAllocationHistoryArchive { + @PrimaryColumn() + 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; + + @Column() + created_at: Date; + + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + + @Column() + updated_at: Date; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) + archived_at: Date; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/option_item.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/option_item.entity.ts new file mode 100644 index 0000000..f9e7ac4 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/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/delete/entity/sort_criteria.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/sort_criteria.entity.ts new file mode 100644 index 0000000..260c9a9 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/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/delete/entity/task.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/task.entity.ts new file mode 100644 index 0000000..1daca02 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/task.entity.ts @@ -0,0 +1,71 @@ +import { AudioOptionItem } from "./audio_option_item.entity"; +import { AudioFile } from "./audio_file.entity"; +import { User } from "./user.entity"; +import { TemplateFile } from "./template_file.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + OneToOne, + JoinColumn, + OneToMany, + ManyToOne, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; +import { bigintTransformer } from "../../../common/entity"; + +@Entity({ name: "tasks" }) +export class Task { + @PrimaryGeneratedColumn() + id: number; + @Column() + job_number: string; + @Column() + account_id: number; + @Column({ nullable: true, type: "boolean" }) + is_job_number_enabled: boolean | null; + @Column() + audio_file_id: number; + @Column() + status: string; + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + typist_user_id: number | null; + @Column() + priority: string; + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + template_file_id: number | null; + @Column({ nullable: true, type: "datetime" }) + started_at: Date | null; + @Column({ nullable: true, type: "datetime" }) + finished_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; + @OneToOne(() => AudioFile, (audiofile) => audiofile.task) + @JoinColumn({ name: "audio_file_id" }) + file: AudioFile | null; + @OneToMany(() => AudioOptionItem, (option) => option.task) + option_items: AudioOptionItem[] | null; + @OneToOne(() => User, (user) => user.id) + @JoinColumn({ name: "typist_user_id" }) + typist_user: User | null; + @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) + @JoinColumn({ name: "template_file_id" }) + template_file: TemplateFile | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/template_file.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/template_file.entity.ts new file mode 100644 index 0000000..79c14f5 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/template_file.entity.ts @@ -0,0 +1,31 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, +} from "typeorm"; +import { Task } from "./task.entity"; + +@Entity({ name: "template_files" }) +export class TemplateFile { + @PrimaryGeneratedColumn() + id: number; + @Column() + account_id: number; + @Column() + url: string; + @Column() + file_name: string; + @Column({ nullable: true, type: "datetime" }) + created_by: string | null; + @CreateDateColumn() + created_at: Date; + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + @UpdateDateColumn() + updated_at: Date; + @OneToMany(() => Task, (task) => task.template_file) + tasks: Task[] | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/term.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/term.entity.ts new file mode 100644 index 0000000..7a2097b --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/term.entity.ts @@ -0,0 +1,37 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity({ name: 'terms' }) +export class Term { + @PrimaryGeneratedColumn() + id: number; + + @Column() + document_type: string; + + @Column() + version: string; + + @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: 'varchar' }) + 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/delete/entity/user.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/user.entity.ts new file mode 100644 index 0000000..f7fa3d7 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/user.entity.ts @@ -0,0 +1,170 @@ +import { Account } from "./account.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + OneToOne, + OneToMany, + PrimaryColumn, +} from "typeorm"; +import { License } from "./license.entity"; +import { UserGroupMember } from "./user_group_member.entity"; + +@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; + + @OneToOne(() => License, (license) => license.user) + license: License | null; + + @OneToMany(() => UserGroupMember, (userGroupMember) => userGroupMember.user) + userGroupMembers: UserGroupMember[] | null; +} + +@Entity({ name: "users_archive" }) +export class UserArchive { + @PrimaryColumn() + 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() + email_verified: boolean; + + @Column() + auto_renew: boolean; + + @Column() + notification: boolean; + + @Column() + encryption: boolean; + + @Column() + prompt: boolean; + + @Column({ nullable: true, type: "datetime" }) + deleted_at: Date | null; + + @Column({ nullable: true, type: "datetime" }) + created_by: string | null; + + @Column() + created_at: Date; + + @Column({ nullable: true, type: "datetime" }) + updated_by: string | null; + + @Column() + updated_at: Date; + + @CreateDateColumn({ + default: () => "datetime('now', 'localtime')", + type: "datetime", + }) // defaultはSQLite用設定値.本番用は別途migrationで設定 + archived_at: Date; +} + +export type newUser = Omit< + User, + | "id" + | "deleted_at" + | "created_at" + | "updated_at" + | "updated_by" + | "created_by" + | "account" + | "license" + | "userGroupMembers" + | "email_verified" +>; diff --git a/data_migration_tools/server/src/repositories/delete/entity/user_group.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/user_group.entity.ts new file mode 100644 index 0000000..2a1fbce --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/user_group.entity.ts @@ -0,0 +1,48 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + OneToMany, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; +import { UserGroupMember } from './user_group_member.entity'; + +@Entity({ name: 'user_group' }) +export class UserGroup { + @PrimaryGeneratedColumn() + id: number; + + @Column() + account_id: number; + + @Column() + name: 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', + }) // 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; + + @OneToMany( + () => UserGroupMember, + (userGroupMember) => userGroupMember.userGroup, + ) + userGroupMembers: UserGroupMember[] | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/user_group_member.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/user_group_member.entity.ts new file mode 100644 index 0000000..1afa807 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/user_group_member.entity.ts @@ -0,0 +1,52 @@ +import { User } from "./user.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + JoinColumn, + ManyToOne, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; +import { UserGroup } from "./user_group.entity"; + +@Entity({ name: "user_group_member" }) +export class UserGroupMember { + @PrimaryGeneratedColumn() + id: number; + + @Column() + user_group_id: number; + + @Column() + user_id: number; + + @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 | 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(() => User, (user) => user.id) + @JoinColumn({ name: "user_id" }) + user: User | null; + + @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) + @JoinColumn({ name: "user_group_id" }) + userGroup: UserGroup | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/workflow.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/workflow.entity.ts new file mode 100644 index 0000000..83cdaae --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/workflow.entity.ts @@ -0,0 +1,66 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, + JoinColumn, + ManyToOne, +} from "typeorm"; +import { WorkflowTypist } from "./workflow_typists.entity"; +import { Worktype } from "./worktype.entity"; +import { TemplateFile } from "./template_file.entity"; +import { User } from "./user.entity"; +import { bigintTransformer } from "../../../common/entity"; + +@Entity({ name: "workflows" }) +export class Workflow { + @PrimaryGeneratedColumn() + id: number; + + @Column() + account_id: number; + + @Column() + author_id: number; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + worktype_id: number | null; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + template_id: number | 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(() => User, (user) => user.id) + @JoinColumn({ name: "author_id" }) + author: User | null; + + @ManyToOne(() => Worktype, (worktype) => worktype.id) + @JoinColumn({ name: "worktype_id" }) + worktype: Worktype | null; + + @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) + @JoinColumn({ name: "template_id" }) + template: TemplateFile | null; + + @OneToMany(() => WorkflowTypist, (workflowTypist) => workflowTypist.workflow) + workflowTypists: WorkflowTypist[] | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/workflow_typists.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/workflow_typists.entity.ts new file mode 100644 index 0000000..e404ba0 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/workflow_typists.entity.ts @@ -0,0 +1,58 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from "typeorm"; +import { Workflow } from "./workflow.entity"; +import { User } from "./user.entity"; +import { UserGroup } from "./user_group.entity"; +import { bigintTransformer } from "../../../common/entity"; + +@Entity({ name: "workflow_typists" }) +export class WorkflowTypist { + @PrimaryGeneratedColumn() + id: number; + + @Column() + workflow_id: number; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + typist_id: number | null; + + @Column({ nullable: true, type: "bigint", transformer: bigintTransformer }) + typist_group_id: number | 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(() => Workflow, (workflow) => workflow.id) + @JoinColumn({ name: "workflow_id" }) + workflow: Workflow | null; + + @ManyToOne(() => User, (user) => user.id) + @JoinColumn({ name: "typist_id" }) + typist: User | null; + + @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) + @JoinColumn({ name: "typist_group_id" }) + typistGroup: UserGroup | null; +} diff --git a/data_migration_tools/server/src/repositories/delete/entity/worktype.entity.ts b/data_migration_tools/server/src/repositories/delete/entity/worktype.entity.ts new file mode 100644 index 0000000..d444a00 --- /dev/null +++ b/data_migration_tools/server/src/repositories/delete/entity/worktype.entity.ts @@ -0,0 +1,49 @@ +import { Account } from "./account.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, +} from "typeorm"; +import { OptionItem } from "./option_item.entity"; + +@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; + + @OneToMany(() => OptionItem, (optionItem) => optionItem.worktype) + option_items: OptionItem[]; +} diff --git a/data_migration_tools/server/src/repositories/delete/errors/types.ts b/data_migration_tools/server/src/repositories/delete/errors/types.ts new file mode 100644 index 0000000..e69de29 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 + ); + } + }); + } +} diff --git a/dictation_server/src/common/validators/authorId.validator.ts b/dictation_server/src/common/validators/authorId.validator.ts index 02ad278..23afafb 100644 --- a/dictation_server/src/common/validators/authorId.validator.ts +++ b/dictation_server/src/common/validators/authorId.validator.ts @@ -5,11 +5,26 @@ import { ValidationOptions, registerDecorator, } from 'class-validator'; +import { + PostUpdateUserRequest, + SignupRequest, +} from '../../features/users/types/types'; +import { USER_ROLES } from '../../constants'; // 大文字英数字とアンダースコアのみを許可するバリデータ @ValidatorConstraint({ name: 'IsAuthorId', async: false }) export class IsAuthorId implements ValidatorConstraintInterface { validate(value: any, args: ValidationArguments) { + const request = args.object as SignupRequest | PostUpdateUserRequest; + // requestの存在チェック + if (!request) { + return false; + } + const { role } = request; + // roleがauthor以外の場合はスキップする + if (role !== USER_ROLES.AUTHOR) { + return true; + } return /^[A-Z0-9_]*$/.test(value); } defaultMessage(args: ValidationArguments) {