Merge branch 'develop' into main
This commit is contained in:
commit
279fecec88
@ -20,4 +20,6 @@ RUN mkdir build \
|
||||
COPY --from=build-container app/dictation_server/dist/ dist/
|
||||
COPY --from=build-container app/dictation_server/.env ./
|
||||
COPY --from=build-container app/dictation_server/node_modules/ node_modules/
|
||||
ARG BUILD_VERSION
|
||||
ENV BUILD_VERSION=${BUILD_VERSION}
|
||||
CMD ["node", "./dist/main.js" ]
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、
|
||||
# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
tags:
|
||||
@ -26,43 +25,40 @@ jobs:
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがmainブランチに存在するか確認'
|
||||
- job: backend_deploy
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureRmWebAppDeployment@4
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
ConnectionType: 'AzureRM'
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
appType: 'webAppContainer'
|
||||
WebAppName: 'app-odms-dictation-prod'
|
||||
ResourceGroupName: 'prod-application-rg'
|
||||
DockerNamespace: 'crodmsregistrymaintenance.azurecr.io'
|
||||
DockerRepository: '$(Build.Repository.Name)/staging/dictation'
|
||||
DockerImageTag: '$(Build.SourceVersion)'
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
appName: 'app-odms-dictation-prod'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'odms-prod-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||
# TODO: stagingパイプライン実装時、staging用のイメージに変更する
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
containerName: staging
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-prod'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-prod
|
||||
SecretsFilter: '*'
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
@ -88,15 +84,42 @@ jobs:
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
- job: migration
|
||||
condition: succeeded('initialize')
|
||||
displayName: DB migration
|
||||
dependsOn:
|
||||
- initialize
|
||||
- backend_deploy
|
||||
- frontend_deploy
|
||||
- job: smoke_test
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: db-migrate-pipelines
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-dictation-prod'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-prod'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-dictation-prod'
|
||||
ResourceGroupName: 'odms-prod-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
- job: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('swap_slot')
|
||||
displayName: DB migration
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
@ -104,7 +127,7 @@ jobs:
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-prod'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
ConnectedServiceName: 'omds-service-connection-prod'
|
||||
KeyVaultName: kv-odms-secret-prod
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成し、
|
||||
# 環境変数 AZURE_SERVICE_CONNECTION の値としてServiceConenction名を設定しておくこと
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
@ -47,7 +46,7 @@ jobs:
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
ConnectedServiceName: 'omds-service-connection-stg'
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
SecretsFilter: '*'
|
||||
- task: Bash@3
|
||||
@ -59,6 +58,7 @@ jobs:
|
||||
npm run test
|
||||
env:
|
||||
JWT_PUBLIC_KEY: $(token-public-key)
|
||||
JWT_PRIVATE_KEY: $(token-private-key)
|
||||
SENDGRID_API_KEY: $(sendgrid-api-key)
|
||||
NOTIFICATION_HUB_NAME: $(notification-hub-name)
|
||||
NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string)
|
||||
@ -74,43 +74,31 @@ jobs:
|
||||
ADB2C_TENANT_ID: $(adb2c-tenant-id)
|
||||
ADB2C_CLIENT_ID: $(adb2c-client-id)
|
||||
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
|
||||
MAIL_FROM: xxxxxx
|
||||
APP_DOMAIN: xxxxxxxxx
|
||||
EMAIL_CONFIRM_LIFETIME : 0
|
||||
TENANT_NAME : xxxxxxxxxxxx
|
||||
SIGNIN_FLOW_NAME : xxxxxxxxxxxx
|
||||
STORAGE_TOKEN_EXPIRE_TIME : 0
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION)
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerDictation.dockerfile
|
||||
imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion)
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: $(AZURE_SERVICE_CONNECTION)
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: $(Build.Repository.Name)/staging/dictation:$(Build.SourceVersion)
|
||||
- job: backend_deploy
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
- job: frontend_build
|
||||
dependsOn: backend_build
|
||||
condition: succeeded('backend_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureRmWebAppDeployment@4
|
||||
inputs:
|
||||
ConnectionType: 'AzureRM'
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
appType: 'webAppContainer'
|
||||
WebAppName: 'app-odms-dictation-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
DockerNamespace: 'crodmsregistrymaintenance.azurecr.io'
|
||||
DockerRepository: '$(Build.Repository.Name)/staging/dictation'
|
||||
DockerImageTag: '$(Build.SourceVersion)'
|
||||
- job: frontend_build
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: Build Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
@ -141,7 +129,7 @@ jobs:
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
@ -151,10 +139,22 @@ jobs:
|
||||
--container-name $(containerName) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: frontend_deploy
|
||||
- job: backend_deploy
|
||||
dependsOn: frontend_build
|
||||
condition: succeeded('frontend_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# TODO: Productionと同様にデプロイを行う
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
@ -165,69 +165,15 @@ jobs:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
SecretsFilter: '*'
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: $(AZURE_SERVICE_CONNECTION)
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob download \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(containerName) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||
- task: AzureStaticWebApp@0
|
||||
displayName: 'Static Web App: '
|
||||
inputs:
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
app_location: '/$(Build.SourceVersion)'
|
||||
config_file_location: /dictation_client
|
||||
skip_app_build: true
|
||||
skip_api_build: true
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
# TODO: Productionと同様にデプロイを行う
|
||||
- job: migration
|
||||
condition: succeeded('initialize')
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
displayName: DB migration
|
||||
dependsOn:
|
||||
- initialize
|
||||
- backend_deploy
|
||||
- frontend_deploy
|
||||
pool:
|
||||
name: db-migrate-pipelines
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: $(AZURE_SERVICE_CONNECTION)
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||
sql-migrate --version
|
||||
cat ./dictation_server/db/dbconfig.yml
|
||||
# migration実行
|
||||
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||
# TODO: Productionと同様にマイグレーションを行う
|
||||
@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Snackbar from "components/snackbar";
|
||||
import { selectSnackber } from "features/ui/selectors";
|
||||
import { closeSnackbar } from "features/ui/uiSlice";
|
||||
import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants";
|
||||
|
||||
const App = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
@ -21,8 +22,12 @@ const App = (): JSX.Element => {
|
||||
useEffect(() => {
|
||||
const id = globalAxios.interceptors.response.use(
|
||||
(response: AxiosResponse) => response,
|
||||
(e: AxiosError) => {
|
||||
if (e?.response?.status === 401) {
|
||||
(e: AxiosError<{ code?: string }>) => {
|
||||
if (
|
||||
e?.response?.status === 401 &&
|
||||
e?.response?.data?.code &&
|
||||
!UNAUTHORIZED_TO_CONTINUE_ERROR_CODES.includes(e.response.data.code)
|
||||
) {
|
||||
dispatch(clearToken());
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/?logout=true",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import TopPage from "pages/TopPage";
|
||||
import AuthPage from "pages/AuthPage";
|
||||
import LoginPage from "pages/LoginPage";
|
||||
import SamplePage from "pages/SamplePage";
|
||||
import { AuthErrorPage } from "pages/ErrorPage";
|
||||
@ -20,18 +21,21 @@ import WorkflowPage from "pages/WorkflowPage";
|
||||
import TypistGroupSettingPage from "pages/TypistGroupSettingPage";
|
||||
import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage";
|
||||
import AccountPage from "pages/AccountPage";
|
||||
import AcceptToUsePage from "pages/TermsPage";
|
||||
import { TemplateFilePage } from "pages/TemplateFilePage";
|
||||
import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
<Routes>
|
||||
<Route path="/" element={<TopPage />} />
|
||||
<Route path="/auth" element={<AuthPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/authError" element={<AuthErrorPage />} />
|
||||
<Route
|
||||
path="/signup"
|
||||
element={<SignupPage completeTo="/signup/complete" />}
|
||||
/>
|
||||
<Route path="/terms" element={<AcceptToUsePage />} />
|
||||
<Route path="/signup/complete" element={<SignupCompletePage />} />
|
||||
<Route path="/mail-confirm/" element={<VerifyPage />} />
|
||||
<Route path="/mail-confirm/user" element={<UserVerifyPage />} />
|
||||
|
||||
@ -127,7 +127,7 @@ export interface AllocatableLicenseInfo {
|
||||
* @type {string}
|
||||
* @memberof AllocatableLicenseInfo
|
||||
*/
|
||||
'expiryDate': string;
|
||||
'expiryDate'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -442,11 +442,17 @@ export interface CreateAccountRequest {
|
||||
*/
|
||||
'adminPassword': string;
|
||||
/**
|
||||
* 同意済み利用規約のバージョン
|
||||
* 同意済み利用規約のバージョン(EULA)
|
||||
* @type {string}
|
||||
* @memberof CreateAccountRequest
|
||||
*/
|
||||
'acceptedTermsVersion': string;
|
||||
'acceptedEulaVersion': string;
|
||||
/**
|
||||
* 同意済み利用規約のバージョン(DPA)
|
||||
* @type {string}
|
||||
* @memberof CreateAccountRequest
|
||||
*/
|
||||
'acceptedDpaVersion': string;
|
||||
/**
|
||||
* reCAPTCHA Token
|
||||
* @type {string}
|
||||
@ -530,7 +536,7 @@ export interface CreateTypistGroupRequest {
|
||||
*/
|
||||
export interface CreateWorkflowsRequest {
|
||||
/**
|
||||
* Authornの内部ID
|
||||
* Authorの内部ID
|
||||
* @type {number}
|
||||
* @memberof CreateWorkflowsRequest
|
||||
*/
|
||||
@ -643,6 +649,32 @@ export interface ErrorResponse {
|
||||
*/
|
||||
'code': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetAccountInfoMinimalAccessRequest
|
||||
*/
|
||||
export interface GetAccountInfoMinimalAccessRequest {
|
||||
/**
|
||||
* idトークン
|
||||
* @type {string}
|
||||
* @memberof GetAccountInfoMinimalAccessRequest
|
||||
*/
|
||||
'idToken': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetAccountInfoMinimalAccessResponse
|
||||
*/
|
||||
export interface GetAccountInfoMinimalAccessResponse {
|
||||
/**
|
||||
* 階層
|
||||
* @type {number}
|
||||
* @memberof GetAccountInfoMinimalAccessResponse
|
||||
*/
|
||||
'tier': number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -994,6 +1026,19 @@ export interface GetTemplatesResponse {
|
||||
*/
|
||||
'templates': Array<TemplateFile>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface GetTermsInfoResponse
|
||||
*/
|
||||
export interface GetTermsInfoResponse {
|
||||
/**
|
||||
*
|
||||
* @type {Array<TermInfo>}
|
||||
* @memberof GetTermsInfoResponse
|
||||
*/
|
||||
'termsInfo': Array<TermInfo>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1784,6 +1829,25 @@ export interface TemplateUploadLocationResponse {
|
||||
*/
|
||||
'url': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface TermInfo
|
||||
*/
|
||||
export interface TermInfo {
|
||||
/**
|
||||
* 利用規約種別
|
||||
* @type {string}
|
||||
* @memberof TermInfo
|
||||
*/
|
||||
'documentType': string;
|
||||
/**
|
||||
* バージョン
|
||||
* @type {string}
|
||||
* @memberof TermInfo
|
||||
*/
|
||||
'version': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1860,6 +1924,31 @@ export interface TypistGroup {
|
||||
*/
|
||||
'name': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface UpdateAcceptedVersionRequest
|
||||
*/
|
||||
export interface UpdateAcceptedVersionRequest {
|
||||
/**
|
||||
* IDトークン
|
||||
* @type {string}
|
||||
* @memberof UpdateAcceptedVersionRequest
|
||||
*/
|
||||
'idToken': string;
|
||||
/**
|
||||
* 更新バージョン(EULA)
|
||||
* @type {string}
|
||||
* @memberof UpdateAcceptedVersionRequest
|
||||
*/
|
||||
'acceptedEULAVersion': string;
|
||||
/**
|
||||
* 更新バージョン(DPA)
|
||||
* @type {string}
|
||||
* @memberof UpdateAcceptedVersionRequest
|
||||
*/
|
||||
'acceptedDPAVersion'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -1923,6 +2012,37 @@ export interface UpdateTypistGroupRequest {
|
||||
*/
|
||||
'typistIds': Array<number>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface UpdateWorkflowRequest
|
||||
*/
|
||||
export interface UpdateWorkflowRequest {
|
||||
/**
|
||||
* Authorの内部ID
|
||||
* @type {number}
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'authorId': number;
|
||||
/**
|
||||
* Worktypeの内部ID
|
||||
* @type {number}
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'worktypeId'?: number;
|
||||
/**
|
||||
* テンプレートの内部ID
|
||||
* @type {number}
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'templateId'?: number;
|
||||
/**
|
||||
* ルーティング候補のタイピストユーザー/タイピストグループ
|
||||
* @type {Array<WorkflowTypist>}
|
||||
* @memberof UpdateWorkflowRequest
|
||||
*/
|
||||
'typists': Array<WorkflowTypist>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -2408,9 +2528,9 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteAccount: async (deleteAccountRequest: DeleteAccountRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
deleteAccountAndData: async (deleteAccountRequest: DeleteAccountRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'deleteAccountRequest' is not null or undefined
|
||||
assertParamExists('deleteAccount', 'deleteAccountRequest', deleteAccountRequest)
|
||||
assertParamExists('deleteAccountAndData', 'deleteAccountRequest', deleteAccountRequest)
|
||||
const localVarPath = `/accounts/delete`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@ -2441,6 +2561,80 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorktype: async (id: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteWorktype', 'id', id)
|
||||
const localVarPath = `/accounts/worktypes/{id}/delete`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAccountInfoMinimalAccess: async (getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'getAccountInfoMinimalAccessRequest' is not null or undefined
|
||||
assertParamExists('getAccountInfoMinimalAccess', 'getAccountInfoMinimalAccessRequest', getAccountInfoMinimalAccessRequest)
|
||||
const localVarPath = `/accounts/minimal-access`;
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(getAccountInfoMinimalAccessRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下のAuthor一覧を取得します
|
||||
* @summary
|
||||
@ -3180,8 +3374,30 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccount(deleteAccountRequest, options);
|
||||
async deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccountAndData(deleteAccountRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteWorktype(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteWorktype(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetAccountInfoMinimalAccessResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@ -3446,8 +3662,28 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteAccount(deleteAccountRequest, options).then((request) => request(axios, basePath));
|
||||
deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteAccountAndData(deleteAccountRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorktype(id: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteWorktype(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: any): AxiosPromise<GetAccountInfoMinimalAccessResponse> {
|
||||
return localVarFp.getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのアカウント配下のAuthor一覧を取得します
|
||||
@ -3707,8 +3943,32 @@ export class AccountsApi extends BaseAPI {
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public deleteAccount(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).deleteAccount(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
public deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).deleteAccountAndData(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public deleteWorktype(id: number, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).deleteWorktype(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {GetAccountInfoMinimalAccessRequest} getAccountInfoMinimalAccessRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest: GetAccountInfoMinimalAccessRequest, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).getAccountInfoMinimalAccess(getAccountInfoMinimalAccessRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5981,6 +6241,105 @@ export class TemplatesApi extends BaseAPI {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TermsApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const TermsApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getTermsInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/terms`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TermsApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const TermsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = TermsApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getTermsInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetTermsInfoResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getTermsInfo(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TermsApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const TermsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = TermsApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getTermsInfo(options?: any): AxiosPromise<GetTermsInfoResponse> {
|
||||
return localVarFp.getTermsInfo(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* TermsApi - object-oriented interface
|
||||
* @export
|
||||
* @class TermsApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class TermsApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TermsApi
|
||||
*/
|
||||
public getTermsInfo(options?: AxiosRequestConfig) {
|
||||
return TermsApiFp(this.configuration).getTermsInfo(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* UsersApi - axios parameter creator
|
||||
* @export
|
||||
@ -6281,6 +6640,42 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 利用規約同意バージョンを更新
|
||||
* @summary
|
||||
* @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateAcceptedVersion: async (updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'updateAcceptedVersionRequest' is not null or undefined
|
||||
assertParamExists('updateAcceptedVersion', 'updateAcceptedVersionRequest', updateAcceptedVersionRequest)
|
||||
const localVarPath = `/users/accepted-version`;
|
||||
// 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;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(updateAcceptedVersionRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのタスクソート条件を更新します
|
||||
* @summary
|
||||
@ -6456,6 +6851,17 @@ export const UsersApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.signup(signupRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* 利用規約同意バージョンを更新
|
||||
* @summary
|
||||
* @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAcceptedVersion(updateAcceptedVersionRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのタスクソート条件を更新します
|
||||
* @summary
|
||||
@ -6565,6 +6971,16 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
|
||||
signup(signupRequest: SignupRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.signup(signupRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 利用規約同意バージョンを更新
|
||||
* @summary
|
||||
* @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.updateAcceptedVersion(updateAcceptedVersionRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* ログインしているユーザーのタスクソート条件を更新します
|
||||
* @summary
|
||||
@ -6688,6 +7104,18 @@ export class UsersApi extends BaseAPI {
|
||||
return UsersApiFp(this.configuration).signup(signupRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 利用規約同意バージョンを更新
|
||||
* @summary
|
||||
* @param {UpdateAcceptedVersionRequest} updateAcceptedVersionRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof UsersApi
|
||||
*/
|
||||
public updateAcceptedVersion(updateAcceptedVersionRequest: UpdateAcceptedVersionRequest, options?: AxiosRequestConfig) {
|
||||
return UsersApiFp(this.configuration).updateAcceptedVersion(updateAcceptedVersionRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* ログインしているユーザーのタスクソート条件を更新します
|
||||
* @summary
|
||||
@ -6761,6 +7189,44 @@ export const WorkflowsApiAxiosParamCreator = function (configuration?: Configura
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを削除します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorkflow: async (workflowId: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'workflowId' is not null or undefined
|
||||
assertParamExists('deleteWorkflow', 'workflowId', workflowId)
|
||||
const localVarPath = `/workflows/{workflowId}/delete`
|
||||
.replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローの一覧を取得します
|
||||
* @summary
|
||||
@ -6790,6 +7256,50 @@ export const WorkflowsApiAxiosParamCreator = function (configuration?: Configura
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを編集します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {UpdateWorkflowRequest} updateWorkflowRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateWorkflow: async (workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'workflowId' is not null or undefined
|
||||
assertParamExists('updateWorkflow', 'workflowId', workflowId)
|
||||
// verify required parameter 'updateWorkflowRequest' is not null or undefined
|
||||
assertParamExists('updateWorkflow', 'updateWorkflowRequest', updateWorkflowRequest)
|
||||
const localVarPath = `/workflows/{workflowId}`
|
||||
.replace(`{${"workflowId"}}`, encodeURIComponent(String(workflowId)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(updateWorkflowRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
@ -6816,6 +7326,17 @@ export const WorkflowsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createWorkflows(createWorkflowsRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを削除します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteWorkflow(workflowId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteWorkflow(workflowId, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローの一覧を取得します
|
||||
* @summary
|
||||
@ -6826,6 +7347,18 @@ export const WorkflowsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getWorkflows(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを編集します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {UpdateWorkflowRequest} updateWorkflowRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateWorkflow(workflowId, updateWorkflowRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@ -6846,6 +7379,16 @@ export const WorkflowsApiFactory = function (configuration?: Configuration, base
|
||||
createWorkflows(createWorkflowsRequest: CreateWorkflowsRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.createWorkflows(createWorkflowsRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを削除します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorkflow(workflowId: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteWorkflow(workflowId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローの一覧を取得します
|
||||
* @summary
|
||||
@ -6855,6 +7398,17 @@ export const WorkflowsApiFactory = function (configuration?: Configuration, base
|
||||
getWorkflows(options?: any): AxiosPromise<GetWorkflowsResponse> {
|
||||
return localVarFp.getWorkflows(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* アカウント内のワークフローを編集します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {UpdateWorkflowRequest} updateWorkflowRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.updateWorkflow(workflowId, updateWorkflowRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -6877,6 +7431,18 @@ export class WorkflowsApi extends BaseAPI {
|
||||
return WorkflowsApiFp(this.configuration).createWorkflows(createWorkflowsRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* アカウント内のワークフローを削除します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof WorkflowsApi
|
||||
*/
|
||||
public deleteWorkflow(workflowId: number, options?: AxiosRequestConfig) {
|
||||
return WorkflowsApiFp(this.configuration).deleteWorkflow(workflowId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* アカウント内のワークフローの一覧を取得します
|
||||
* @summary
|
||||
@ -6887,6 +7453,19 @@ export class WorkflowsApi extends BaseAPI {
|
||||
public getWorkflows(options?: AxiosRequestConfig) {
|
||||
return WorkflowsApiFp(this.configuration).getWorkflows(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* アカウント内のワークフローを編集します
|
||||
* @summary
|
||||
* @param {number} workflowId ワークフローの内部ID
|
||||
* @param {UpdateWorkflowRequest} updateWorkflowRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof WorkflowsApi
|
||||
*/
|
||||
public updateWorkflow(workflowId: number, updateWorkflowRequest: UpdateWorkflowRequest, options?: AxiosRequestConfig) {
|
||||
return WorkflowsApiFp(this.configuration).updateWorkflow(workflowId, updateWorkflowRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import worktype from "features/workflow/worktype/worktypeSlice";
|
||||
import account from "features/account/accountSlice";
|
||||
import template from "features/workflow/template/templateSlice";
|
||||
import workflow from "features/workflow/workflowSlice";
|
||||
import terms from "features/terms/termsSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -40,6 +41,7 @@ export const store = configureStore({
|
||||
account,
|
||||
template,
|
||||
workflow,
|
||||
terms,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ export const errorCodes = [
|
||||
"E010206", // DBのTierが想定外の値エラー
|
||||
"E010207", // ユーザーのRole変更不可エラー
|
||||
"E010208", // ユーザーの暗号化パスワード不足エラー
|
||||
"E010209", // ユーザーの同意済み利用規約バージョンが最新でないエラー
|
||||
"E010301", // メールアドレス登録済みエラー
|
||||
"E010302", // authorId重複エラー
|
||||
"E010401", // PONumber重複エラー
|
||||
@ -55,5 +56,7 @@ export const errorCodes = [
|
||||
"E011001", // ワークタイプ重複エラー
|
||||
"E011002", // ワークタイプ登録上限超過エラー
|
||||
"E011003", // ワークタイプ不在エラー
|
||||
"E011004", // ワークタイプ使用中エラー
|
||||
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
"E013002", // ワークフロー不在エラー
|
||||
] as const;
|
||||
|
||||
@ -81,3 +81,21 @@ const isErrorResponse = (error: unknown): error is ErrorResponse => {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ export const msalConfig: Configuration = {
|
||||
clientId: import.meta.env.VITE_B2C_CLIENTID,
|
||||
authority: import.meta.env.VITE_B2C_AUTHORITY,
|
||||
knownAuthorities: [import.meta.env.VITE_B2C_KNOWNAUTHORITIES],
|
||||
redirectUri: `${globalThis.location.origin}/login`,
|
||||
redirectUri: `${globalThis.location.origin}/auth`,
|
||||
navigateToLoginRequestUrl: false,
|
||||
},
|
||||
cache: {
|
||||
|
||||
@ -62,3 +62,16 @@ export const isIdToken = (arg: any): arg is IdToken => {
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getIdTokenFromLocalStorage = (
|
||||
localStorageKeyforIdToken: string
|
||||
): string | null => {
|
||||
const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
return idTokenObject.secret;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -28,3 +28,9 @@ export const TIERS = {
|
||||
TIER4: "4",
|
||||
TIER5: "5",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 401エラー時にログアウトさせずに処理を継続するエラーコード
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = ["E010209"];
|
||||
|
||||
@ -130,7 +130,7 @@ export const deleteAccountAsync = createAsyncThunk<
|
||||
const accountApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
await accountApi.deleteAccount(deleteAccounRequest, {
|
||||
await accountApi.deleteAccountAndData(deleteAccounRequest, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
|
||||
@ -1,17 +1,26 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { LoginState } from "./state";
|
||||
import { loginAsync } from "./operations";
|
||||
|
||||
const initialState: LoginState = {
|
||||
apps: {
|
||||
LoginApiCallStatus: "none",
|
||||
localStorageKeyforIdToken: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const loginSlice = createSlice({
|
||||
name: "login",
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
changeLocalStorageKeyforIdToken: (
|
||||
state,
|
||||
action: PayloadAction<{ localStorageKeyforIdToken: string }>
|
||||
) => {
|
||||
const { localStorageKeyforIdToken } = action.payload;
|
||||
state.apps.localStorageKeyforIdToken = localStorageKeyforIdToken;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loginAsync.pending, (state) => {
|
||||
state.apps.LoginApiCallStatus = "pending";
|
||||
@ -25,4 +34,5 @@ export const loginSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeLocalStorageKeyforIdToken } = loginSlice.actions;
|
||||
export default loginSlice.reducer;
|
||||
|
||||
@ -3,6 +3,7 @@ import type { RootState } from "app/store";
|
||||
import { setToken } from "features/auth/authSlice";
|
||||
import { AuthApi } from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||
|
||||
export const loginAsync = createAsyncThunk<
|
||||
{
|
||||
@ -14,7 +15,7 @@ export const loginAsync = createAsyncThunk<
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
/* Empty Object */
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("login/loginAsync", async (args, thunkApi) => {
|
||||
@ -41,6 +42,8 @@ export const loginAsync = createAsyncThunk<
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue({});
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,3 +4,7 @@ export const selectLoginApiCallStatus = (
|
||||
state: RootState
|
||||
): "fulfilled" | "rejected" | "none" | "pending" =>
|
||||
state.login.apps.LoginApiCallStatus;
|
||||
|
||||
export const selectLocalStorageKeyforIdToken = (
|
||||
state: RootState
|
||||
): string | null => state.login.apps.localStorageKeyforIdToken;
|
||||
|
||||
@ -4,4 +4,5 @@ export interface LoginState {
|
||||
|
||||
export interface Apps {
|
||||
LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending";
|
||||
localStorageKeyforIdToken: string | null;
|
||||
}
|
||||
|
||||
@ -3,10 +3,12 @@ import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { getTranslationID } from "translation";
|
||||
import { closeSnackbar, openSnackbar } from "features/ui/uiSlice";
|
||||
import { TERMS_DOCUMENT_TYPE } from "features/terms/constants";
|
||||
import {
|
||||
AccountsApi,
|
||||
CreateAccountRequest,
|
||||
GetDealersResponse,
|
||||
TermsApi,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
@ -93,3 +95,42 @@ export const getDealersAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getLatestEulaVersionAsync = createAsyncThunk<
|
||||
string,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("login/getLatestEulaVersionAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const termsApi = new TermsApi(config);
|
||||
|
||||
try {
|
||||
const termsInfo = await termsApi.getTermsInfo();
|
||||
const latestEulaVersion = termsInfo.data.termsInfo.find(
|
||||
(val) => val.documentType === TERMS_DOCUMENT_TYPE.EULA
|
||||
);
|
||||
if (!latestEulaVersion) {
|
||||
throw new Error("EULA info is not found");
|
||||
}
|
||||
return latestEulaVersion.version;
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,3 +72,6 @@ export const selectSelectedDealer = (state: RootState) => {
|
||||
const { dealer } = state.signup.apps;
|
||||
return dealers.find((x: Dealer) => x.id === dealer);
|
||||
};
|
||||
|
||||
export const selectEulaVersion = (state: RootState) =>
|
||||
state.signup.domain.eulaVersion;
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { SignupState } from "./state";
|
||||
import { getDealersAsync, signupAsync } from "./operations";
|
||||
import {
|
||||
getDealersAsync,
|
||||
getLatestEulaVersionAsync,
|
||||
signupAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: SignupState = {
|
||||
apps: {
|
||||
@ -15,6 +19,7 @@ const initialState: SignupState = {
|
||||
},
|
||||
domain: {
|
||||
dealers: [],
|
||||
eulaVersion: "",
|
||||
},
|
||||
};
|
||||
|
||||
@ -74,6 +79,15 @@ export const signupSlice = createSlice({
|
||||
builder.addCase(getDealersAsync.rejected, () => {
|
||||
//
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.pending, () => {
|
||||
//
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.fulfilled, (state, action) => {
|
||||
state.domain.eulaVersion = action.payload;
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.rejected, () => {
|
||||
//
|
||||
});
|
||||
},
|
||||
});
|
||||
export const {
|
||||
|
||||
@ -18,4 +18,5 @@ export interface Apps {
|
||||
|
||||
export interface Domain {
|
||||
dealers: Dealer[];
|
||||
eulaVersion: string;
|
||||
}
|
||||
|
||||
8
dictation_client/src/features/terms/constants.ts
Normal file
8
dictation_client/src/features/terms/constants.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 利用規約の種類
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERMS_DOCUMENT_TYPE = {
|
||||
DPA: "DPA",
|
||||
EULA: "EULA",
|
||||
} as const;
|
||||
4
dictation_client/src/features/terms/index.ts
Normal file
4
dictation_client/src/features/terms/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./termsSlice";
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
158
dictation_client/src/features/terms/operations.ts
Normal file
158
dictation_client/src/features/terms/operations.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { getTranslationID } from "translation";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getIdTokenFromLocalStorage } from "common/token";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import {
|
||||
UsersApi,
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
AccountsApi,
|
||||
TermsApi,
|
||||
GetTermsInfoResponse,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
export const getAccountInfoMinimalAccessAsync = createAsyncThunk<
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
{
|
||||
localStorageKeyforIdToken: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/getAccountInfoMinimalAccessAsync", async (args, thunkApi) => {
|
||||
const { localStorageKeyforIdToken } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
// IDトークンの取得
|
||||
const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken);
|
||||
|
||||
// IDトークンが取得できない場合エラーとする
|
||||
if (!idToken) {
|
||||
throw new Error("Unable to retrieve the ID token.");
|
||||
}
|
||||
const res = await accountApi.getAccountInfoMinimalAccess(
|
||||
{ idToken },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getTermsInfoAsync = createAsyncThunk<
|
||||
GetTermsInfoResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/getTermsInfoAsync", async (_args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const termsApi = new TermsApi(config);
|
||||
|
||||
try {
|
||||
const termsInfo = await termsApi.getTermsInfo({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
return termsInfo.data;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateAcceptedVersionAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
tier: number;
|
||||
localStorageKeyforIdToken: string;
|
||||
updateAccceptVersions: {
|
||||
acceptedVerDPA: string;
|
||||
acceptedVerEULA: string;
|
||||
};
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/UpdateAcceptedVersionAsync", async (args, thunkApi) => {
|
||||
const { tier, localStorageKeyforIdToken, updateAccceptVersions } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const userApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
// IDトークンの取得
|
||||
const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken);
|
||||
|
||||
// IDトークンが取得できない場合エラーとする
|
||||
if (!idToken) {
|
||||
throw new Error("Unable to retrieve the ID token.");
|
||||
}
|
||||
await userApi.updateAcceptedVersion(
|
||||
{
|
||||
idToken,
|
||||
acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA,
|
||||
acceptedDPAVersion: !(TIERS.TIER5 === tier.toString())
|
||||
? updateAccceptVersions.acceptedVerDPA
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
20
dictation_client/src/features/terms/selectors.ts
Normal file
20
dictation_client/src/features/terms/selectors.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { RootState } from "app/store";
|
||||
import { TERMS_DOCUMENT_TYPE } from "features/terms/constants";
|
||||
|
||||
export const selectTermVersions = (state: RootState) => {
|
||||
const { termsInfo } = state.terms.domain;
|
||||
|
||||
const acceptedVerDPA =
|
||||
termsInfo.find(
|
||||
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.DPA
|
||||
)?.version || "";
|
||||
|
||||
const acceptedVerEULA =
|
||||
termsInfo.find(
|
||||
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA
|
||||
)?.version || "";
|
||||
|
||||
return { acceptedVerDPA, acceptedVerEULA };
|
||||
};
|
||||
|
||||
export const selectTier = (state: RootState) => state.terms.domain.tier;
|
||||
15
dictation_client/src/features/terms/state.ts
Normal file
15
dictation_client/src/features/terms/state.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TermInfo } from "../../api/api";
|
||||
|
||||
export interface AcceptState {
|
||||
domain: Domain;
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
tier: number;
|
||||
termsInfo: TermInfo[];
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
64
dictation_client/src/features/terms/termsSlice.ts
Normal file
64
dictation_client/src/features/terms/termsSlice.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { AcceptState } from "./state";
|
||||
import {
|
||||
getAccountInfoMinimalAccessAsync,
|
||||
getTermsInfoAsync,
|
||||
updateAcceptedVersionAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: AcceptState = {
|
||||
domain: {
|
||||
tier: 0,
|
||||
termsInfo: [
|
||||
{
|
||||
documentType: "",
|
||||
version: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
apps: {
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const termsSlice = createSlice({
|
||||
name: "terms",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getAccountInfoMinimalAccessAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(
|
||||
getAccountInfoMinimalAccessAsync.fulfilled,
|
||||
(state, actions) => {
|
||||
state.apps.isLoading = false;
|
||||
state.domain.tier = actions.payload.tier;
|
||||
}
|
||||
);
|
||||
builder.addCase(getAccountInfoMinimalAccessAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.fulfilled, (state, actions) => {
|
||||
state.apps.isLoading = false;
|
||||
state.domain.termsInfo = actions.payload.termsInfo;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default termsSlice.reducer;
|
||||
@ -87,7 +87,7 @@ export const userSlice = createSlice({
|
||||
action: PayloadAction<{ authorId: string | undefined }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
state.apps.addUser.authorId = authorId;
|
||||
state.apps.addUser.authorId = authorId?.toUpperCase();
|
||||
},
|
||||
changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => {
|
||||
const { autoRenew } = action.payload;
|
||||
@ -144,7 +144,7 @@ export const userSlice = createSlice({
|
||||
state.apps.updateUser.name = user.name;
|
||||
state.apps.updateUser.email = user.email;
|
||||
state.apps.updateUser.role = user.role as RoleType;
|
||||
state.apps.updateUser.authorId = user.authorId;
|
||||
state.apps.updateUser.authorId = user.authorId?.toUpperCase();
|
||||
state.apps.updateUser.encryption = user.encryption;
|
||||
state.apps.updateUser.encryptionPassword = undefined;
|
||||
state.apps.updateUser.prompt = user.prompt;
|
||||
@ -156,7 +156,7 @@ export const userSlice = createSlice({
|
||||
state.apps.selectedUser.name = user.name;
|
||||
state.apps.selectedUser.email = user.email;
|
||||
state.apps.selectedUser.role = user.role as RoleType;
|
||||
state.apps.selectedUser.authorId = user.authorId;
|
||||
state.apps.selectedUser.authorId = user.authorId?.toUpperCase();
|
||||
state.apps.selectedUser.encryption = user.encryption;
|
||||
state.apps.selectedUser.encryptionPassword = undefined;
|
||||
state.apps.selectedUser.prompt = user.prompt;
|
||||
@ -175,7 +175,7 @@ export const userSlice = createSlice({
|
||||
action: PayloadAction<{ authorId: string }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
state.apps.updateUser.authorId = authorId;
|
||||
state.apps.updateUser.authorId = authorId.toUpperCase();
|
||||
},
|
||||
changeUpdateEncryption: (
|
||||
state,
|
||||
@ -243,7 +243,8 @@ export const userSlice = createSlice({
|
||||
state.apps.licenseAllocateUser.id = selectedUser.id;
|
||||
state.apps.licenseAllocateUser.name = selectedUser.name;
|
||||
state.apps.licenseAllocateUser.email = selectedUser.email;
|
||||
state.apps.licenseAllocateUser.authorId = selectedUser.authorId;
|
||||
state.apps.licenseAllocateUser.authorId =
|
||||
selectedUser.authorId.toUpperCase();
|
||||
state.apps.licenseAllocateUser.licenseStatus = selectedUser.licenseStatus;
|
||||
state.apps.licenseAllocateUser.expiration = selectedUser.expiration;
|
||||
state.apps.licenseAllocateUser.remaining = selectedUser.remaining;
|
||||
|
||||
@ -16,7 +16,6 @@ import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getTranslationID } from "translation";
|
||||
import { WorkflowRelations } from "./state";
|
||||
|
||||
export const listWorkflowAsync = createAsyncThunk<
|
||||
GetWorkflowsResponse,
|
||||
@ -141,6 +140,105 @@ export const createWorkflowAsync = createAsyncThunk<
|
||||
}
|
||||
});
|
||||
|
||||
export const updateWorkflowAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/updateWorkflowAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const workflowsApi = new WorkflowsApi(config);
|
||||
const {
|
||||
selectedWorkflow,
|
||||
selectedAssignees,
|
||||
authorId,
|
||||
templateId,
|
||||
worktypeId,
|
||||
} = state.workflow.apps;
|
||||
|
||||
try {
|
||||
if (selectedWorkflow === undefined) {
|
||||
throw new Error("selectedWorkflow is not found");
|
||||
}
|
||||
|
||||
if (authorId === undefined) {
|
||||
throw new Error("authorId is not found");
|
||||
}
|
||||
// 選択されたタイピストを取得し、リクエスト用の型に変換する
|
||||
const typists = selectedAssignees.map(
|
||||
(item): WorkflowTypist => ({
|
||||
typistId: item.typistUserId,
|
||||
typistGroupId: item.typistGroupId,
|
||||
})
|
||||
);
|
||||
await workflowsApi.updateWorkflow(
|
||||
selectedWorkflow.id,
|
||||
{
|
||||
authorId,
|
||||
typists,
|
||||
templateId,
|
||||
worktypeId,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
const { code, statusCode } = error;
|
||||
|
||||
if (statusCode === 400) {
|
||||
// AuthorIDとWorktypeIDが一致するものが既に存在する場合
|
||||
if (code === "E013001") {
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID(
|
||||
"workflowPage.message.workflowConflictError"
|
||||
),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
|
||||
// パラメータが存在しない場合
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("workflowPage.message.saveFailedError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
// その他のエラー
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getworkflowRelationsAsync = createAsyncThunk<
|
||||
{
|
||||
authors: Author[];
|
||||
@ -211,3 +309,60 @@ export const getworkflowRelationsAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const deleteWorkflowAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{ workflowId: number },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/deleteWorkflowAsync", async (args, thunkApi) => {
|
||||
const { workflowId } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const workflowsApi = new WorkflowsApi(config);
|
||||
|
||||
try {
|
||||
await workflowsApi.deleteWorkflow(workflowId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
// ワークフローが削除済みの場合は成功扱いとする
|
||||
if (error.code === "E013002") {
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -36,6 +36,15 @@ export const selectWorkflowAssinee = (state: RootState) => {
|
||||
),
|
||||
};
|
||||
};
|
||||
export const selectSelectedWorkflow = (state: RootState) =>
|
||||
state.workflow.apps.selectedWorkflow;
|
||||
export const selectAuthorId = (state: RootState) =>
|
||||
state.workflow.apps.authorId;
|
||||
export const selectWorktypeId = (state: RootState) =>
|
||||
state.workflow.apps.worktypeId;
|
||||
export const selectTemplateId = (state: RootState) =>
|
||||
state.workflow.apps.templateId;
|
||||
|
||||
export const selectIsAddLoading = (state: RootState) =>
|
||||
state.workflow.apps.isAddLoading;
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ export interface Apps {
|
||||
authorId?: number;
|
||||
worktypeId?: number;
|
||||
templateId?: number;
|
||||
selectedWorkflow?: Workflow;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
|
||||
@ -3,7 +3,9 @@ import { Assignee } from "api";
|
||||
import {
|
||||
createWorkflowAsync,
|
||||
getworkflowRelationsAsync,
|
||||
deleteWorkflowAsync,
|
||||
listWorkflowAsync,
|
||||
updateWorkflowAsync,
|
||||
} from "./operations";
|
||||
import { WorkflowState } from "./state";
|
||||
|
||||
@ -21,12 +23,17 @@ export const workflowSlice = createSlice({
|
||||
initialState,
|
||||
reducers: {
|
||||
clearWorkflow: (state) => {
|
||||
state.apps.selectedWorkflow = undefined;
|
||||
state.apps.selectedAssignees = [];
|
||||
state.apps.authorId = undefined;
|
||||
state.apps.worktypeId = undefined;
|
||||
state.apps.templateId = undefined;
|
||||
state.domain.workflowRelations = undefined;
|
||||
},
|
||||
setAssignees: (state, action: PayloadAction<{ assignees: Assignee[] }>) => {
|
||||
const { assignees } = action.payload;
|
||||
state.apps.selectedAssignees = assignees;
|
||||
},
|
||||
addAssignee: (state, action: PayloadAction<{ assignee: Assignee }>) => {
|
||||
const { assignee } = action.payload;
|
||||
const { selectedAssignees } = state.apps;
|
||||
@ -55,18 +62,35 @@ export const workflowSlice = createSlice({
|
||||
x.typistGroupId !== assignee.typistGroupId
|
||||
);
|
||||
},
|
||||
changeAuthor: (state, action: PayloadAction<{ authorId: number }>) => {
|
||||
changeAuthor: (
|
||||
state,
|
||||
action: PayloadAction<{ authorId?: number | undefined }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
state.apps.authorId = authorId;
|
||||
},
|
||||
changeWorktype: (state, action: PayloadAction<{ worktypeId?: number }>) => {
|
||||
changeWorktype: (
|
||||
state,
|
||||
action: PayloadAction<{ worktypeId?: number | undefined }>
|
||||
) => {
|
||||
const { worktypeId } = action.payload;
|
||||
state.apps.worktypeId = worktypeId;
|
||||
},
|
||||
changeTemplate: (state, action: PayloadAction<{ templateId?: number }>) => {
|
||||
changeTemplate: (
|
||||
state,
|
||||
action: PayloadAction<{ templateId?: number | undefined }>
|
||||
) => {
|
||||
const { templateId } = action.payload;
|
||||
state.apps.templateId = templateId;
|
||||
},
|
||||
changeSelectedWorkflow: (
|
||||
state,
|
||||
action: PayloadAction<{ workflowId: number }>
|
||||
) => {
|
||||
const { workflowId } = action.payload;
|
||||
const workflow = state.domain.workflows?.find((x) => x.id === workflowId);
|
||||
state.apps.selectedWorkflow = workflow;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listWorkflowAsync.pending, (state) => {
|
||||
@ -127,15 +151,35 @@ export const workflowSlice = createSlice({
|
||||
builder.addCase(createWorkflowAsync.rejected, (state) => {
|
||||
state.apps.isAddLoading = false;
|
||||
});
|
||||
builder.addCase(updateWorkflowAsync.pending, (state) => {
|
||||
state.apps.isAddLoading = true;
|
||||
});
|
||||
builder.addCase(updateWorkflowAsync.fulfilled, (state) => {
|
||||
state.apps.isAddLoading = false;
|
||||
});
|
||||
builder.addCase(updateWorkflowAsync.rejected, (state) => {
|
||||
state.apps.isAddLoading = false;
|
||||
});
|
||||
builder.addCase(deleteWorkflowAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(deleteWorkflowAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(deleteWorkflowAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setAssignees,
|
||||
addAssignee,
|
||||
removeAssignee,
|
||||
changeAuthor,
|
||||
changeWorktype,
|
||||
changeTemplate,
|
||||
changeSelectedWorkflow,
|
||||
clearWorkflow,
|
||||
} = workflowSlice.actions;
|
||||
|
||||
|
||||
@ -342,3 +342,75 @@ export const updateActiveWorktypeAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const deleteWorktypeAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{ worktypeId: number },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/deleteWorktypeAsync", async (args, thunkApi) => {
|
||||
const { worktypeId } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountsApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
await accountsApi.deleteWorktype(worktypeId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
if (error.statusCode === 400) {
|
||||
if (error.code === "E011003") {
|
||||
// ワークタイプが削除済みの場合は成功扱いとする
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (error.code === "E011004") {
|
||||
// ワークタイプがワークフローで使用中の場合は削除できない
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID(
|
||||
"worktypeIdSetting.message.worktypeInUseError"
|
||||
),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -149,7 +149,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
{isTier5 && !viewInfo.account.parentAccountName && (
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeDealer({
|
||||
@ -238,8 +239,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
</dt>
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changePrimaryAdministrator({
|
||||
@ -303,8 +304,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
</dt>
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeSecondryAdministrator({
|
||||
|
||||
78
dictation_client/src/pages/AuthPage/index.tsx
Normal file
78
dictation_client/src/pages/AuthPage/index.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AuthError } from "@azure/msal-browser";
|
||||
import { AppDispatch } from "app/store";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import {
|
||||
selectLoginApiCallStatus,
|
||||
changeLocalStorageKeyforIdToken,
|
||||
} from "features/login";
|
||||
import React, { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const AuthPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const status = useSelector(selectLoginApiCallStatus);
|
||||
|
||||
// TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。
|
||||
useEffect(() => {
|
||||
if (status !== "none") {
|
||||
// ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const loginResult = await instance.handleRedirectPromise();
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log({ loginResult }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
if (loginResult && loginResult.account) {
|
||||
const { homeAccountId, idTokenClaims } = loginResult.account;
|
||||
if (idTokenClaims && idTokenClaims.aud) {
|
||||
const localStorageKeyforIdToken = `${homeAccountId}-${
|
||||
import.meta.env.VITE_B2C_KNOWNAUTHORITIES
|
||||
}-idtoken-${idTokenClaims.aud}----`;
|
||||
|
||||
// AADB2Cログイン画面以外から本画面に遷移した場合用にIDトークン取得用キーをstateに保存
|
||||
dispatch(
|
||||
changeLocalStorageKeyforIdToken({
|
||||
localStorageKeyforIdToken,
|
||||
})
|
||||
);
|
||||
|
||||
// トークン取得と設定を行う
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.log({ e }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
// AAD B2Cの多要素認証画面やパスワードリセット画面で「cancel」をクリックすると、handleRedirectPromise()にてエラーが発生するため、
|
||||
// それをハンドリングして適切な画面遷移処理を行う。
|
||||
if (e instanceof AuthError) {
|
||||
// エラーコードはerrorMessageの中の一部として埋め込まれており完全一致で取得するのは筋が悪いため、部分一致で取得する。
|
||||
// TODO 他にもAADB2Cのエラーコードを使用する箇所が出てきた場合、定数化すること
|
||||
if (e.errorMessage.startsWith("AADB2C90091")) {
|
||||
navigate("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [instance, navigate, status, dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<h3>loading ...</h3>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthPage;
|
||||
@ -1,30 +1,39 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AuthError } from "@azure/msal-browser";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { isIdToken } from "common/token";
|
||||
import { loadAccessToken, loadRefreshToken } from "features/auth/utils";
|
||||
import { loginAsync, selectLocalStorageKeyforIdToken } from "features/login";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import { loadAccessToken, loadRefreshToken } from "features/auth/utils";
|
||||
import { loginAsync, selectLoginApiCallStatus } from "features/login";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isErrorObject } from "common/errors";
|
||||
|
||||
const LoginPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [, i18n] = useTranslation();
|
||||
const status = useSelector(selectLoginApiCallStatus);
|
||||
const localStorageKeyforIdToken = useSelector(
|
||||
selectLocalStorageKeyforIdToken
|
||||
);
|
||||
|
||||
const login = useCallback(
|
||||
const tokenSet = useCallback(
|
||||
async (idToken: string) => {
|
||||
// ログイン処理呼び出し
|
||||
const { meta } = await dispatch(loginAsync({ idToken }));
|
||||
const { meta, payload } = await dispatch(loginAsync({ idToken }));
|
||||
|
||||
// ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する
|
||||
if (meta.requestStatus === "rejected") {
|
||||
if (isErrorObject(payload)) {
|
||||
// 未同意の規約がある場合は利用規約同意画面に遷移する
|
||||
if (payload.error.code === "E010209") {
|
||||
navigate("/terms");
|
||||
return;
|
||||
}
|
||||
}
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/AuthError",
|
||||
});
|
||||
@ -48,53 +57,26 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
[dispatch, i18n.language, instance, navigate]
|
||||
);
|
||||
|
||||
// TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。
|
||||
useEffect(() => {
|
||||
if (status !== "none") {
|
||||
// ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない
|
||||
// AADB2Cのログイン画面とLoginPageを経由していない場合はトップページに遷移する
|
||||
if (!localStorageKeyforIdToken) {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const loginResult = await instance.handleRedirectPromise();
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log({ loginResult }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
if (loginResult && loginResult.account) {
|
||||
const { homeAccountId, idTokenClaims } = loginResult.account;
|
||||
if (idTokenClaims && idTokenClaims.aud) {
|
||||
// IDトークンの取得
|
||||
const idTokenString = localStorage.getItem(
|
||||
`${homeAccountId}-${
|
||||
import.meta.env.VITE_B2C_KNOWNAUTHORITIES
|
||||
}-idtoken-${idTokenClaims.aud}----`
|
||||
);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
await login(idTokenObject.secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.log({ e }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
// AAD B2Cの多要素認証画面やパスワードリセット画面で「cancel」をクリックすると、handleRedirectPromise()にてエラーが発生するため、
|
||||
// それをハンドリングして適切な画面遷移処理を行う。
|
||||
if (e instanceof AuthError) {
|
||||
// エラーコードはerrorMessageの中の一部として埋め込まれており完全一致で取得するのは筋が悪いため、部分一致で取得する。
|
||||
// TODO 他にもAADB2Cのエラーコードを使用する箇所が出てきた場合、定数化すること
|
||||
if (e.errorMessage.startsWith("AADB2C90091")) {
|
||||
navigate("/");
|
||||
}
|
||||
// IDトークンの取得
|
||||
const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
await tokenSet(idTokenObject.secret);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [instance, login, navigate, status]);
|
||||
// 画面描画後のみ実行するため引数を設定しない
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
selectEmail,
|
||||
selectPassword,
|
||||
selectSelectedDealer,
|
||||
selectEulaVersion,
|
||||
} from "../../features/signup/selectors";
|
||||
import { signupAsync } from "../../features/signup/operations";
|
||||
|
||||
@ -27,6 +28,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
const adminMail = useSelector(selectEmail);
|
||||
const adminPassword = useSelector(selectPassword);
|
||||
const dealer = useSelector(selectSelectedDealer);
|
||||
const acceptedEulaVersion = useSelector(selectEulaVersion);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(
|
||||
@ -37,7 +39,8 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
adminName,
|
||||
adminMail,
|
||||
adminPassword,
|
||||
acceptedTermsVersion: "",
|
||||
acceptedEulaVersion,
|
||||
acceptedDpaVersion: "",
|
||||
token: "",
|
||||
})
|
||||
);
|
||||
@ -49,6 +52,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
adminName,
|
||||
adminMail,
|
||||
adminPassword,
|
||||
acceptedEulaVersion,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -24,7 +24,10 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { getDealersAsync } from "features/signup/operations";
|
||||
import {
|
||||
getDealersAsync,
|
||||
getLatestEulaVersionAsync,
|
||||
} from "features/signup/operations";
|
||||
import { LANGUAGE_LIST } from "features/top/constants";
|
||||
import { openSnackbar } from "features/ui";
|
||||
import { COUNTRY_LIST } from "./constants";
|
||||
@ -84,6 +87,7 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
// 入力画面の初期化時の処理
|
||||
useEffect(() => {
|
||||
dispatch(getDealersAsync());
|
||||
dispatch(getLatestEulaVersionAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -264,10 +268,9 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
/>
|
||||
{isPushCreateButton && hasErrorEmptyAdminName && (
|
||||
<span className={styles.formError}>
|
||||
{" "}
|
||||
{t(
|
||||
{` ${t(
|
||||
getTranslationID("signupPage.message.inputEmptyError")
|
||||
)}
|
||||
)}`}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
@ -369,8 +372,9 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("signupPage.label.termsLink"))}
|
||||
</a>{" "}
|
||||
{t(getTranslationID("signupPage.label.termsLinkFor"))} <br />
|
||||
</a>
|
||||
{` ${t(getTranslationID("signupPage.label.termsLinkFor"))} `}
|
||||
<br />
|
||||
<label htmlFor="check-box">
|
||||
<input
|
||||
id="check-box"
|
||||
|
||||
188
dictation_client/src/pages/TermsPage/index.tsx
Normal file
188
dictation_client/src/pages/TermsPage/index.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
/* eslint-disable jsx-a11y/label-has-associated-control */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import Header from "components/header";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import Footer from "components/footer";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
getAccountInfoMinimalAccessAsync,
|
||||
getTermsInfoAsync,
|
||||
updateAcceptedVersionAsync,
|
||||
selectTier,
|
||||
selectTermVersions,
|
||||
} from "features//terms";
|
||||
import { selectLocalStorageKeyforIdToken } from "features/login";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const TermsPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const updateAccceptVersions = useSelector(selectTermVersions);
|
||||
const localStorageKeyforIdToken = useSelector(
|
||||
selectLocalStorageKeyforIdToken
|
||||
);
|
||||
const tier = useSelector(selectTier);
|
||||
|
||||
const [isCheckedEula, setIsCheckedEula] = useState(false);
|
||||
const [isCheckedDpa, setIsCheckedDpa] = useState(false);
|
||||
|
||||
const [isClickedEulaLink, setIsClickedEulaLink] = useState(false);
|
||||
const [isClickedDpaLink, setIsClickedDpaLink] = useState(false);
|
||||
|
||||
// 画面起動時
|
||||
useEffect(() => {
|
||||
dispatch(getTermsInfoAsync());
|
||||
if (localStorageKeyforIdToken) {
|
||||
dispatch(getAccountInfoMinimalAccessAsync({ localStorageKeyforIdToken }));
|
||||
} else {
|
||||
// ログイン画面を経由していないため、トップページに遷移する
|
||||
navigate("/");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// ユーザーが第5階層であるかどうかを判定する(アクセストークンから自分の階層を取得できないので自前で作成)
|
||||
const isTier5 = () => TIERS.TIER5.includes(tier.toString());
|
||||
|
||||
// ボタン押下可否判定ロジック
|
||||
const canClickButton = () => {
|
||||
if (isTier5()) {
|
||||
return isCheckedEula;
|
||||
}
|
||||
return isCheckedEula && isCheckedDpa;
|
||||
};
|
||||
|
||||
// ボタン押下時処理
|
||||
const onAcceptTermsOfUse = useCallback(async () => {
|
||||
if (
|
||||
localStorageKeyforIdToken &&
|
||||
updateAccceptVersions.acceptedVerDPA !== "" &&
|
||||
updateAccceptVersions.acceptedVerEULA !== ""
|
||||
) {
|
||||
const { meta } = await dispatch(
|
||||
updateAcceptedVersionAsync({
|
||||
tier,
|
||||
localStorageKeyforIdToken,
|
||||
updateAccceptVersions,
|
||||
})
|
||||
);
|
||||
|
||||
// 同意済バージョンが更新できたら、再度トークン生成を行う
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
}, [
|
||||
navigate,
|
||||
localStorageKeyforIdToken,
|
||||
updateAccceptVersions,
|
||||
tier,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.mainSmall}>
|
||||
<div>
|
||||
<h1 className={`${styles.marginBtm1} ${styles.alignCenter}`}>
|
||||
{t(getTranslationID("termsPage.label.title"))}
|
||||
</h1>
|
||||
</div>
|
||||
<section className={styles.form}>
|
||||
<form action="" name="" method="">
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
href="/" /* TODO Eula用の利用規約リンクが決定したら設定を行う */
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedEulaLink(true)}
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfEula"))}
|
||||
</a>
|
||||
{` ${t(getTranslationID("termsPage.label.forOdds"))}`}
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isCheckedEula}
|
||||
className={styles.formCheck}
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedEula(e.target.checked)}
|
||||
disabled={!isClickedEulaLink}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
)}
|
||||
</label>
|
||||
</p>
|
||||
</dd>
|
||||
{/* 第五階層以外の場合はEulaのリンクをあわせて表示する */}
|
||||
{!isTier5() && (
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
href="/" /* TODO Dpa用の利用規約リンクが決定したら設定を行う */
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedDpaLink(true)}
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfDpa"))}
|
||||
</a>
|
||||
{` ${t(getTranslationID("termsPage.label.forOdds"))}`}
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isCheckedDpa}
|
||||
className={styles.formCheck}
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedDpa(e.target.checked)}
|
||||
disabled={!isClickedDpaLink}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
)}
|
||||
</label>
|
||||
</p>
|
||||
</dd>
|
||||
)}
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("termsPage.label.button"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||
canClickButton() ? styles.isActive : ""
|
||||
}`}
|
||||
onClick={onAcceptTermsOfUse}
|
||||
/>
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsPage;
|
||||
@ -200,7 +200,11 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
className={styles.formInput}
|
||||
value={addUser.authorId ?? undefined}
|
||||
onChange={(e) => {
|
||||
dispatch(changeAuthorId({ authorId: e.target.value }));
|
||||
dispatch(
|
||||
changeAuthorId({
|
||||
authorId: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isPushCreateButton && hasErrorEmptyAuthorId && (
|
||||
|
||||
@ -184,7 +184,9 @@ export const UserUpdatePopup: React.FC<UserUpdatePopupProps> = (props) => {
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
changeUpdateAuthorId({ authorId: e.target.value })
|
||||
changeUpdateAuthorId({
|
||||
authorId: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
import undo from "assets/images/undo.svg";
|
||||
@ -18,6 +18,7 @@ import {
|
||||
selectIsLoading,
|
||||
selectWorktypes,
|
||||
selectActiveWorktypeId,
|
||||
deleteWorktypeAsync,
|
||||
} from "features/workflow/worktype";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
|
||||
@ -86,6 +87,23 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedActiveWorktypeId]);
|
||||
|
||||
// 削除ボタン押下時の処理
|
||||
const onDeleteWoktype = useCallback(
|
||||
async (worktypeId: number) => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(deleteWorktypeAsync({ worktypeId }));
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listWorktypesAsync());
|
||||
}
|
||||
},
|
||||
[dispatch, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddWorktypeIdPopup
|
||||
@ -253,9 +271,10 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
// onClick={}
|
||||
onClick={() => onDeleteWoktype(worktype.id)}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
|
||||
313
dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx
Normal file
313
dictation_client/src/pages/WorkflowPage/editworkflowPopup.tsx
Normal file
@ -0,0 +1,313 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { AppDispatch } from "app/store";
|
||||
import progress_activit from "assets/images/progress_activit.svg";
|
||||
import {
|
||||
addAssignee,
|
||||
removeAssignee,
|
||||
changeAuthor,
|
||||
changeTemplate,
|
||||
changeWorktype,
|
||||
clearWorkflow,
|
||||
selectIsAddLoading,
|
||||
selectWorkflowAssinee,
|
||||
selectWorkflowError,
|
||||
selectWorkflowRelations,
|
||||
selectSelectedWorkflow,
|
||||
selectAuthorId,
|
||||
selectWorktypeId,
|
||||
setAssignees,
|
||||
selectTemplateId,
|
||||
} from "features/workflow";
|
||||
import {
|
||||
getworkflowRelationsAsync,
|
||||
listWorkflowAsync,
|
||||
updateWorkflowAsync,
|
||||
} from "features/workflow/operations";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { getTranslationID } from "translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
interface EditWorkflowPopupProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const EditWorkflowPopup: React.FC<EditWorkflowPopupProps> = (
|
||||
props
|
||||
): JSX.Element => {
|
||||
const { onClose } = props;
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
// 保存ボタンを押したかどうか
|
||||
const [isPushEditButton, setIsPushEditButton] = useState<boolean>(false);
|
||||
|
||||
const workflow = useSelector(selectSelectedWorkflow);
|
||||
const authorId = useSelector(selectAuthorId);
|
||||
const worktypeId = useSelector(selectWorktypeId);
|
||||
const templateId = useSelector(selectTemplateId);
|
||||
|
||||
const workflowRelations = useSelector(selectWorkflowRelations);
|
||||
const { poolAssignees, selectedAssignees } = useSelector(
|
||||
selectWorkflowAssinee
|
||||
);
|
||||
const isLoading = useSelector(selectIsAddLoading);
|
||||
const { hasAuthorIdEmptyError, hasSelectedWorkflowAssineeEmptyError } =
|
||||
useSelector(selectWorkflowError);
|
||||
useEffect(() => {
|
||||
dispatch(getworkflowRelationsAsync());
|
||||
// ポップアップのアンマウント時に初期化を行う
|
||||
return () => {
|
||||
dispatch(clearWorkflow());
|
||||
setIsPushEditButton(false);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(changeAuthor({ authorId: workflow?.author?.id }));
|
||||
dispatch(changeWorktype({ worktypeId: workflow?.worktype?.id }));
|
||||
dispatch(changeTemplate({ templateId: workflow?.template?.id }));
|
||||
dispatch(setAssignees({ assignees: workflow?.typists ?? [] }));
|
||||
}, [dispatch, workflow]);
|
||||
|
||||
const changeWorktypeId = useCallback(
|
||||
(target: string) => {
|
||||
// 空文字の場合はundefinedをdispatchする
|
||||
if (target === "") {
|
||||
dispatch(changeWorktype({ worktypeId: undefined }));
|
||||
} else if (!Number.isNaN(Number(target))) {
|
||||
dispatch(changeWorktype({ worktypeId: Number(target) }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const changeTemplateId = useCallback(
|
||||
(target: string) => {
|
||||
// 空文字の場合はundefinedをdispatchする
|
||||
if (target === "") {
|
||||
dispatch(changeTemplate({ templateId: undefined }));
|
||||
} else if (!Number.isNaN(Number(target))) {
|
||||
dispatch(changeTemplate({ templateId: Number(target) }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const changeAuthorId = useCallback(
|
||||
(target: string) => {
|
||||
if (!Number.isNaN(target)) {
|
||||
dispatch(changeAuthor({ authorId: Number(target) }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// 保存ボタン押下時の処理
|
||||
const handleSave = useCallback(async () => {
|
||||
setIsPushEditButton(true);
|
||||
// エラーチェック
|
||||
if (hasAuthorIdEmptyError || hasSelectedWorkflowAssineeEmptyError) {
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(updateWorkflowAsync());
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
onClose();
|
||||
dispatch(listWorkflowAsync());
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
hasAuthorIdEmptyError,
|
||||
hasSelectedWorkflowAssineeEmptyError,
|
||||
onClose,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("workflowPage.label.editRoutingRule"))}
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<img
|
||||
src={close}
|
||||
className={styles.modalTitleIcon}
|
||||
alt="close"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</p>
|
||||
<form className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dt>{t(getTranslationID("workflowPage.label.authorID"))}</dt>
|
||||
<dd>
|
||||
<select
|
||||
className={styles.formInput}
|
||||
value={authorId}
|
||||
onChange={(e) => {
|
||||
changeAuthorId(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="" hidden>
|
||||
{`-- ${t(
|
||||
getTranslationID("workflowPage.label.selectAuthor")
|
||||
)} --`}
|
||||
</option>
|
||||
{workflowRelations?.authors.map((author) => (
|
||||
<option key={author.authorId} value={author.id}>
|
||||
{author.authorId}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{isPushEditButton && hasAuthorIdEmptyError && (
|
||||
<span className={styles.formError}>
|
||||
{t(getTranslationID("workflowPage.message.inputEmptyError"))}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dt className={styles.overLine}>
|
||||
{t(getTranslationID("workflowPage.label.worktypeOptional"))}
|
||||
</dt>
|
||||
<dd>
|
||||
<select
|
||||
className={styles.formInput}
|
||||
value={worktypeId}
|
||||
onChange={(e) => {
|
||||
changeWorktypeId(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="" hidden>
|
||||
{`-- ${t(
|
||||
getTranslationID("workflowPage.label.selectWorktypeId")
|
||||
)} --`}
|
||||
</option>
|
||||
<option value="">
|
||||
{`-- ${t(getTranslationID("common.label.notSelected"))} --`}
|
||||
</option>
|
||||
{workflowRelations?.worktypes.map((worktype) => (
|
||||
<option key={worktype.id} value={worktype.id}>
|
||||
{worktype.worktypeId}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</dd>
|
||||
<dt className={styles.formTitle}>
|
||||
{t(getTranslationID("typistGroupSetting.label.transcriptionist"))}
|
||||
</dt>
|
||||
<dd className={`${styles.formChange} ${styles.last}`}>
|
||||
<ul className={styles.chooseMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("workflowPage.label.selected"))}
|
||||
</li>
|
||||
{selectedAssignees?.map((x) => {
|
||||
const key = `${x.typistName}_${
|
||||
x.typistUserId ?? x.typistGroupId
|
||||
}`;
|
||||
return (
|
||||
<li key={key}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={x.typistName}
|
||||
id={key}
|
||||
checked
|
||||
onClick={() => {
|
||||
dispatch(removeAssignee({ assignee: x }));
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={key} title="Remove">
|
||||
{x.typistName}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<p />
|
||||
<ul className={styles.holdMember}>
|
||||
<li className={styles.changeTitle}>
|
||||
{t(getTranslationID("workflowPage.label.pool"))}
|
||||
</li>
|
||||
{poolAssignees?.map((x) => {
|
||||
const key = `${x.typistName}_${
|
||||
x.typistUserId ?? x.typistGroupId
|
||||
}`;
|
||||
return (
|
||||
<li key={key}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.formCheck}
|
||||
value={x.typistName}
|
||||
id={key}
|
||||
onClick={() => dispatch(addAssignee({ assignee: x }))}
|
||||
/>
|
||||
<label htmlFor={key} title="Add">
|
||||
{x.typistName}
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{isPushEditButton && hasSelectedWorkflowAssineeEmptyError && (
|
||||
<span
|
||||
className={styles.formError}
|
||||
style={{ margin: "0px 30px 0px 30px" }}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"workflowPage.message.selectedTypistEmptyError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dt className={styles.overLine}>
|
||||
{t(getTranslationID("workflowPage.label.templateOptional"))}
|
||||
</dt>
|
||||
<dd className={styles.last}>
|
||||
<select
|
||||
className={styles.formInput}
|
||||
value={templateId}
|
||||
onChange={(e) => {
|
||||
changeTemplateId(e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="" hidden>
|
||||
{`-- ${t(
|
||||
getTranslationID("workflowPage.label.selectTemplate")
|
||||
)} --`}
|
||||
</option>
|
||||
<option value="">
|
||||
{`-- ${t(getTranslationID("common.label.notSelected"))} --`}
|
||||
</option>
|
||||
{workflowRelations?.templates.map((template) => (
|
||||
<option
|
||||
key={`${template.name}_${template.id}`}
|
||||
value={template.id}
|
||||
>
|
||||
{template.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
value={t(getTranslationID("common.label.save"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||
!isLoading ? styles.isActive : ""
|
||||
}`}
|
||||
onClick={handleSave}
|
||||
/>
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Header from "components/header";
|
||||
import Footer from "components/footer";
|
||||
import styles from "styles/app.module.scss";
|
||||
@ -10,23 +10,50 @@ import groupSettingImg from "assets/images/group_setting.svg";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { listWorkflowAsync } from "features/workflow/operations";
|
||||
import { selectIsLoading, selectWorkflows } from "features/workflow";
|
||||
import {
|
||||
deleteWorkflowAsync,
|
||||
listWorkflowAsync,
|
||||
changeSelectedWorkflow,
|
||||
selectIsLoading,
|
||||
selectWorkflows,
|
||||
} from "features/workflow";
|
||||
import progress_activit from "assets/images/progress_activit.svg";
|
||||
import { getTranslationID } from "translation";
|
||||
import { AddWorkflowPopup } from "./addworkflowPopup";
|
||||
import { EditWorkflowPopup } from "./editworkflowPopup";
|
||||
|
||||
const WorkflowPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
// 追加Popupの表示制御
|
||||
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
|
||||
// 編集Popupの表示制御
|
||||
const [isShowEditPopup, setIsShowEditPopup] = useState<boolean>(false);
|
||||
|
||||
const workflows = useSelector(selectWorkflows);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(listWorkflowAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
// ワークフロー削除
|
||||
const onDeleteWorkflow = useCallback(
|
||||
async (workflowId: number) => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(deleteWorkflowAsync({ workflowId }));
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listWorkflowAsync());
|
||||
}
|
||||
},
|
||||
[dispatch, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isShowAddPopup && (
|
||||
@ -36,6 +63,13 @@ const WorkflowPage: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isShowEditPopup && (
|
||||
<EditWorkflowPopup
|
||||
onClose={() => {
|
||||
setIsShowEditPopup(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
@ -136,14 +170,29 @@ const WorkflowPage: React.FC = (): JSX.Element => {
|
||||
<td className={styles.clm0}>
|
||||
<ul className={styles.menuInTable}>
|
||||
<li>
|
||||
<a href="">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
changeSelectedWorkflow({
|
||||
workflowId: workflow.id,
|
||||
})
|
||||
);
|
||||
setIsShowEditPopup(true);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID("workflowPage.label.editRule")
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="">
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
onClick={() => {
|
||||
onDeleteWorkflow(workflow.id);
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@ -293,6 +293,31 @@ h5 {
|
||||
width: 1.4rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
.accountSignout {
|
||||
display: inline-block;
|
||||
margin-left: 1rem;
|
||||
color: #999999;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.accountSignout .accountIcon {
|
||||
opacity: 0.5;
|
||||
margin-right: 0.2rem;
|
||||
-moz-transition: all 0.3s ease-out;
|
||||
-ms-transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.accountSignout:hover {
|
||||
color: #333333;
|
||||
}
|
||||
.accountSignout:hover .accountIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
_:-ms-lang(x)::-ms-backdrop,
|
||||
.header {
|
||||
@ -647,6 +672,7 @@ h3 + .brCrumb .tlIcon {
|
||||
padding: 0.6rem 0;
|
||||
margin-right: 1rem;
|
||||
cursor: pointer;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.form label:has(input:disabled) {
|
||||
cursor: default;
|
||||
@ -870,6 +896,24 @@ h3 + .brCrumb .tlIcon {
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
.listDocument {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.listDocument li {
|
||||
padding: 0 0 0 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.listDocument li::before {
|
||||
content: "";
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
background: #333333;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0.2rem;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.boxFlex {
|
||||
display: flex;
|
||||
@ -1428,7 +1472,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license > div,
|
||||
.dictation > div,
|
||||
.partners > div,
|
||||
.workflow > div {
|
||||
.workflow > div,
|
||||
.support > div {
|
||||
padding: 0 2rem;
|
||||
position: relative;
|
||||
}
|
||||
@ -1437,7 +1482,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license > div .icLoading,
|
||||
.dictation > div .icLoading,
|
||||
.partners > div .icLoading,
|
||||
.workflow > div .icLoading {
|
||||
.workflow > div .icLoading,
|
||||
.support > div .icLoading {
|
||||
top: 5.5rem;
|
||||
left: calc(50% - 25px);
|
||||
}
|
||||
@ -1446,7 +1492,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr.tableHeader th.clm0,
|
||||
.dictation .table tr.tableHeader th.clm0,
|
||||
.partners .table tr.tableHeader th.clm0,
|
||||
.workflow .table tr.tableHeader th.clm0 {
|
||||
.workflow .table tr.tableHeader th.clm0,
|
||||
.support .table tr.tableHeader th.clm0 {
|
||||
width: 0px;
|
||||
padding: 0 0;
|
||||
}
|
||||
@ -1455,7 +1502,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr:not(.tableHeader),
|
||||
.dictation .table tr:not(.tableHeader),
|
||||
.partners .table tr:not(.tableHeader),
|
||||
.workflow .table tr:not(.tableHeader) {
|
||||
.workflow .table tr:not(.tableHeader),
|
||||
.support .table tr:not(.tableHeader) {
|
||||
position: relative;
|
||||
}
|
||||
.account .table tr:not(.tableHeader):hover .menuInTable,
|
||||
@ -1463,7 +1511,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr:not(.tableHeader):hover .menuInTable,
|
||||
.dictation .table tr:not(.tableHeader):hover .menuInTable,
|
||||
.partners .table tr:not(.tableHeader):hover .menuInTable,
|
||||
.workflow .table tr:not(.tableHeader):hover .menuInTable {
|
||||
.workflow .table tr:not(.tableHeader):hover .menuInTable,
|
||||
.support .table tr:not(.tableHeader):hover .menuInTable {
|
||||
opacity: 1;
|
||||
}
|
||||
.account .table tr:not(.tableHeader).isSelected,
|
||||
@ -1471,7 +1520,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr:not(.tableHeader).isSelected,
|
||||
.dictation .table tr:not(.tableHeader).isSelected,
|
||||
.partners .table tr:not(.tableHeader).isSelected,
|
||||
.workflow .table tr:not(.tableHeader).isSelected {
|
||||
.workflow .table tr:not(.tableHeader).isSelected,
|
||||
.support .table tr:not(.tableHeader).isSelected {
|
||||
background: #0084b2;
|
||||
color: #ffffff;
|
||||
}
|
||||
@ -1480,7 +1530,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr:not(.tableHeader).isSelected:hover,
|
||||
.dictation .table tr:not(.tableHeader).isSelected:hover,
|
||||
.partners .table tr:not(.tableHeader).isSelected:hover,
|
||||
.workflow .table tr:not(.tableHeader).isSelected:hover {
|
||||
.workflow .table tr:not(.tableHeader).isSelected:hover,
|
||||
.support .table tr:not(.tableHeader).isSelected:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
.account .table tr:not(.tableHeader).isSelected .menuInTable,
|
||||
@ -1488,7 +1539,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table tr:not(.tableHeader).isSelected .menuInTable,
|
||||
.dictation .table tr:not(.tableHeader).isSelected .menuInTable,
|
||||
.partners .table tr:not(.tableHeader).isSelected .menuInTable,
|
||||
.workflow .table tr:not(.tableHeader).isSelected .menuInTable {
|
||||
.workflow .table tr:not(.tableHeader).isSelected .menuInTable,
|
||||
.support .table tr:not(.tableHeader).isSelected .menuInTable {
|
||||
display: block;
|
||||
}
|
||||
.account .table td,
|
||||
@ -1496,7 +1548,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table td,
|
||||
.dictation .table td,
|
||||
.partners .table td,
|
||||
.workflow .table td {
|
||||
.workflow .table td,
|
||||
.support .table td {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -1508,7 +1561,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table td.clm0,
|
||||
.dictation .table td.clm0,
|
||||
.partners .table td.clm0,
|
||||
.workflow .table td.clm0 {
|
||||
.workflow .table td.clm0,
|
||||
.support .table td.clm0 {
|
||||
width: 0px;
|
||||
padding: 0 0;
|
||||
overflow: visible;
|
||||
@ -1521,7 +1575,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table.user,
|
||||
.dictation .table.user,
|
||||
.partners .table.user,
|
||||
.workflow .table.user {
|
||||
.workflow .table.user,
|
||||
.support .table.user {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
.account .table.user th::after,
|
||||
@ -1529,7 +1584,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table.user th::after,
|
||||
.dictation .table.user th::after,
|
||||
.partners .table.user th::after,
|
||||
.workflow .table.user th::after {
|
||||
.workflow .table.user th::after,
|
||||
.support .table.user th::after {
|
||||
display: none;
|
||||
}
|
||||
.account .table.user tr:not(.tableHeader) td,
|
||||
@ -1537,7 +1593,8 @@ _:-ms-lang(x)::-ms-backdrop,
|
||||
.license .table.user tr:not(.tableHeader) td,
|
||||
.dictation .table.user tr:not(.tableHeader) td,
|
||||
.partners .table.user tr:not(.tableHeader) td,
|
||||
.workflow .table.user tr:not(.tableHeader) td {
|
||||
.workflow .table.user tr:not(.tableHeader) td,
|
||||
.support .table.user tr:not(.tableHeader) td {
|
||||
padding-bottom: 2rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
@ -2500,6 +2557,9 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
padding: 0 3rem;
|
||||
}
|
||||
|
||||
.txContents {
|
||||
padding: 3rem;
|
||||
}
|
||||
.txNormal {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
|
||||
10
dictation_client/src/styles/app.module.scss.d.ts
vendored
10
dictation_client/src/styles/app.module.scss.d.ts
vendored
@ -7,6 +7,7 @@ declare const classNames: {
|
||||
readonly isActive: "isActive";
|
||||
readonly accountInfo: "accountInfo";
|
||||
readonly accountIcon: "accountIcon";
|
||||
readonly accountSignout: "accountSignout";
|
||||
readonly main: "main";
|
||||
readonly mainSmall: "mainSmall";
|
||||
readonly mainLogin: "mainLogin";
|
||||
@ -45,10 +46,10 @@ declare const classNames: {
|
||||
readonly formBack: "formBack";
|
||||
readonly formButtonTx: "formButtonTx";
|
||||
readonly formDone: "formDone";
|
||||
readonly formDelete: "formDelete";
|
||||
readonly formTrash: "formTrash";
|
||||
readonly listVertical: "listVertical";
|
||||
readonly listHeader: "listHeader";
|
||||
readonly listDocument: "listDocument";
|
||||
readonly boxFlex: "boxFlex";
|
||||
readonly aru: "aru";
|
||||
readonly btw: "btw";
|
||||
@ -101,11 +102,11 @@ declare const classNames: {
|
||||
readonly dictation: "dictation";
|
||||
readonly partners: "partners";
|
||||
readonly workflow: "workflow";
|
||||
readonly support: "support";
|
||||
readonly clm0: "clm0";
|
||||
readonly menuInTable: "menuInTable";
|
||||
readonly isSelected: "isSelected";
|
||||
readonly formCheckToggle: "formCheckToggle";
|
||||
readonly toggleBase: "toggleBase";
|
||||
readonly alignRight: "alignRight";
|
||||
readonly menuAction: "menuAction";
|
||||
readonly inTable: "inTable";
|
||||
readonly menuLink: "menuLink";
|
||||
@ -197,7 +198,6 @@ declare const classNames: {
|
||||
readonly worktype: "worktype";
|
||||
readonly selectMenu: "selectMenu";
|
||||
readonly alignLeft: "alignLeft";
|
||||
readonly alignRight: "alignRight";
|
||||
readonly floatNone: "floatNone";
|
||||
readonly floatLeft: "floatLeft";
|
||||
readonly floatRight: "floatRight";
|
||||
@ -217,8 +217,8 @@ declare const classNames: {
|
||||
readonly paddSide1: "paddSide1";
|
||||
readonly paddSide2: "paddSide2";
|
||||
readonly paddSide3: "paddSide3";
|
||||
readonly txContents: "txContents";
|
||||
readonly txIcon: "txIcon";
|
||||
readonly txWswrap: "txWswrap";
|
||||
readonly required: "required";
|
||||
};
|
||||
export = classNames;
|
||||
|
||||
@ -358,6 +358,7 @@
|
||||
"label": {
|
||||
"title": "Arbeitsablauf",
|
||||
"addRoutingRule": "(de)Add Routing Rule",
|
||||
"editRoutingRule": "(de)Edit Routing Rule",
|
||||
"templateSetting": "(de)Template Setting",
|
||||
"worktypeIdSetting": "(de)WorktypeID Setting",
|
||||
"typistGroupSetting": "(de)Transcriptionist Group Setting",
|
||||
@ -428,7 +429,8 @@
|
||||
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(de)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -497,5 +499,15 @@
|
||||
"message": "(de)Your account has been deleted. Thank you for using our services.",
|
||||
"backToTopPageLink": "(de)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(de)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(de)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(de)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(de)for ODDS.",
|
||||
"button": "(de)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,6 +358,7 @@
|
||||
"label": {
|
||||
"title": "Workflow",
|
||||
"addRoutingRule": "Add Routing Rule",
|
||||
"editRoutingRule": "Edit Routing Rule",
|
||||
"templateSetting": "Template Setting",
|
||||
"worktypeIdSetting": "WorktypeID Setting",
|
||||
"typistGroupSetting": "Transcriptionist Group Setting",
|
||||
@ -428,7 +429,8 @@
|
||||
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -497,5 +499,15 @@
|
||||
"message": "Your account has been deleted. Thank you for using our services.",
|
||||
"backToTopPageLink": "Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "Click here to read the terms of use.",
|
||||
"linkOfDpa": "Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "Yes, I agree to the terms of use.",
|
||||
"forOdds": "for ODDS.",
|
||||
"button": "Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,6 +358,7 @@
|
||||
"label": {
|
||||
"title": "flujo de trabajo",
|
||||
"addRoutingRule": "(es)Add Routing Rule",
|
||||
"editRoutingRule": "(es)Edit Routing Rule",
|
||||
"templateSetting": "(es)Template Setting",
|
||||
"worktypeIdSetting": "(es)WorktypeID Setting",
|
||||
"typistGroupSetting": "(es)Transcriptionist Group Setting",
|
||||
@ -428,7 +429,8 @@
|
||||
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(es)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -497,5 +499,15 @@
|
||||
"message": "(es)Your account has been deleted. Thank you for using our services.",
|
||||
"backToTopPageLink": "(es)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(es)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(es)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(es)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(es)for ODDS.",
|
||||
"button": "(es)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,6 +358,7 @@
|
||||
"label": {
|
||||
"title": "Flux de travail",
|
||||
"addRoutingRule": "(fr)Add Routing Rule",
|
||||
"editRoutingRule": "(fr)Edit Routing Rule",
|
||||
"templateSetting": "(fr)Template Setting",
|
||||
"worktypeIdSetting": "(fr)WorktypeID Setting",
|
||||
"typistGroupSetting": "(fr)Transcriptionist Group Setting",
|
||||
@ -428,7 +429,8 @@
|
||||
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(fr)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -497,5 +499,15 @@
|
||||
"message": "(fr)Your account has been deleted. Thank you for using our services.",
|
||||
"backToTopPageLink": "(fr)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(fr)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(fr)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(fr)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(fr)for ODDS.",
|
||||
"button": "(fr)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `checkout_permission` DROP FOREIGN KEY `checkout_permission_fk_task_id`;
|
||||
ALTER TABLE `tasks` DROP FOREIGN KEY `tasks_fk_account_id`;
|
||||
ALTER TABLE `template_files` DROP FOREIGN KEY `template_files_fk_account_id`;
|
||||
ALTER TABLE `option_items` DROP FOREIGN KEY `option_items_fk_worktype_id`;
|
||||
ALTER TABLE `worktypes` DROP FOREIGN KEY `worktypes_fk_account_id`;
|
||||
ALTER TABLE `audio_option_items` DROP FOREIGN KEY `audio_option_items_fk_audio_file_id`;
|
||||
ALTER TABLE `audio_files` DROP FOREIGN KEY `audio_files_fk_account_id`;
|
||||
ALTER TABLE `user_group_member` DROP FOREIGN KEY `user_group_member_fk_user_group_id`;
|
||||
ALTER TABLE `user_group` DROP FOREIGN KEY `user_group_fk_account_id`;
|
||||
ALTER TABLE `license_allocation_history` DROP FOREIGN KEY `license_allocation_history_fk_account_id`;
|
||||
ALTER TABLE `card_licenses` DROP FOREIGN KEY `card_licenses_fk_license_id`;
|
||||
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_fk_account_id`;
|
||||
ALTER TABLE `license_orders` DROP FOREIGN KEY `license_orders_fk_from_account_id`;
|
||||
ALTER TABLE `sort_criteria` DROP FOREIGN KEY `sort_criteria_fk_user_id`;
|
||||
ALTER TABLE `users` DROP FOREIGN KEY `users_fk_account_id`;
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `checkout_permission` ADD CONSTRAINT `checkout_permission_fk_task_id` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `tasks` ADD CONSTRAINT `tasks_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `template_files` ADD CONSTRAINT `template_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `option_items` ADD CONSTRAINT `option_items_fk_worktype_id` FOREIGN KEY (`worktype_id`) REFERENCES `worktypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `worktypes` ADD CONSTRAINT `worktypes_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `audio_option_items` ADD CONSTRAINT `audio_option_items_fk_audio_file_id` FOREIGN KEY (`audio_file_id`) REFERENCES `audio_files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `audio_files` ADD CONSTRAINT `audio_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `user_group_member` ADD CONSTRAINT `user_group_member_fk_user_group_id` FOREIGN KEY (`user_group_id`) REFERENCES `user_group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `user_group` ADD CONSTRAINT `user_group_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `license_allocation_history` ADD CONSTRAINT `license_allocation_history_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `card_licenses` ADD CONSTRAINT `card_licenses_fk_license_id` FOREIGN KEY (`license_id`) REFERENCES `licenses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `license_orders` ADD CONSTRAINT `license_orders_fk_from_account_id` FOREIGN KEY (`from_account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `sort_criteria` ADD CONSTRAINT `sort_criteria_fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `users` ADD CONSTRAINT `users_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,9 @@
|
||||
-- +migrate Up
|
||||
insert into terms(terms.document_type, terms.version) values('EURA', 'V0.1');
|
||||
insert into terms(terms.document_type, terms.version) values('DPA', 'V0.1');
|
||||
commit;
|
||||
|
||||
-- +migrate Down
|
||||
delete from terms where terms.document_type = 'EURA' and terms.version = 'V0.1';
|
||||
delete from terms where terms.document_type = 'DPA' and terms.version = 'V0.1';
|
||||
commit;
|
||||
@ -990,6 +990,59 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes/{id}/delete": {
|
||||
"post": {
|
||||
"operationId": "deleteWorktype",
|
||||
"summary": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Worktypeの内部ID",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeleteWorktypeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes/{id}/option-items": {
|
||||
"get": {
|
||||
"operationId": "getOptionItems",
|
||||
@ -3156,6 +3209,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "パラメータ不正エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
@ -3229,7 +3290,7 @@
|
||||
}
|
||||
},
|
||||
"/terms": {
|
||||
"post": {
|
||||
"get": {
|
||||
"operationId": "getTermsInfo",
|
||||
"summary": "",
|
||||
"parameters": [],
|
||||
@ -3698,6 +3759,7 @@
|
||||
"required": ["worktypeId"]
|
||||
},
|
||||
"UpdateWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"DeleteWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"GetWorktypeOptionItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -4403,7 +4465,7 @@
|
||||
"licenseId": { "type": "number" },
|
||||
"expiryDate": { "format": "date-time", "type": "string" }
|
||||
},
|
||||
"required": ["licenseId", "expiryDate"]
|
||||
"required": ["licenseId"]
|
||||
},
|
||||
"GetAllocatableLicensesResponse": {
|
||||
"type": "object",
|
||||
@ -4561,7 +4623,24 @@
|
||||
"required": ["pns", "handler"]
|
||||
},
|
||||
"RegisterResponse": { "type": "object", "properties": {} },
|
||||
"GetTermsInfoResponse": { "type": "object", "properties": {} }
|
||||
"TermInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"documentType": { "type": "string", "description": "利用規約種別" },
|
||||
"version": { "type": "string", "description": "バージョン" }
|
||||
},
|
||||
"required": ["documentType", "version"]
|
||||
},
|
||||
"GetTermsInfoResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"termsInfo": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/TermInfo" }
|
||||
}
|
||||
},
|
||||
"required": ["termsInfo"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ export const ErrorCodes = [
|
||||
'E010206', // DBのTierが想定外の値エラー
|
||||
'E010207', // ユーザーのRole変更不可エラー
|
||||
'E010208', // ユーザーの暗号化パスワード不足エラー
|
||||
'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー
|
||||
'E010301', // メールアドレス登録済みエラー
|
||||
'E010302', // authorId重複エラー
|
||||
'E010401', // PONumber重複エラー
|
||||
@ -56,6 +57,7 @@ export const ErrorCodes = [
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
'E011004', // ワークタイプ使用中エラー
|
||||
'E012001', // テンプレートファイル不在エラー
|
||||
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
'E013002', // ワークフロー不在エラー
|
||||
|
||||
@ -21,6 +21,7 @@ export const errors: Errors = {
|
||||
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',
|
||||
@ -45,6 +46,7 @@ export const errors: Errors = {
|
||||
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',
|
||||
|
||||
@ -92,13 +92,15 @@ export class RoleGuard implements CanActivate {
|
||||
* @returns true/false
|
||||
*/
|
||||
checkRole(role: string): boolean {
|
||||
const { roles } = this.settings;
|
||||
|
||||
const settings = this.settings;
|
||||
if (!settings || !settings.roles) {
|
||||
return true;
|
||||
}
|
||||
const userRoles = role.split(' ');
|
||||
|
||||
// Role毎にAccessTokenの権限チェックを行う
|
||||
for (let i = 0; i < roles.length; i++) {
|
||||
const role = roles[i];
|
||||
for (let i = 0; i < settings.roles.length; i++) {
|
||||
const role = settings.roles[i];
|
||||
let isValid = false;
|
||||
if (Array.isArray(role)) {
|
||||
isValid = role.every((x) => userRoles.includes(x));
|
||||
@ -172,9 +174,12 @@ export class RoleGuard implements CanActivate {
|
||||
* @returns true/false
|
||||
*/
|
||||
checkTier(tier: number): boolean {
|
||||
const { tiers } = this.settings;
|
||||
const settings = this.settings;
|
||||
if (!settings || !settings.tiers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 宣言された階層中にパラメータの内容が含まれていればtrue
|
||||
return tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
|
||||
return settings.tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ export const getPrivateKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PRIVATE_KEY')?.replace(/\\n/g, '\n') ?? ''
|
||||
configService.getOrThrow<string>('JWT_PRIVATE_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
|
||||
@ -140,6 +140,6 @@ export const getPublicKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n') ?? ''
|
||||
configService.getOrThrow<string>('JWT_PUBLIC_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
|
||||
@ -15,10 +15,9 @@ export const makePassword = (): string => {
|
||||
|
||||
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
|
||||
let valid = false;
|
||||
let autoGeneratedPassword: string;
|
||||
let autoGeneratedPassword: string = '';
|
||||
|
||||
while (!valid) {
|
||||
autoGeneratedPassword = '';
|
||||
// パスワードをランダムに決定
|
||||
while (autoGeneratedPassword.length < passLength) {
|
||||
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
||||
|
||||
@ -34,10 +34,13 @@ import { TemplatesService } from '../../features/templates/templates.service';
|
||||
import { TemplatesModule } from '../../features/templates/templates.module';
|
||||
import { WorkflowsService } from '../../features/workflows/workflows.service';
|
||||
import { WorkflowsModule } from '../../features/workflows/workflows.module';
|
||||
import { TermsService } from '../../features/terms/terms.service';
|
||||
import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module';
|
||||
import { TermsModule } from '../../features/terms/terms.module';
|
||||
|
||||
export const makeTestingModule = async (
|
||||
datasource: DataSource,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
@ -56,6 +59,7 @@ export const makeTestingModule = async (
|
||||
LicensesModule,
|
||||
TemplatesModule,
|
||||
WorkflowsModule,
|
||||
TermsModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
LicensesRepositoryModule,
|
||||
@ -71,6 +75,7 @@ export const makeTestingModule = async (
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
TermsRepositoryModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
@ -82,6 +87,7 @@ export const makeTestingModule = async (
|
||||
LicensesService,
|
||||
TemplatesService,
|
||||
WorkflowsService,
|
||||
TermsService,
|
||||
],
|
||||
})
|
||||
.useMocker(async (token) => {
|
||||
|
||||
@ -3,6 +3,7 @@ import { DataSource } from 'typeorm';
|
||||
import { User, UserArchive } from '../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../repositories/accounts/entity/account.entity';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { License } from '../../repositories/licenses/entity/license.entity';
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
@ -57,11 +58,11 @@ export const makeHierarchicalAccounts = async (
|
||||
}
|
||||
// 第2階層を作成
|
||||
{
|
||||
const { account: tier1 } = state.tier1Accounts.slice().shift();
|
||||
const tier1 = state.tier1Accounts.slice().shift();
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
parent_account_id: tier1?.account.id,
|
||||
company_name: 'OMDS_US',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
@ -72,7 +73,7 @@ export const makeHierarchicalAccounts = async (
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
parent_account_id: tier1?.account.id,
|
||||
company_name: 'OMDS_EU',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
@ -201,7 +202,7 @@ export const makeTestAccount = async (
|
||||
}
|
||||
|
||||
// Accountの管理者を設定する
|
||||
let secondaryAdminUserId = null;
|
||||
let secondaryAdminUserId: number | null = null;
|
||||
if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
|
||||
secondaryAdminUserId = userId;
|
||||
}
|
||||
@ -224,6 +225,9 @@ export const makeTestAccount = async (
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
if (!account || !admin) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
|
||||
return {
|
||||
account: account,
|
||||
@ -263,7 +267,9 @@ export const makeTestSimpleAccount = async (
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
return account;
|
||||
};
|
||||
|
||||
@ -299,11 +305,15 @@ export const makeTestUser = async (
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
return await datasource.getRepository(User).findOne({
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -312,7 +322,10 @@ export const makeTestUser = async (
|
||||
* @param id アカウントID
|
||||
* @returns 該当アカウント
|
||||
*/
|
||||
export const getAccount = async (dataSource: DataSource, id: number) => {
|
||||
export const getAccount = async (
|
||||
dataSource: DataSource,
|
||||
id: number,
|
||||
): Promise<Account | null> => {
|
||||
return await dataSource.getRepository(Account).findOne({
|
||||
where: { id: id },
|
||||
});
|
||||
@ -353,7 +366,7 @@ export const getUserFromExternalId = async (
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
): Promise<User | null> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
@ -381,3 +394,14 @@ export const getUserArchive = async (
|
||||
): Promise<UserArchive[]> => {
|
||||
return await dataSource.getRepository(UserArchive).find();
|
||||
};
|
||||
export const getLicenses = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
): Promise<License[]> => {
|
||||
const licenses = await datasource.getRepository(License).find({
|
||||
where: {
|
||||
account_id: account_id,
|
||||
},
|
||||
});
|
||||
return licenses;
|
||||
};
|
||||
|
||||
@ -244,7 +244,7 @@ export const OPTION_ITEM_VALUE_TYPE = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADB2C_SIGN_IN_TYPE = {
|
||||
EAMILADDRESS: 'emailAddress',
|
||||
EMAILADDRESS: 'emailAddress',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@ -252,3 +252,12 @@ export const ADB2C_SIGN_IN_TYPE = {
|
||||
* @const {string}
|
||||
*/
|
||||
export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]';
|
||||
|
||||
/**
|
||||
* 利用規約種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERM_TYPE = {
|
||||
EULA: 'EULA',
|
||||
DPA: 'DPA',
|
||||
} as const;
|
||||
|
||||
@ -2,10 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AccountsController } from './accounts.controller';
|
||||
import { AccountsService } from './accounts.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
describe('AccountsController', () => {
|
||||
let controller: AccountsController;
|
||||
const mockAccountService = {};
|
||||
const mockAuthService = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -16,10 +18,12 @@ describe('AccountsController', () => {
|
||||
}),
|
||||
],
|
||||
controllers: [AccountsController],
|
||||
providers: [AccountsService],
|
||||
providers: [AccountsService, AuthService],
|
||||
})
|
||||
.overrideProvider(AccountsService)
|
||||
.useValue(mockAccountService)
|
||||
.overrideProvider(AuthService)
|
||||
.useValue(mockAuthService)
|
||||
.compile();
|
||||
|
||||
controller = module.get<AccountsController>(AccountsController);
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
UseGuards,
|
||||
Param,
|
||||
Query,
|
||||
HttpException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiOperation,
|
||||
@ -65,6 +66,8 @@ import {
|
||||
GetAuthorsResponse,
|
||||
GetAccountInfoMinimalAccessRequest,
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
DeleteWorktypeRequestParam,
|
||||
DeleteWorktypeResponse,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -74,12 +77,15 @@ import { AccessToken } from '../../common/token';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('accounts')
|
||||
@Controller('accounts')
|
||||
export class AccountsController {
|
||||
constructor(
|
||||
private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@ -194,14 +200,26 @@ export class AccountsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('me')
|
||||
async getMyAccount(@Req() req: Request): Promise<GetMyAccountResponse> {
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const context = makeContext(payload.userId);
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
//アカウントID取得処理
|
||||
const accountInfo = await this.accountService.getAccountInfo(
|
||||
context,
|
||||
payload.userId,
|
||||
userId,
|
||||
);
|
||||
return accountInfo;
|
||||
}
|
||||
@ -231,8 +249,21 @@ export class AccountsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('authors')
|
||||
async getAuthors(@Req() req: Request): Promise<GetAuthorsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
const authors = await this.accountService.getAuthors(context, userId);
|
||||
@ -264,10 +295,23 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typists')
|
||||
async getTypists(@Req() req: Request): Promise<GetTypistsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const typists = await this.accountService.getTypists(payload.userId);
|
||||
const typists = await this.accountService.getTypists(userId);
|
||||
|
||||
return { typists };
|
||||
}
|
||||
@ -296,12 +340,23 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typist-groups')
|
||||
async getTypistGroups(@Req() req: Request): Promise<GetTypistGroupsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const typistGroups = await this.accountService.getTypistGroups(
|
||||
payload.userId,
|
||||
);
|
||||
const typistGroups = await this.accountService.getTypistGroups(userId);
|
||||
|
||||
return { typistGroups };
|
||||
}
|
||||
@ -342,8 +397,22 @@ export class AccountsController {
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -391,8 +460,22 @@ export class AccountsController {
|
||||
): Promise<CreateTypistGroupResponse> {
|
||||
const { typistGroupName, typistIds } = body;
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.createTypistGroup(
|
||||
context,
|
||||
@ -441,8 +524,22 @@ export class AccountsController {
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -492,10 +589,24 @@ export class AccountsController {
|
||||
@Body() body: CreatePartnerAccountRequest,
|
||||
): Promise<CreatePartnerAccountResponse> {
|
||||
const { companyName, country, email, adminName } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.createPartnerAccount(
|
||||
context,
|
||||
@ -503,8 +614,8 @@ export class AccountsController {
|
||||
country,
|
||||
email,
|
||||
adminName,
|
||||
payload.userId,
|
||||
payload.tier,
|
||||
userId,
|
||||
tier,
|
||||
);
|
||||
|
||||
return {};
|
||||
@ -620,15 +731,28 @@ export class AccountsController {
|
||||
): Promise<IssueLicenseResponse> {
|
||||
const { orderedAccountId, poNumber } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.issueLicense(
|
||||
context,
|
||||
orderedAccountId,
|
||||
accessToken.userId,
|
||||
accessToken.tier,
|
||||
userId,
|
||||
tier,
|
||||
poNumber,
|
||||
);
|
||||
return {};
|
||||
@ -688,14 +812,27 @@ export class AccountsController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelIssueRequest,
|
||||
): Promise<CancelIssueResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.cancelIssue(
|
||||
context,
|
||||
payload.userId,
|
||||
userId,
|
||||
body.poNumber,
|
||||
body.orderedAccountId,
|
||||
);
|
||||
@ -723,8 +860,21 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async getWorktypes(@Req() req: Request): Promise<GetWorktypesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const worktypes = await this.accountService.getWorktypes(context, userId);
|
||||
@ -762,8 +912,22 @@ export class AccountsController {
|
||||
@Body() body: CreateWorktypesRequest,
|
||||
): Promise<CreateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.createWorktype(
|
||||
@ -808,8 +972,22 @@ export class AccountsController {
|
||||
): Promise<UpdateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -824,6 +1002,59 @@ export class AccountsController {
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/worktypes/:id/delete')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: DeleteWorktypeResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'deleteWorktype' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async deleteWorktype(
|
||||
@Req() req: Request,
|
||||
@Param() param: DeleteWorktypeRequestParam,
|
||||
): Promise<DeleteWorktypeResponse> {
|
||||
const { id } = param;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.deleteWorktype(context, userId, id);
|
||||
return {};
|
||||
}
|
||||
|
||||
@Get('/worktypes/:id/option-items')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
@ -854,8 +1085,22 @@ export class AccountsController {
|
||||
@Param() param: GetOptionItemsRequestParam,
|
||||
): Promise<GetOptionItemsResponse> {
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -900,8 +1145,22 @@ export class AccountsController {
|
||||
): Promise<UpdateOptionItemsResponse> {
|
||||
const { optionItems } = body;
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -945,8 +1204,22 @@ export class AccountsController {
|
||||
@Body() body: PostActiveWorktypeRequest,
|
||||
): Promise<PostActiveWorktypeResponse> {
|
||||
const { id } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -989,8 +1262,22 @@ export class AccountsController {
|
||||
@Query() query: GetPartnersRequest,
|
||||
): Promise<GetPartnersResponse> {
|
||||
const { limit, offset } = query;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const response = await this.accountService.getPartners(
|
||||
@ -1042,8 +1329,22 @@ export class AccountsController {
|
||||
primaryAdminUserId,
|
||||
secondryAdminUserId,
|
||||
} = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId, tier } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.updateAccountInfo(
|
||||
@ -1056,7 +1357,7 @@ export class AccountsController {
|
||||
secondryAdminUserId,
|
||||
);
|
||||
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/delete')
|
||||
@ -1088,12 +1389,26 @@ export class AccountsController {
|
||||
@Body() body: DeleteAccountRequest,
|
||||
): Promise<DeleteAccountResponse> {
|
||||
const { accountId } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.deleteAccountAndData(context, userId, accountId);
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/minimal-access')
|
||||
@ -1116,11 +1431,22 @@ export class AccountsController {
|
||||
async getAccountInfoMinimalAccess(
|
||||
@Body() body: GetAccountInfoMinimalAccessRequest,
|
||||
): Promise<GetAccountInfoMinimalAccessResponse> {
|
||||
const context = makeContext(uuidv4());
|
||||
// IDトークンの検証
|
||||
const idToken = await this.authService.getVerifiedIdToken(body.idToken);
|
||||
const isVerified = await this.authService.isVerifiedUser(idToken);
|
||||
if (!isVerified) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010201'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO 仮実装。API実装タスクで本実装する。
|
||||
// const idToken = await this.authService.getVerifiedIdToken(body.idToken);
|
||||
// await this.accountService.getAccountInfoMinimalAccess(context, idToken);
|
||||
return;
|
||||
const context = makeContext(idToken.sub);
|
||||
|
||||
const tier = await this.accountService.getAccountInfoMinimalAccess(
|
||||
context,
|
||||
idToken.sub,
|
||||
);
|
||||
return { tier };
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktypes.repository.module';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -22,6 +23,6 @@ import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktype
|
||||
BlobstorageModule,
|
||||
],
|
||||
controllers: [AccountsController],
|
||||
providers: [AccountsService],
|
||||
providers: [AccountsService, AuthService],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ import {
|
||||
GetPartnersResponse,
|
||||
PostWorktypeOptionItem,
|
||||
Author,
|
||||
Partner,
|
||||
} from './types/types';
|
||||
import {
|
||||
DateWithZeroTime,
|
||||
@ -65,12 +66,15 @@ import {
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdInUseError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from '../../repositories/worktypes/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsService {
|
||||
private readonly mailFrom =
|
||||
this.configService.getOrThrow<string>('MAIL_FROM');
|
||||
constructor(
|
||||
private readonly accountRepository: AccountsRepositoryService,
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
@ -256,9 +260,6 @@ export class AccountsService {
|
||||
}
|
||||
|
||||
try {
|
||||
// メールの送信元を取得
|
||||
const from = this.configService.get<string>('MAIL_FROM') ?? '';
|
||||
|
||||
// メールの内容を構成
|
||||
const { subject, text, html } =
|
||||
await this.sendgridService.createMailContentFromEmailConfirm(
|
||||
@ -272,7 +273,7 @@ export class AccountsService {
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -393,7 +394,7 @@ export class AccountsService {
|
||||
userInfo.account_id,
|
||||
);
|
||||
|
||||
let parentInfo: Account;
|
||||
let parentInfo: Account | undefined;
|
||||
if (accountInfo.parent_account_id) {
|
||||
parentInfo = await this.accountRepository.findAccountById(
|
||||
accountInfo.parent_account_id,
|
||||
@ -480,14 +481,20 @@ export class AccountsService {
|
||||
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
const userGroup = await this.userGroupsRepository.getTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
);
|
||||
const { name, userGroupMembers } =
|
||||
await this.userGroupsRepository.getTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
);
|
||||
if (!userGroupMembers) {
|
||||
throw new TypistGroupNotExistError(
|
||||
`Typist Group is not exist. typistGroupId: ${typistGroupId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
typistGroupName: userGroup.name,
|
||||
typistIds: userGroup.userGroupMembers.map((x) => x.user_id),
|
||||
typistGroupName: name,
|
||||
typistIds: userGroupMembers.map((x) => x.user_id),
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
@ -540,6 +547,11 @@ export class AccountsService {
|
||||
|
||||
const typists = typistUsers.map((x) => {
|
||||
const user = adb2cUsers.find((adb2c) => adb2c.id === x.external_id);
|
||||
if (!user) {
|
||||
throw new Error(
|
||||
`user not found. externalId: ${x.external_id}, userId: ${x.id}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
id: x.id,
|
||||
name: user.displayName,
|
||||
@ -585,6 +597,11 @@ export class AccountsService {
|
||||
);
|
||||
|
||||
const authors = authorUsers.map((x) => {
|
||||
if (!x.author_id) {
|
||||
throw new Error(
|
||||
`author_id is Not Found. externalId: ${x.external_id}, userId: ${x.id}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
id: x.id,
|
||||
authorId: x.author_id,
|
||||
@ -700,8 +717,8 @@ export class AccountsService {
|
||||
creatorAccountTier + 1,
|
||||
externalUser.sub,
|
||||
USER_ROLES.NONE,
|
||||
null,
|
||||
null,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
account = newAccount;
|
||||
user = adminUser;
|
||||
@ -742,7 +759,6 @@ export class AccountsService {
|
||||
}
|
||||
|
||||
try {
|
||||
const from = this.configService.get<string>('MAIL_FROM') || '';
|
||||
const { subject, text, html } =
|
||||
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
|
||||
account.id,
|
||||
@ -752,7 +768,7 @@ export class AccountsService {
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -835,11 +851,19 @@ export class AccountsService {
|
||||
// 各子アカウントのShortageを算出してreturn用の変数にマージする
|
||||
const childrenPartnerLicenses: PartnerLicenseInfo[] = [];
|
||||
for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) {
|
||||
let childShortage;
|
||||
const { allocatableLicenseWithMargin, expiringSoonLicense } =
|
||||
childPartnerLicenseFromRepository;
|
||||
let childShortage: number = 0;
|
||||
if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) {
|
||||
childShortage =
|
||||
childPartnerLicenseFromRepository.allocatableLicenseWithMargin -
|
||||
childPartnerLicenseFromRepository.expiringSoonLicense;
|
||||
if (
|
||||
allocatableLicenseWithMargin === undefined ||
|
||||
expiringSoonLicense === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`Tier5 account has no allocatableLicenseWithMargin or expiringSoonLicense. accountId: ${accountId}`,
|
||||
);
|
||||
}
|
||||
childShortage = allocatableLicenseWithMargin - expiringSoonLicense;
|
||||
} else {
|
||||
childShortage =
|
||||
childPartnerLicenseFromRepository.stockLicense -
|
||||
@ -906,13 +930,13 @@ export class AccountsService {
|
||||
licenseOrder.issued_at !== null
|
||||
? new Date(licenseOrder.issued_at)
|
||||
.toISOString()
|
||||
.substr(0, 10)
|
||||
.substring(0, 10)
|
||||
.replace(/-/g, '/')
|
||||
: null,
|
||||
: undefined,
|
||||
numberOfOrder: licenseOrder.quantity,
|
||||
orderDate: new Date(licenseOrder.ordered_at)
|
||||
.toISOString()
|
||||
.substr(0, 10)
|
||||
.substring(0, 10)
|
||||
.replace(/-/g, '/'),
|
||||
poNumber: licenseOrder.po_number,
|
||||
status: licenseOrder.status,
|
||||
@ -1417,6 +1441,81 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを削除します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id
|
||||
* @returns worktype
|
||||
*/
|
||||
async deleteWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.deleteWorktype.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account, account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`account not found. externalId: ${externalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプを削除する
|
||||
await this.worktypesRepository.deleteWorktype(accountId, id);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case UserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AccountNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010501'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeがWorkflowで使用中の場合は400エラーを返す
|
||||
case WorktypeIdInUseError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011004'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.deleteWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプに紐づいたオプションアイテム一覧を取得します
|
||||
* @param context
|
||||
@ -1558,7 +1657,7 @@ export class AccountsService {
|
||||
async updateActiveWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
id: number | undefined,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` +
|
||||
@ -1625,34 +1724,40 @@ export class AccountsService {
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
const partners = await this.accountRepository.getPartners(
|
||||
const partnersRecords = await this.accountRepository.getPartners(
|
||||
accountId,
|
||||
limit,
|
||||
offset,
|
||||
);
|
||||
|
||||
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
|
||||
let externalIds = partners.partnersInfo.map(
|
||||
let externalIds = partnersRecords.partnersInfo.map(
|
||||
(x) => x.primaryAccountExternalId,
|
||||
);
|
||||
externalIds = externalIds.filter((item) => item !== undefined);
|
||||
const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
|
||||
|
||||
// DBから取得した情報とADB2Cから取得した情報をマージ
|
||||
const response = partners.partnersInfo.map((db) => {
|
||||
const partners = partnersRecords.partnersInfo.map((db): Partner => {
|
||||
const adb2cUser = adb2cUsers.find(
|
||||
(adb2c) => db.primaryAccountExternalId === adb2c.id,
|
||||
);
|
||||
|
||||
let primaryAdmin = undefined;
|
||||
let mail = undefined;
|
||||
if (adb2cUser) {
|
||||
primaryAdmin = adb2cUser.displayName;
|
||||
mail = adb2cUser.identities.find(
|
||||
(identity) =>
|
||||
identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
).issuerAssignedId;
|
||||
if (!adb2cUser) {
|
||||
throw new Error(
|
||||
`adb2c user not found. externalId: ${db.primaryAccountExternalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const primaryAdmin = adb2cUser.displayName;
|
||||
const mail = adb2cUser.identities?.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
)?.issuerAssignedId;
|
||||
if (!mail) {
|
||||
throw new Error(
|
||||
`adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
name: db.name,
|
||||
tier: db.tier,
|
||||
@ -1665,17 +1770,15 @@ export class AccountsService {
|
||||
});
|
||||
|
||||
return {
|
||||
total: partners.total,
|
||||
partners: response,
|
||||
total: partnersRecords.total,
|
||||
partners: partners,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`);
|
||||
}
|
||||
@ -1843,4 +1946,61 @@ export class AccountsService {
|
||||
`[OUT] [${context.trackingId}] ${this.deleteAccountAndData.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* IDトークンのsubからアカウントの階層情報を取得します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @returns account info minimal access
|
||||
*/
|
||||
async getAccountInfoMinimalAccess(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
): Promise<number> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name} | params: { externalId: ${externalId} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { account } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`Account not found. externalId: ${externalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return account.tier;
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.trackingId}] error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case UserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AccountNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010501'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export type LicensesRepositoryMockValue = {
|
||||
orderHistories: LicenseOrder[];
|
||||
}
|
||||
| Error;
|
||||
issueLicense: undefined | Error;
|
||||
issueLicense: void | Error;
|
||||
};
|
||||
export type UsersRepositoryMockValue = {
|
||||
findUserById: User | Error;
|
||||
@ -61,10 +61,12 @@ export type ConfigMockValue = {
|
||||
get: string | Error;
|
||||
};
|
||||
export type AccountsRepositoryMockValue = {
|
||||
getLicenseSummaryInfo: {
|
||||
licenseSummary: LicenseSummaryInfo;
|
||||
isStorageAvailable: boolean;
|
||||
};
|
||||
getLicenseSummaryInfo:
|
||||
| {
|
||||
licenseSummary: LicenseSummaryInfo;
|
||||
isStorageAvailable: boolean;
|
||||
}
|
||||
| Error;
|
||||
createAccount: { newAccount: Account; adminUser: User } | Error;
|
||||
};
|
||||
|
||||
@ -181,18 +183,7 @@ export const makeLicensesRepositoryMock = (
|
||||
issueLicense:
|
||||
issueLicense instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(issueLicense)
|
||||
: jest
|
||||
.fn<
|
||||
Promise<{
|
||||
context: Context;
|
||||
orderedAccountId: number;
|
||||
myAccountId: number;
|
||||
tier: number;
|
||||
poNumber: string;
|
||||
}>,
|
||||
[]
|
||||
>()
|
||||
.mockResolvedValue(issueLicense),
|
||||
: jest.fn<Promise<void>, []>().mockResolvedValue(issueLicense),
|
||||
};
|
||||
};
|
||||
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
|
||||
@ -355,7 +346,7 @@ export const makeDefaultAccountsRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
return {
|
||||
getLicenseSummaryInfo: {
|
||||
licenseSummary: licenseSummaryInfo,
|
||||
@ -385,7 +376,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
|
||||
const typists: User[] = [];
|
||||
typists.push(
|
||||
@ -434,7 +425,7 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
|
||||
return {
|
||||
getUserGroups: [
|
||||
@ -444,6 +435,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
name: 'GroupA',
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -451,6 +446,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
name: 'GroupB',
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -23,14 +23,14 @@ export const getSortCriteria = async (dataSource: DataSource) => {
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -54,7 +54,7 @@ export const createLicense = async (
|
||||
export const createLicenseSetExpiryDateAndStatus = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
expiryDate: Date,
|
||||
expiryDate: Date | null,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
@ -171,19 +171,21 @@ export const createOptionItems = async (
|
||||
datasource: DataSource,
|
||||
worktypeId: number,
|
||||
): Promise<OptionItem[]> => {
|
||||
const optionItems = [];
|
||||
const optionItems: OptionItem[] = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
optionItems.push({
|
||||
worktype_id: worktypeId,
|
||||
item_label: '',
|
||||
default_value_type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initial_value: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const optionItem = new OptionItem();
|
||||
{
|
||||
optionItem.worktype_id = worktypeId;
|
||||
optionItem.item_label = '';
|
||||
optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT;
|
||||
optionItem.initial_value = '';
|
||||
optionItem.created_by = 'test_runner';
|
||||
optionItem.created_at = new Date();
|
||||
optionItem.updated_by = 'updater';
|
||||
optionItem.updated_at = new Date();
|
||||
}
|
||||
optionItems.push(optionItem);
|
||||
}
|
||||
|
||||
await datasource.getRepository(OptionItem).insert(optionItems);
|
||||
|
||||
@ -327,8 +327,8 @@ export class GetOrderHistoriesRequest {
|
||||
export class LicenseOrder {
|
||||
@ApiProperty({ description: '注文日付' })
|
||||
orderDate: string;
|
||||
@ApiProperty({ description: '発行日付' })
|
||||
issueDate: string;
|
||||
@ApiProperty({ description: '発行日付', required: false })
|
||||
issueDate?: string;
|
||||
@ApiProperty({ description: '注文数' })
|
||||
numberOfOrder: number;
|
||||
@ApiProperty({ description: 'POナンバー' })
|
||||
@ -497,6 +497,16 @@ export class UpdateWorktypeRequestParam {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class DeleteWorktypeRequestParam {
|
||||
@ApiProperty({ description: 'Worktypeの内部ID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class DeleteWorktypeResponse {}
|
||||
|
||||
export class PostActiveWorktypeRequest {
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
|
||||
@ -5,12 +5,19 @@ import {
|
||||
makeAdB2cServiceMock,
|
||||
makeDefaultAdB2cMockValue,
|
||||
} from './test/auth.service.mock';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
|
||||
@ -67,6 +67,18 @@ export class AuthController {
|
||||
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
// 同意済み利用規約バージョンが最新かチェック
|
||||
const isAcceptedLatestVersion =
|
||||
await this.authService.isAcceptedLatestVersion(context, idToken);
|
||||
|
||||
// 最新でなければエラー
|
||||
if (!isAcceptedLatestVersion) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010209'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const refreshToken = await this.authService.generateRefreshToken(
|
||||
context,
|
||||
idToken,
|
||||
|
||||
@ -4,8 +4,14 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module';
|
||||
@Module({
|
||||
imports: [ConfigModule, AdB2cModule, UsersRepositoryModule],
|
||||
imports: [
|
||||
ConfigModule,
|
||||
AdB2cModule,
|
||||
UsersRepositoryModule,
|
||||
TermsRepositoryModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
|
||||
@ -3,13 +3,22 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import {
|
||||
makeAuthServiceMock,
|
||||
makeDefaultAdB2cMockValue,
|
||||
makeDefaultConfigValue,
|
||||
makeDefaultGetPublicKeyFromJwk,
|
||||
} from './test/auth.service.mock';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
import { makeTestAccount } from '../../common/test/utility';
|
||||
import { AuthService } from './auth.service';
|
||||
import { createTermInfo } from './test/utility';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
describe('AuthService', () => {
|
||||
it('IDトークンの検証とペイロードの取得に成功する', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -20,7 +29,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token = 'invalid.id.token';
|
||||
|
||||
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
|
||||
@ -30,7 +40,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -43,7 +54,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -56,7 +68,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
|
||||
|
||||
@ -67,7 +80,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -80,8 +94,9 @@ describe('AuthService', () => {
|
||||
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(メタデータ)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getMetaData = new Error('failed get metadata');
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -94,8 +109,9 @@ describe('AuthService', () => {
|
||||
});
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(キーセット)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getSignKeySets = new Error('failed get keyset');
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -109,10 +125,11 @@ describe('AuthService', () => {
|
||||
|
||||
it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getSignKeySets = [
|
||||
{ kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
|
||||
];
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -125,6 +142,140 @@ describe('AuthService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkIsAcceptedLatestVersion', () => {
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const idToken = {
|
||||
emails: [],
|
||||
sub: admin.external_id,
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
};
|
||||
|
||||
await createTermInfo(source, 'EULA', '1.0');
|
||||
await createTermInfo(source, 'DPA', '1.0');
|
||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const idToken = {
|
||||
emails: [],
|
||||
sub: admin.external_id,
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
};
|
||||
|
||||
await createTermInfo(source, 'EULA', '1.0');
|
||||
await createTermInfo(source, 'DPA', '1.0');
|
||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const idToken = {
|
||||
emails: [],
|
||||
sub: admin.external_id,
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
};
|
||||
|
||||
await createTermInfo(source, 'EULA', '1.1');
|
||||
await createTermInfo(source, 'DPA', '1.0');
|
||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('同意済み利用規約(EULA)バージョンが最新でないときにチェックが通らないこと(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const idToken = {
|
||||
emails: [],
|
||||
sub: admin.external_id,
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
};
|
||||
|
||||
await createTermInfo(source, 'EULA', '1.1');
|
||||
await createTermInfo(source, 'DPA', '1.0');
|
||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョン(DPA)が最新でないときにチェックが通らないこと(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const idToken = {
|
||||
emails: [],
|
||||
sub: admin.external_id,
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
};
|
||||
|
||||
await createTermInfo(source, 'EULA', '1.0');
|
||||
await createTermInfo(source, 'DPA', '1.1');
|
||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
const idTokenPayload = {
|
||||
exp: 9000000000,
|
||||
nbf: 1000000000,
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
RefreshToken,
|
||||
isIDToken,
|
||||
} from '../../common/token';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { ADMIN_ROLES, TIERS, USER_ROLES } from '../../constants';
|
||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
@ -25,6 +25,13 @@ import { Context } from '../../common/log';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly refreshTokenLifetimeWeb =
|
||||
this.configService.getOrThrow<number>('REFRESH_TOKEN_LIFETIME_WEB');
|
||||
private readonly refreshTokenLifetimeDefault =
|
||||
this.configService.getOrThrow<number>('REFRESH_TOKEN_LIFETIME_DEFAULT');
|
||||
private readonly accessTokenlifetime = this.configService.getOrThrow<number>(
|
||||
'ACCESS_TOKEN_LIFETIME_WEB',
|
||||
);
|
||||
constructor(
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly configService: ConfigService,
|
||||
@ -68,10 +75,7 @@ export class AuthService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
const lifetimeWeb = this.configService.get('REFRESH_TOKEN_LIFETIME_WEB');
|
||||
const lifetimeDefault = this.configService.get(
|
||||
'REFRESH_TOKEN_LIFETIME_DEFAULT',
|
||||
);
|
||||
|
||||
let user: User;
|
||||
// ユーザー情報とユーザーが属しているアカウント情報を取得
|
||||
try {
|
||||
@ -106,7 +110,10 @@ export class AuthService {
|
||||
);
|
||||
}
|
||||
// 要求された環境用トークンの寿命を決定
|
||||
const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault;
|
||||
const refreshTokenLifetime =
|
||||
type === 'web'
|
||||
? this.refreshTokenLifetimeWeb
|
||||
: this.refreshTokenLifetimeDefault;
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
// ユーザーのロールを設定
|
||||
@ -165,7 +172,6 @@ export class AuthService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateAccessToken.name}`,
|
||||
);
|
||||
const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB');
|
||||
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
@ -188,7 +194,7 @@ export class AuthService {
|
||||
tier: token.tier,
|
||||
userId: token.userId,
|
||||
},
|
||||
lifetime,
|
||||
this.accessTokenlifetime,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
@ -205,11 +211,14 @@ export class AuthService {
|
||||
async getVerifiedIdToken(token: string): Promise<IDToken> {
|
||||
this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`);
|
||||
|
||||
let kid = '';
|
||||
let kid: string | undefined = '';
|
||||
try {
|
||||
// JWTトークンのヘッダを見るため一度デコードする
|
||||
const decodedToken = jwt.decode(token, { complete: true });
|
||||
kid = decodedToken.header.kid;
|
||||
kid = decodedToken?.header.kid;
|
||||
if (!kid) {
|
||||
throw new Error('kid not found');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw new HttpException(
|
||||
@ -331,4 +340,59 @@ export class AuthService {
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 同意済み利用規約バージョンが最新かチェック
|
||||
* @param idToken AzureAD B2Cにより発行されたIDトークン
|
||||
* @returns boolean
|
||||
*/
|
||||
async isAcceptedLatestVersion(
|
||||
context: Context,
|
||||
idToken: IDToken,
|
||||
): Promise<boolean> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` +
|
||||
`idToken.sub: ${idToken.sub}, };`,
|
||||
);
|
||||
|
||||
try {
|
||||
// DBからユーザーの同意済み利用規約バージョンと最新バージョンを取得
|
||||
const {
|
||||
acceptedEulaVersion,
|
||||
latestEulaVersion,
|
||||
acceptedDpaVersion,
|
||||
latestDpaVersion,
|
||||
tier,
|
||||
} = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub);
|
||||
|
||||
// 第五階層はEULAのみ判定
|
||||
if (tier === TIERS.TIER5) {
|
||||
if (!acceptedEulaVersion) {
|
||||
return false;
|
||||
}
|
||||
// 最新バージョンに同意済みか判定
|
||||
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
||||
return eulaAccepted;
|
||||
} else {
|
||||
// 第一~第四階層はEULA、DPAを判定
|
||||
if (!acceptedEulaVersion || !acceptedDpaVersion) {
|
||||
return false;
|
||||
}
|
||||
// 最新バージョンに同意済みか判定
|
||||
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
||||
const dpaAccepted = acceptedDpaVersion === latestDpaVersion;
|
||||
return eulaAccepted && dpaAccepted;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.trackingId}] error=${e}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.isAcceptedLatestVersion.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,9 +9,13 @@ export type AdB2cMockValue = {
|
||||
getMetaData: B2cMetadata | Error;
|
||||
getSignKeySets: JwkSignKey[] | Error;
|
||||
};
|
||||
export type ConfigMockValue = {
|
||||
getOrThrow: number;
|
||||
};
|
||||
|
||||
export const makeAuthServiceMock = async (
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
configMockValue: ConfigMockValue,
|
||||
): Promise<AuthService> => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
@ -21,7 +25,7 @@ export const makeAuthServiceMock = async (
|
||||
case AdB2cService:
|
||||
return makeAdB2cServiceMock(adB2cMockValue);
|
||||
case ConfigService:
|
||||
return {};
|
||||
return makeConfigMock(configMockValue);
|
||||
case UsersRepositoryService:
|
||||
return {};
|
||||
}
|
||||
@ -80,3 +84,16 @@ export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => {
|
||||
'-----END PUBLIC KEY-----',
|
||||
].join('\n');
|
||||
};
|
||||
export const makeConfigMock = (value: ConfigMockValue) => {
|
||||
const { getOrThrow } = value;
|
||||
|
||||
return {
|
||||
getOrThrow: jest.fn<Promise<number>, []>().mockResolvedValue(getOrThrow),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultConfigValue = (): ConfigMockValue => {
|
||||
return {
|
||||
getOrThrow: 80000,
|
||||
};
|
||||
};
|
||||
|
||||
18
dictation_server/src/features/auth/test/utility.ts
Normal file
18
dictation_server/src/features/auth/test/utility.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Term } from '../../../repositories/terms/entity/term.entity';
|
||||
|
||||
export const createTermInfo = async (
|
||||
datasource: DataSource,
|
||||
documentType: string,
|
||||
version: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(Term).insert({
|
||||
document_type: documentType,
|
||||
version: version,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
identifiers.pop() as Term;
|
||||
};
|
||||
@ -17,3 +17,11 @@ export class AccessTokenResponse {
|
||||
accessToken: string;
|
||||
}
|
||||
export class AccessTokenRequest {}
|
||||
|
||||
export type TermsCheckInfo = {
|
||||
tier: number;
|
||||
acceptedEulaVersion?: string;
|
||||
acceptedDpaVersion?: string;
|
||||
latestEulaVersion: string;
|
||||
latestDpaVersion: string;
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Query,
|
||||
@ -37,6 +38,7 @@ import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { Request } from 'express';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('files')
|
||||
@Controller('files')
|
||||
@ -70,16 +72,28 @@ export class FilesController {
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR] }))
|
||||
@Post('audio/upload-finished')
|
||||
async uploadFinished(
|
||||
@Req() req: Request,
|
||||
@Body() body: AudioUploadFinishedRequest,
|
||||
): Promise<AudioUploadFinishedResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const {
|
||||
url,
|
||||
@ -100,7 +114,7 @@ export class FilesController {
|
||||
|
||||
const res = await this.filesService.uploadFinished(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
url,
|
||||
authorId,
|
||||
fileName,
|
||||
@ -143,7 +157,6 @@ export class FilesController {
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR] }))
|
||||
async uploadLocation(
|
||||
@Req() req: Request,
|
||||
// クエリパラメータ AudioUploadLocationRequest は空であるため内部で使用しない。
|
||||
@ -151,10 +164,23 @@ export class FilesController {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Query() _query: AudioUploadLocationRequest,
|
||||
): Promise<AudioUploadLocationResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishUploadSas(context, accessToken);
|
||||
return { url };
|
||||
@ -197,14 +223,27 @@ export class FilesController {
|
||||
): Promise<AudioDownloadLocationResponse> {
|
||||
const { audioFileId } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishAudioFileDownloadSas(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
audioFileId,
|
||||
);
|
||||
|
||||
@ -248,14 +287,27 @@ export class FilesController {
|
||||
): Promise<TemplateDownloadLocationResponse> {
|
||||
const { audioFileId } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishTemplateFileDownloadSas(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
audioFileId,
|
||||
);
|
||||
|
||||
@ -289,8 +341,21 @@ export class FilesController {
|
||||
async uploadTemplateLocation(
|
||||
@Req() req: Request,
|
||||
): Promise<TemplateUploadLocationResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -335,8 +400,21 @@ export class FilesController {
|
||||
@Body() body: TemplateUploadFinishedRequest,
|
||||
): Promise<TemplateUploadFinishedReqponse> {
|
||||
const { name, url } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
await this.filesService.templateUploadFinished(context, userId, url, name);
|
||||
|
||||
@ -35,11 +35,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
await service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
|
||||
@ -57,11 +56,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
await service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
|
||||
@ -78,11 +76,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
);
|
||||
@ -102,11 +99,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
blobParam.publishUploadSas = new Error('Azure service down');
|
||||
|
||||
await expect(
|
||||
service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
);
|
||||
@ -295,7 +291,7 @@ describe('タスク作成', () => {
|
||||
});
|
||||
|
||||
describe('音声ファイルダウンロードURL取得', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -308,11 +304,13 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
@ -333,7 +331,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -341,6 +339,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
@ -353,6 +352,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -382,6 +382,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -396,6 +397,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -429,6 +431,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -443,6 +446,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -467,6 +471,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -481,6 +486,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -492,6 +498,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -506,6 +513,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
@ -526,7 +534,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -534,6 +542,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -549,7 +558,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -562,11 +571,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
@ -586,7 +597,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -594,6 +605,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
@ -606,6 +618,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -629,6 +642,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -643,6 +657,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -672,6 +687,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -686,6 +702,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -710,6 +727,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -724,6 +742,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -735,6 +754,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -749,6 +769,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
@ -768,7 +789,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -776,6 +797,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -791,7 +813,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
describe('publishTemplateFileUploadSas', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -804,12 +826,15 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -832,7 +857,9 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -858,7 +885,9 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -887,7 +916,7 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
describe('templateUploadFinished', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -900,12 +929,15 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('アップロード完了後のテンプレートファイル情報をDBに保存できる(新規追加)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -937,7 +969,9 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
it('アップロード完了後のテンプレートファイル情報をDBに保存できる(更新)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -975,7 +1009,9 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
it('DBへの保存に失敗した場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
} from '../../repositories/tasks/errors/types';
|
||||
import { Context } from '../../common/log';
|
||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
@ -206,7 +207,7 @@ export class FilesService {
|
||||
*/
|
||||
async publishUploadSas(
|
||||
context: Context,
|
||||
token: AccessToken,
|
||||
externalId: string,
|
||||
): Promise<string> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
|
||||
@ -216,10 +217,11 @@ export class FilesService {
|
||||
let accountId: number;
|
||||
let country: string;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(
|
||||
token.userId,
|
||||
);
|
||||
accountId = user.account.id;
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account_id;
|
||||
country = user.account.country;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
@ -291,14 +293,17 @@ export class FilesService {
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
let authorId: string | undefined;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account.id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
authorId = user.author_id ?? undefined;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
|
||||
@ -321,7 +326,7 @@ export class FilesService {
|
||||
accountId,
|
||||
status,
|
||||
);
|
||||
const file = task.file;
|
||||
const { file } = task;
|
||||
|
||||
// タスクに紐づく音声ファイルだけが消される場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
@ -332,9 +337,9 @@ export class FilesService {
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
if (!isTypist && file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -425,14 +430,17 @@ export class FilesService {
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
let authorId: string | undefined;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
accountId = user.account.id;
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account_id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
authorId = user.author_id ?? undefined;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.log(
|
||||
@ -454,6 +462,15 @@ export class FilesService {
|
||||
accountId,
|
||||
status,
|
||||
);
|
||||
const { file } = task;
|
||||
|
||||
// タスクに紐づく音声ファイルだけが消される場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
if (!file) {
|
||||
throw new AudioFileNotFoundError(
|
||||
`Audio file is not exists in DB. audio_file_id:${audioFileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const template_file = task.template_file;
|
||||
|
||||
@ -466,9 +483,9 @@ export class FilesService {
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
if (!isTypist && file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -515,6 +532,7 @@ export class FilesService {
|
||||
makeErrorResponse('E010603'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AudioFileNotFoundError:
|
||||
case TemplateFileNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010701'),
|
||||
@ -552,15 +570,18 @@ export class FilesService {
|
||||
`[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`,
|
||||
);
|
||||
try {
|
||||
const {
|
||||
account: { id: accountId, country },
|
||||
} = await this.usersRepository.findUserByExternalId(externalId);
|
||||
const { account } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
|
||||
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
|
||||
const isContainerExists = await this.blobStorageService.containerExists(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
account.id,
|
||||
account.country,
|
||||
);
|
||||
if (!isContainerExists) {
|
||||
throw new Error('container not found.');
|
||||
@ -569,8 +590,8 @@ export class FilesService {
|
||||
// SASトークン発行
|
||||
const url = await this.blobStorageService.publishTemplateUploadSas(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
account.id,
|
||||
account.country,
|
||||
);
|
||||
|
||||
return url;
|
||||
|
||||
@ -134,12 +134,15 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
created_by: 'test',
|
||||
created_at: new Date(),
|
||||
updated_by: null,
|
||||
updated_at: null,
|
||||
updated_at: new Date(),
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
account: {
|
||||
id: 2,
|
||||
parent_account_id: 2,
|
||||
@ -154,7 +157,10 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
created_by: '',
|
||||
created_at: new Date(),
|
||||
updated_by: '',
|
||||
updated_at: null,
|
||||
updated_at: new Date(),
|
||||
active_worktype_id: null,
|
||||
secondary_admin_user_id: null,
|
||||
user: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -172,6 +178,14 @@ export const makeDefaultTasksRepositoryMockValue =
|
||||
status: 'Uploaded',
|
||||
priority: '01',
|
||||
created_at: new Date(),
|
||||
finished_at: null,
|
||||
started_at: null,
|
||||
template_file_id: null,
|
||||
typist_user_id: null,
|
||||
file: null,
|
||||
option_items: null,
|
||||
template_file: null,
|
||||
typist_user: null,
|
||||
},
|
||||
getTasksFromAccountId: {
|
||||
tasks: [],
|
||||
|
||||
@ -98,7 +98,7 @@ export const createTask = async (
|
||||
export const makeTestingModuleWithBlob = async (
|
||||
datasource: DataSource,
|
||||
blobStorageService: BlobstorageServiceMockValue,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
@ -34,6 +35,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('licenses')
|
||||
@Controller('licenses')
|
||||
@ -73,12 +75,25 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CreateOrdersRequest,
|
||||
): Promise<CreateOrdersResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
// ライセンス注文処理
|
||||
await this.licensesService.licenseOrders(
|
||||
payload,
|
||||
userId,
|
||||
body.poNumber,
|
||||
body.orderCount,
|
||||
);
|
||||
@ -111,11 +126,24 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: IssueCardLicensesRequest,
|
||||
): Promise<IssueCardLicensesResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
|
||||
payload.userId,
|
||||
userId,
|
||||
body.createCount,
|
||||
);
|
||||
|
||||
@ -154,11 +182,24 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: ActivateCardLicensesRequest,
|
||||
): Promise<ActivateCardLicensesResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
await this.licensesService.activateCardLicenseKey(
|
||||
payload.userId,
|
||||
userId,
|
||||
body.cardLicenseKey,
|
||||
);
|
||||
|
||||
@ -194,16 +235,26 @@ export class LicensesController {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Req() req: Request,
|
||||
): Promise<GetAllocatableLicensesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const allocatableLicenses =
|
||||
await this.licensesService.getAllocatableLicenses(
|
||||
context,
|
||||
payload.userId,
|
||||
);
|
||||
await this.licensesService.getAllocatableLicenses(context, userId);
|
||||
|
||||
return allocatableLicenses;
|
||||
}
|
||||
@ -245,16 +296,25 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelOrderRequest,
|
||||
): Promise<CancelOrderResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.licensesService.cancelOrder(
|
||||
context,
|
||||
payload.userId,
|
||||
body.poNumber,
|
||||
);
|
||||
await this.licensesService.cancelOrder(context, userId, body.poNumber);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,11 +56,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
expect(
|
||||
await service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
await service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('ユーザID取得できなかった場合、エラーとなる', async () => {
|
||||
@ -78,11 +78,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '', role: '', tier: 5 };
|
||||
const userId = '';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -105,11 +105,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -130,11 +130,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E010401'),
|
||||
@ -154,7 +154,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new IssueCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.createCount = 10;
|
||||
const issueCardLicensesResponse: IssueCardLicensesResponse = {
|
||||
cardLicenseKeys: [
|
||||
@ -171,7 +171,7 @@ describe('LicensesService', () => {
|
||||
],
|
||||
};
|
||||
expect(
|
||||
await service.issueCardLicenseKeys(token.userId, body.createCount),
|
||||
await service.issueCardLicenseKeys(userId, body.createCount),
|
||||
).toEqual(issueCardLicensesResponse);
|
||||
});
|
||||
it('カードライセンス発行に失敗した場合、エラーになる', async () => {
|
||||
@ -187,10 +187,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new IssueCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.createCount = 1000;
|
||||
await expect(
|
||||
service.issueCardLicenseKeys(token.userId, body.createCount),
|
||||
service.issueCardLicenseKeys(userId, body.createCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -210,10 +210,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
expect(
|
||||
await service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
await service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('カードライセンス取り込みに失敗した場合、エラーになる(DBエラー)', async () => {
|
||||
@ -229,10 +229,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -254,10 +254,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
@ -276,10 +276,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
@ -287,7 +287,7 @@ describe('LicensesService', () => {
|
||||
});
|
||||
|
||||
describe('DBテスト', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -300,12 +300,15 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
@ -323,7 +326,9 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
it('カードライセンス取り込みが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
@ -362,13 +367,15 @@ describe('DBテスト', () => {
|
||||
);
|
||||
const dbSelectResultFromLicense = await selectLicense(source, license_id);
|
||||
expect(
|
||||
dbSelectResultFromCardLicense.cardLicense.activated_at,
|
||||
dbSelectResultFromCardLicense?.cardLicense?.activated_at,
|
||||
).toBeDefined();
|
||||
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId);
|
||||
expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId);
|
||||
});
|
||||
|
||||
it('取込可能なライセンスのみが取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const now = new Date();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
@ -513,7 +520,7 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス割り当て', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -526,12 +533,15 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -567,11 +577,11 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
await service.allocateLicense(makeContext('trackingId'), userId, 1);
|
||||
const resultLicense = await selectLicense(source, 1);
|
||||
expect(resultLicense.license.allocated_user_id).toBe(userId);
|
||||
expect(resultLicense.license.status).toBe(
|
||||
expect(resultLicense.license?.allocated_user_id).toBe(userId);
|
||||
expect(resultLicense.license?.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
);
|
||||
expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual(
|
||||
expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual(
|
||||
expiry_date.setMilliseconds(0),
|
||||
);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -579,22 +589,24 @@ describe('ライセンス割り当て', () => {
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
true,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
});
|
||||
|
||||
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -630,30 +642,32 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
await service.allocateLicense(makeContext('trackingId'), userId, 1);
|
||||
const result = await selectLicense(source, 1);
|
||||
expect(result.license.allocated_user_id).toBe(userId);
|
||||
expect(result.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result.license.expiry_date).toEqual(date);
|
||||
expect(result.license?.allocated_user_id).toBe(userId);
|
||||
expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result.license?.expiry_date).toEqual(date);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
true,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
});
|
||||
|
||||
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -705,32 +719,32 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
// もともと割り当てられていたライセンスの状態確認
|
||||
const result1 = await selectLicense(source, 1);
|
||||
expect(result1.license.allocated_user_id).toBe(null);
|
||||
expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
|
||||
expect(result1.license.expiry_date).toEqual(date);
|
||||
expect(result1.license?.allocated_user_id).toBe(null);
|
||||
expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
|
||||
expect(result1.license?.expiry_date).toEqual(date);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
false,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(false);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
|
||||
// 新たに割り当てたライセンスの状態確認
|
||||
const result2 = await selectLicense(source, 2);
|
||||
expect(result2.license.allocated_user_id).toBe(userId);
|
||||
expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual(
|
||||
expect(result2.license?.allocated_user_id).toBe(userId);
|
||||
expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual(
|
||||
expiry_date.setMilliseconds(0),
|
||||
);
|
||||
const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -738,22 +752,24 @@ describe('ライセンス割り当て', () => {
|
||||
userId,
|
||||
2,
|
||||
);
|
||||
expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.license_id,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.license_id,
|
||||
).toBe(2);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.is_allocated,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.account_id,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.account_id,
|
||||
).toBe(accountId);
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がNORMALのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -806,12 +822,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('NONE');
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がCARDのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -864,12 +882,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('CARD');
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がTRIALのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -922,12 +942,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('TRIAL');
|
||||
});
|
||||
|
||||
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -961,7 +983,9 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1013,7 +1037,7 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス割り当て解除', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1026,12 +1050,15 @@ describe('ライセンス割り当て解除', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ライセンスの割り当て解除が完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1068,11 +1095,11 @@ describe('ライセンス割り当て解除', () => {
|
||||
|
||||
// 割り当て解除したライセンスの状態確認
|
||||
const deallocatedLicense = await selectLicense(source, 1);
|
||||
expect(deallocatedLicense.license.allocated_user_id).toBe(null);
|
||||
expect(deallocatedLicense.license.status).toBe(
|
||||
expect(deallocatedLicense.license?.allocated_user_id).toBe(null);
|
||||
expect(deallocatedLicense.license?.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
);
|
||||
expect(deallocatedLicense.license.expiry_date).toEqual(date);
|
||||
expect(deallocatedLicense.license?.expiry_date).toEqual(date);
|
||||
|
||||
// ライセンス履歴テーブルの状態確認
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -1080,25 +1107,27 @@ describe('ライセンス割り当て解除', () => {
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
false,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(false);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('NONE');
|
||||
});
|
||||
|
||||
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1158,7 +1187,7 @@ describe('ライセンス割り当て解除', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス注文キャンセル', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1171,12 +1200,15 @@ describe('ライセンス注文キャンセル', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ライセンス注文のキャンセルが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
@ -1185,7 +1217,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Issue Requesting',
|
||||
@ -1195,7 +1227,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
@ -1214,12 +1246,14 @@ describe('ライセンス注文キャンセル', () => {
|
||||
tier2Accounts[0].account.id,
|
||||
poNumber,
|
||||
);
|
||||
expect(orderRecord.orderLicense.canceled_at).toBeDefined();
|
||||
expect(orderRecord.orderLicense.status).toBe('Order Canceled');
|
||||
expect(orderRecord.orderLicense?.canceled_at).toBeDefined();
|
||||
expect(orderRecord.orderLicense?.status).toBe('Order Canceled');
|
||||
});
|
||||
|
||||
it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
@ -1228,7 +1262,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Issued',
|
||||
@ -1247,7 +1281,9 @@ describe('ライセンス注文キャンセル', () => {
|
||||
});
|
||||
|
||||
it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
@ -1257,7 +1293,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
|
||||
@ -33,20 +33,20 @@ export class LicensesService {
|
||||
* @param body
|
||||
*/
|
||||
async licenseOrders(
|
||||
accessToken: AccessToken,
|
||||
externalId: string,
|
||||
poNumber: string,
|
||||
orderCount: number,
|
||||
): Promise<void> {
|
||||
//アクセストークンからユーザーIDを取得する
|
||||
this.logger.log(`[IN] ${this.licenseOrders.name}`);
|
||||
const userId = accessToken.userId;
|
||||
let myAccountId: number;
|
||||
let parentAccountId: number;
|
||||
let parentAccountId: number | undefined;
|
||||
|
||||
// ユーザIDからアカウントIDを取得する
|
||||
try {
|
||||
myAccountId = (await this.usersRepository.findUserByExternalId(userId))
|
||||
.account_id;
|
||||
myAccountId = (
|
||||
await this.usersRepository.findUserByExternalId(externalId)
|
||||
).account_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
@ -65,9 +65,13 @@ export class LicensesService {
|
||||
|
||||
// 親アカウントIDを取得
|
||||
try {
|
||||
parentAccountId = (
|
||||
await this.accountsRepository.findAccountById(myAccountId)
|
||||
).parent_account_id;
|
||||
parentAccountId =
|
||||
(await this.accountsRepository.findAccountById(myAccountId))
|
||||
.parent_account_id ?? undefined;
|
||||
// 親アカウントIDが取得できない場合はエラー
|
||||
if (parentAccountId === undefined) {
|
||||
throw new Error('parent account id is undefined');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
|
||||
@ -124,11 +124,11 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user1.notification = false;
|
||||
user1.encryption = false;
|
||||
user1.prompt = false;
|
||||
user1.deleted_at = undefined;
|
||||
user1.deleted_at = null;
|
||||
user1.created_by = 'test';
|
||||
user1.created_at = new Date();
|
||||
user1.updated_by = undefined;
|
||||
user1.updated_at = undefined;
|
||||
user1.updated_by = null;
|
||||
user1.updated_at = new Date();
|
||||
|
||||
return {
|
||||
findUserByExternalId: user1,
|
||||
|
||||
@ -12,14 +12,14 @@ import {
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -107,7 +107,7 @@ export const createOrder = async (
|
||||
poNumber: string,
|
||||
fromId: number,
|
||||
toId: number,
|
||||
issuedAt: Date,
|
||||
issuedAt: Date | null,
|
||||
quantity: number,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
@ -138,7 +138,7 @@ export const selectCardLicensesCount = async (
|
||||
export const selectCardLicense = async (
|
||||
datasource: DataSource,
|
||||
cardLicenseKey: string,
|
||||
): Promise<{ cardLicense: CardLicense }> => {
|
||||
): Promise<{ cardLicense: CardLicense | null }> => {
|
||||
const cardLicense = await datasource.getRepository(CardLicense).findOne({
|
||||
where: {
|
||||
card_license_key: cardLicenseKey,
|
||||
@ -150,7 +150,7 @@ export const selectCardLicense = async (
|
||||
export const selectLicense = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<{ license: License }> => {
|
||||
): Promise<{ license: License | null }> => {
|
||||
const license = await datasource.getRepository(License).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
@ -163,7 +163,7 @@ export const selectLicenseAllocationHistory = async (
|
||||
datasource: DataSource,
|
||||
userId: number,
|
||||
licence_id: number,
|
||||
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => {
|
||||
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
|
||||
const licenseAllocationHistory = await datasource
|
||||
.getRepository(LicenseAllocationHistory)
|
||||
.findOne({
|
||||
@ -182,7 +182,7 @@ export const selectOrderLicense = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
poNumber: string,
|
||||
): Promise<{ orderLicense: LicenseOrder }> => {
|
||||
): Promise<{ orderLicense: LicenseOrder | null }> => {
|
||||
const orderLicense = await datasource.getRepository(LicenseOrder).findOne({
|
||||
where: {
|
||||
from_account_id: accountId,
|
||||
|
||||
@ -47,8 +47,8 @@ export class GetAllocatableLicensesRequest {}
|
||||
export class AllocatableLicenseInfo {
|
||||
@ApiProperty()
|
||||
licenseId: number;
|
||||
@ApiProperty()
|
||||
expiryDate: Date;
|
||||
@ApiProperty({ required: false })
|
||||
expiryDate?: Date;
|
||||
}
|
||||
export class GetAllocatableLicensesResponse {
|
||||
@ApiProperty({ type: [AllocatableLicenseInfo] })
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
@ -21,6 +22,7 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('notification')
|
||||
@Controller('notification')
|
||||
@ -57,7 +59,20 @@ export class NotificationController {
|
||||
const { handler, pns } = body;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
|
||||
@ -77,14 +77,14 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user.external_id = 'external_id';
|
||||
user.account_id = 123;
|
||||
user.role = 'none';
|
||||
user.author_id = undefined;
|
||||
user.author_id = null;
|
||||
user.accepted_eula_version = '1.0';
|
||||
user.accepted_dpa_version = '1.0';
|
||||
user.email_verified = true;
|
||||
user.auto_renew = false;
|
||||
user.license_alert = false;
|
||||
user.notification = false;
|
||||
user.deleted_at = undefined;
|
||||
user.deleted_at = null;
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = 'test';
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
@ -32,6 +33,8 @@ import {
|
||||
TasksResponse,
|
||||
} from './types/types';
|
||||
import {
|
||||
SortDirection,
|
||||
TaskListSortableAttribute,
|
||||
isSortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
@ -43,6 +46,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { Roles } from '../../common/types/role';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('tasks')
|
||||
@Controller('tasks')
|
||||
@ -80,22 +84,38 @@ export class TasksController {
|
||||
@Req() req,
|
||||
@Query() body: TasksRequest,
|
||||
): Promise<TasksResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
const context = makeContext(decodedToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const { limit, offset, status } = body;
|
||||
const paramName = isTaskListSortableAttribute(body.paramName)
|
||||
? body.paramName
|
||||
const paramName = isTaskListSortableAttribute(body.paramName ?? '')
|
||||
? (body.paramName as TaskListSortableAttribute)
|
||||
: undefined;
|
||||
const direction = isSortDirection(body.direction)
|
||||
? body.direction
|
||||
const direction = isSortDirection(body.direction ?? '')
|
||||
? (body.direction as SortDirection)
|
||||
: undefined;
|
||||
|
||||
const { tasks, total } = await this.taskService.getTasks(
|
||||
context,
|
||||
decodedToken,
|
||||
userId,
|
||||
roles,
|
||||
offset,
|
||||
limit,
|
||||
// statusが指定されていない場合は全てのステータスを取得する
|
||||
@ -183,10 +203,22 @@ export class TasksController {
|
||||
@Param() param: ChangeStatusRequest,
|
||||
): Promise<ChangeStatusResponse> {
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { role, userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
@ -241,10 +273,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -296,10 +340,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId, role } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
@ -353,10 +409,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -491,11 +559,22 @@ export class TasksController {
|
||||
@Body() body: PostCheckoutPermissionRequest,
|
||||
): Promise<PostCheckoutPermissionResponse> {
|
||||
const { assignees } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
|
||||
const { role, userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -45,10 +45,10 @@ export class TasksService {
|
||||
private readonly notificationhubService: NotificationhubService,
|
||||
) {}
|
||||
|
||||
// TODO [Task2244] 引数にAccessTokenがあるのは不適切なのでController側で分解したい
|
||||
async getTasks(
|
||||
context: Context,
|
||||
accessToken: AccessToken,
|
||||
userId: string,
|
||||
roles: Roles[],
|
||||
offset: number,
|
||||
limit: number,
|
||||
status?: string[],
|
||||
@ -59,10 +59,6 @@ export class TasksService {
|
||||
`[IN] [${context.trackingId}] ${this.getTasks.name} | params: { offset: ${offset}, limit: ${limit}, status: ${status}, paramName: ${paramName}, direction: ${direction} };`,
|
||||
);
|
||||
|
||||
const { role, userId } = accessToken;
|
||||
// TODO [Task2244] Roleに型で定義されている値が入っているかをチェックして異常値を弾く実装に修正する
|
||||
const roles = role.split(' ');
|
||||
|
||||
// パラメータが省略された場合のデフォルト値: 保存するソート条件の値の初期値と揃える
|
||||
const defaultParamName: TaskListSortableAttribute = 'JOB_NUMBER';
|
||||
const defaultDirection: SortDirection = 'ASC';
|
||||
@ -95,6 +91,10 @@ export class TasksService {
|
||||
return { tasks: tasks, total: result.count };
|
||||
}
|
||||
if (roles.includes(USER_ROLES.AUTHOR)) {
|
||||
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (!author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
const result =
|
||||
await this.taskRepository.getTasksFromAuthorIdAndAccountId(
|
||||
author_id,
|
||||
@ -179,6 +179,10 @@ export class TasksService {
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
if (roles.includes(USER_ROLES.AUTHOR)) {
|
||||
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (!author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
await this.taskRepository.getTaskFromAudioFileId(
|
||||
audioFileId,
|
||||
account_id,
|
||||
@ -407,30 +411,21 @@ export class TasksService {
|
||||
permissions: CheckoutPermission[],
|
||||
): Promise<AdB2cUser[]> {
|
||||
// 割り当て候補の外部IDを列挙
|
||||
const assigneesExternalIds = permissions.map((x) => {
|
||||
if (x.user) {
|
||||
return x.user.external_id;
|
||||
}
|
||||
});
|
||||
const assigneesExternalIds = permissions.flatMap((permission) =>
|
||||
permission.user ? [permission.user.external_id] : [],
|
||||
);
|
||||
// 割り当てられているタイピストの外部IDを列挙
|
||||
const typistExternalIds = tasks.flatMap((x) => {
|
||||
if (x.typist_user) {
|
||||
return x.typist_user.external_id;
|
||||
}
|
||||
});
|
||||
const typistExternalIds = tasks.flatMap((task) =>
|
||||
task.typist_user ? [task.typist_user.external_id] : [],
|
||||
);
|
||||
|
||||
//重複をなくす
|
||||
const distinctedExternalIds = [
|
||||
...new Set(assigneesExternalIds.concat(typistExternalIds)),
|
||||
];
|
||||
|
||||
// undefinedがあった場合、取り除く
|
||||
const filteredExternalIds: string[] = distinctedExternalIds.filter(
|
||||
(x): x is string => x !== undefined,
|
||||
);
|
||||
|
||||
// B2Cからユーザー名を取得する
|
||||
return await this.adB2cService.getUsers(context, filteredExternalIds);
|
||||
return await this.adB2cService.getUsers(context, distinctedExternalIds);
|
||||
}
|
||||
/**
|
||||
* 文字起こし候補を変更する
|
||||
@ -451,10 +446,14 @@ export class TasksService {
|
||||
);
|
||||
const { author_id, account_id } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
// RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (role.includes(USER_ROLES.AUTHOR) && !author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
|
||||
await this.taskRepository.changeCheckoutPermission(
|
||||
audioFileId,
|
||||
author_id,
|
||||
author_id ?? undefined,
|
||||
account_id,
|
||||
role,
|
||||
assignees,
|
||||
@ -462,11 +461,16 @@ export class TasksService {
|
||||
|
||||
// すべての割り当て候補ユーザーを取得する
|
||||
const assigneesGroupIds = assignees
|
||||
.filter((x) => x.typistGroupId)
|
||||
.map((x) => x.typistGroupId);
|
||||
.filter((assignee) => assignee.typistGroupId)
|
||||
.flatMap((assignee) =>
|
||||
assignee.typistGroupId ? [assignee.typistGroupId] : [],
|
||||
);
|
||||
|
||||
const assigneesUserIds = assignees
|
||||
.filter((x) => x.typistUserId)
|
||||
.map((x) => x.typistUserId);
|
||||
.filter((assignee) => assignee.typistUserId)
|
||||
.flatMap((assignee) =>
|
||||
assignee.typistUserId ? [assignee.typistUserId] : [],
|
||||
);
|
||||
|
||||
const groupMembers =
|
||||
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
|
||||
|
||||
@ -263,6 +263,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -270,6 +275,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 2,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
@ -277,6 +287,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
@ -284,6 +299,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
@ -291,6 +311,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 3,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -307,7 +332,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user1.auto_renew = false;
|
||||
user1.license_alert = false;
|
||||
user1.notification = false;
|
||||
user1.deleted_at = undefined;
|
||||
user1.deleted_at = null;
|
||||
user1.created_by = 'test';
|
||||
user1.created_at = new Date();
|
||||
user1.author_id = 'abcdef';
|
||||
@ -331,66 +356,82 @@ const defaultTasksRepositoryMockValue: {
|
||||
status: 'Uploaded',
|
||||
priority: '00',
|
||||
created_at: new Date('2023-01-01T01:01:01.000Z'),
|
||||
finished_at: null,
|
||||
started_at: null,
|
||||
typist_user_id: null,
|
||||
template_file_id: null,
|
||||
typist_user: null,
|
||||
template_file: null,
|
||||
option_items: [
|
||||
{
|
||||
id: 1,
|
||||
audio_file_id: 1,
|
||||
label: 'label01',
|
||||
value: 'value01',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
audio_file_id: 1,
|
||||
label: 'label02',
|
||||
value: 'value02',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
audio_file_id: 1,
|
||||
label: 'label03',
|
||||
value: 'value03',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
audio_file_id: 1,
|
||||
label: 'label04',
|
||||
value: 'value04',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
audio_file_id: 1,
|
||||
label: 'label05',
|
||||
value: 'value05',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
audio_file_id: 1,
|
||||
label: 'label06',
|
||||
value: 'value06',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
audio_file_id: 1,
|
||||
label: 'label07',
|
||||
value: 'value07',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
audio_file_id: 1,
|
||||
label: 'label08',
|
||||
value: 'value08',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
audio_file_id: 1,
|
||||
label: 'label09',
|
||||
value: 'value09',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
audio_file_id: 1,
|
||||
label: 'label10',
|
||||
value: 'value10',
|
||||
task: null,
|
||||
},
|
||||
],
|
||||
file: {
|
||||
@ -410,6 +451,8 @@ const defaultTasksRepositoryMockValue: {
|
||||
audio_format: 'DS',
|
||||
comment: 'comment',
|
||||
is_encrypted: true,
|
||||
deleted_at: null,
|
||||
task: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -435,7 +478,16 @@ const defaultTasksRepositoryMockValue: {
|
||||
created_at: new Date(),
|
||||
updated_by: 'test',
|
||||
updated_at: new Date(),
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
task: null,
|
||||
user_group_id: null,
|
||||
user_group: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
|
||||
@ -39,10 +39,10 @@ import {
|
||||
makeNotificationhubServiceMock,
|
||||
} from './tasks.service.mock';
|
||||
|
||||
export const makeTaskTestingModule = async (
|
||||
export const makeTaskTestingModuleWithNotificaiton = async (
|
||||
datasource: DataSource,
|
||||
notificationhubServiceMockValue: NotificationhubServiceMockValue,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
@ -205,7 +205,7 @@ export const createUserGroup = async (
|
||||
export const getTask = async (
|
||||
datasource: DataSource,
|
||||
task_id: number,
|
||||
): Promise<Task> => {
|
||||
): Promise<Task | null> => {
|
||||
const task = await datasource.getRepository(Task).findOne({
|
||||
where: {
|
||||
id: task_id,
|
||||
|
||||
@ -45,7 +45,7 @@ const createTask = (
|
||||
const assignees = createAssignees(permissions, b2cUserInfo);
|
||||
|
||||
// RepositoryDTO => ControllerDTOに変換
|
||||
const typist: Typist =
|
||||
const typist: Typist | undefined =
|
||||
typist_user != null
|
||||
? convertUserToTypist(typist_user, b2cUserInfo)
|
||||
: undefined;
|
||||
@ -113,7 +113,10 @@ const convertUserToAssignee = (
|
||||
): Assignee => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
)?.displayName;
|
||||
if (!typistName) {
|
||||
throw new Error('typistName not found.');
|
||||
}
|
||||
return {
|
||||
typistUserId: user.id,
|
||||
typistName,
|
||||
@ -135,7 +138,10 @@ const convertUserToTypist = (
|
||||
): Typist => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
)?.displayName;
|
||||
if (!typistName) {
|
||||
throw new Error('typistName not found.');
|
||||
}
|
||||
return {
|
||||
id: user.id,
|
||||
name: typistName,
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
import { Controller, Get, HttpStatus, Req, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
@ -16,6 +23,7 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { Request } from 'express';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { TemplatesService } from './templates.service';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('templates')
|
||||
@Controller('templates')
|
||||
@ -46,8 +54,21 @@ export class TemplatesController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get()
|
||||
async getTemplates(@Req() req: Request): Promise<GetTemplatesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const templates = await this.templatesService.getTemplates(context, userId);
|
||||
|
||||
@ -9,7 +9,7 @@ import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
describe('getTemplates', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | undefined = undefined;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -22,12 +22,16 @@ describe('getTemplates', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
source = undefined;
|
||||
});
|
||||
|
||||
it('テンプレートファイル一覧を取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const service = module.get<TemplatesService>(TemplatesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -65,7 +69,10 @@ describe('getTemplates', () => {
|
||||
});
|
||||
|
||||
it('テンプレートファイル一覧を取得できる(0件)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const service = module.get<TemplatesService>(TemplatesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -80,7 +87,10 @@ describe('getTemplates', () => {
|
||||
});
|
||||
|
||||
it('テンプレートファイル一覧の取得に失敗した場合、エラーとなること', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const service = module.get<TemplatesService>(TemplatesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
@ -23,6 +23,9 @@ export const createTemplateFile = async (
|
||||
id: template.id,
|
||||
},
|
||||
});
|
||||
if (!templateFile) {
|
||||
fail();
|
||||
}
|
||||
|
||||
return templateFile;
|
||||
};
|
||||
|
||||
@ -6,11 +6,14 @@ describe('TermsController', () => {
|
||||
let controller: TermsController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockTermsService = {};
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [TermsController],
|
||||
providers: [TermsService],
|
||||
}).compile();
|
||||
|
||||
})
|
||||
.overrideProvider(TermsService)
|
||||
.useValue(mockTermsService)
|
||||
.compile();
|
||||
controller = module.get<TermsController>(TermsController);
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user