Merged PR 948: PH1エンハンス先行リリース対応
## 概要 [ユーザー ストーリー 4489: 【PH1エンハンス】Dictation Finishedになったファイルのステータスを変更したい](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation-2nd/_workitems/edit/4489) [ユーザー ストーリー 4491: 【PH1エンハンス】通知にユーザーIDを付加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation-2nd/_workitems/edit/4491)
This commit is contained in:
parent
ad397f6fe7
commit
a07cfe51aa
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
363
azure-pipelines-staging-ph1-enhance.yml
Normal file
@ -0,0 +1,363 @@
|
||||
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConnectionを作成しておくこと
|
||||
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- release-ph1-enhance
|
||||
tags:
|
||||
include:
|
||||
- stage-*
|
||||
|
||||
jobs:
|
||||
- job: initialize
|
||||
displayName: Initialize
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
persistCredentials: true
|
||||
- script: |
|
||||
git fetch origin release-ph1-enhance:release-ph1-enhance
|
||||
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph1-enhance; then
|
||||
echo "This commit is in the release-ph1-enhance branch."
|
||||
else
|
||||
echo "This commit is not in the release-ph1-enhance branch."
|
||||
exit 1
|
||||
fi
|
||||
displayName: 'タグが付けられたCommitがrelease-ph1-enhanceブランチに存在するか確認'
|
||||
- job: backend_test
|
||||
dependsOn: initialize
|
||||
condition: succeeded('initialize')
|
||||
displayName: UnitTest
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_server/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_server sudo npm ci
|
||||
docker-compose exec -T dictation_server sudo npm run migrate:up:test
|
||||
docker-compose exec -T dictation_server sudo npm run test
|
||||
- job: backend_build
|
||||
dependsOn: backend_test
|
||||
condition: succeeded('backend_test')
|
||||
displayName: Build And Push Backend Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_server
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileServerDictation.dockerfile
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
|
||||
- job: frontend_build_staging
|
||||
dependsOn: backend_build
|
||||
condition: succeeded('backend_build')
|
||||
displayName: Build Frontend Files(staging)
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: staging
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_client
|
||||
verbose: false
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: cd dictation_client && npm run build:stg
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: dictation_client/build
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob upload \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: frontend_build_production
|
||||
dependsOn: frontend_build_staging
|
||||
condition: succeeded('frontend_build_staging')
|
||||
displayName: Build Frontend Files(production)
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: production
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_client
|
||||
verbose: false
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: cd dictation_client && npm run build:prod
|
||||
- task: ArchiveFiles@2
|
||||
inputs:
|
||||
rootFolderOrFile: dictation_client/build
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
|
||||
replaceExistingArchive: true
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob upload \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--type block \
|
||||
--overwrite \
|
||||
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
|
||||
- job: function_test
|
||||
dependsOn: frontend_build_production
|
||||
condition: succeeded('frontend_build_production')
|
||||
displayName: UnitTest
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Bash@3
|
||||
displayName: Bash Script (Test)
|
||||
inputs:
|
||||
targetType: inline
|
||||
workingDirectory: dictation_function/.devcontainer
|
||||
script: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
docker-compose --version
|
||||
docker-compose -f pipeline-docker-compose.yml build
|
||||
docker-compose -f pipeline-docker-compose.yml up -d
|
||||
docker-compose exec -T dictation_function sudo npm ci
|
||||
docker-compose exec -T dictation_function sudo npm run test
|
||||
- job: function_build
|
||||
dependsOn: function_test
|
||||
condition: succeeded('function_test')
|
||||
displayName: Build And Push Function Image
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: Npm@1
|
||||
displayName: npm ci
|
||||
inputs:
|
||||
command: ci
|
||||
workingDir: dictation_function
|
||||
verbose: false
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
dockerFile: DockerfileFunctionDictation.dockerfile
|
||||
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||
buildArguments: |
|
||||
BUILD_VERSION=$(Build.SourceVersion)
|
||||
- task: Docker@0
|
||||
displayName: push
|
||||
inputs:
|
||||
azureSubscriptionEndpoint: 'omds-service-connection-stg'
|
||||
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
|
||||
action: Push an image
|
||||
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
|
||||
- job: backend_deploy
|
||||
dependsOn: function_build
|
||||
condition: succeeded('function_build')
|
||||
displayName: Backend Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureWebAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'app-odms-dictation-stg'
|
||||
deployToSlotOrASE: true
|
||||
resourceGroupName: 'stg-application-rg'
|
||||
slotName: 'staging'
|
||||
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
|
||||
- job: frontend_deploy
|
||||
dependsOn: backend_deploy
|
||||
condition: succeeded('backend_deploy')
|
||||
displayName: Deploy Frontend Files
|
||||
variables:
|
||||
storageAccountName: saomdspipeline
|
||||
environment: staging
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureCLI@2
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
scriptType: 'bash'
|
||||
scriptLocation: 'inlineScript'
|
||||
inlineScript: |
|
||||
az storage blob download \
|
||||
--auth-mode login \
|
||||
--account-name $(storageAccountName) \
|
||||
--container-name $(environment) \
|
||||
--name $(Build.SourceVersion).zip \
|
||||
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
|
||||
- task: Bash@3
|
||||
displayName: Bash Script
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
|
||||
- task: AzureStaticWebApp@0
|
||||
displayName: 'Static Web App: '
|
||||
inputs:
|
||||
workingDirectory: '$(Build.SourcesDirectory)'
|
||||
app_location: '/$(Build.SourceVersion)'
|
||||
config_file_location: /dictation_client
|
||||
skip_app_build: true
|
||||
skip_api_build: true
|
||||
is_static_export: false
|
||||
verbose: false
|
||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||
- job: function_deploy
|
||||
dependsOn: frontend_deploy
|
||||
condition: succeeded('frontend_deploy')
|
||||
displayName: Function Deploy
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureFunctionAppContainer@1
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
appName: 'func-odms-dictation-stg'
|
||||
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||
- job: smoke_test
|
||||
dependsOn: function_deploy
|
||||
condition: succeeded('function_deploy')
|
||||
displayName: 'smoke test'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
# スモークテスト用にjobを確保
|
||||
- job: swap_slot
|
||||
dependsOn: smoke_test
|
||||
condition: succeeded('smoke_test')
|
||||
displayName: 'Swap Staging and Production'
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureAppServiceManage@0
|
||||
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
|
||||
inputs:
|
||||
azureSubscription: 'omds-service-connection-stg'
|
||||
action: 'Swap Slots'
|
||||
WebAppName: 'app-odms-dictation-stg'
|
||||
ResourceGroupName: 'stg-application-rg'
|
||||
SourceSlot: 'staging'
|
||||
SwapWithProduction: true
|
||||
- job: migration
|
||||
dependsOn: swap_slot
|
||||
condition: succeeded('swap_slot')
|
||||
displayName: DB migration
|
||||
pool:
|
||||
name: odms-deploy-pipeline
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
fetchDepth: 1
|
||||
- task: AzureKeyVault@2
|
||||
displayName: 'Azure Key Vault: kv-odms-secret-stg'
|
||||
inputs:
|
||||
ConnectedServiceName: 'omds-service-connection-stg'
|
||||
KeyVaultName: kv-odms-secret-stg
|
||||
- task: CmdLine@2
|
||||
displayName: migration
|
||||
inputs:
|
||||
script: >2
|
||||
# DB接続情報書き換え
|
||||
sed -i -e "s/DB_NAME/$(db-name-ph1-enhance)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
|
||||
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
|
||||
sql-migrate --version
|
||||
cat ./dictation_server/db/dbconfig.yml
|
||||
# migration実行
|
||||
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci
|
||||
@ -7058,6 +7058,44 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* 完了した文字起こしタスクを再開します(ステータスをPendingにします)
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
reopen: async (audioFileId: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'audioFileId' is not null or undefined
|
||||
assertParamExists('reopen', 'audioFileId', audioFileId)
|
||||
const localVarPath = `/tasks/{audioFileId}/reopen`
|
||||
.replace(`{${"audioFileId"}}`, encodeURIComponent(String(audioFileId)));
|
||||
// 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};
|
||||
@ -7224,6 +7262,19 @@ export const TasksApiFp = function(configuration?: Configuration) {
|
||||
const operationBasePath = operationServerMap['TasksApi.getTasks']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 完了した文字起こしタスクを再開します(ステータスをPendingにします)
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async reopen(audioFileId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.reopen(audioFileId, options);
|
||||
const index = configuration?.serverIndex ?? 0;
|
||||
const operationBasePath = operationServerMap['TasksApi.reopen']?.[index]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクを一時中断します(ステータスをPendingにします)
|
||||
* @summary
|
||||
@ -7332,6 +7383,16 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath
|
||||
getTasks(limit?: number, offset?: number, status?: string, direction?: string, paramName?: string, options?: any): AxiosPromise<TasksResponse> {
|
||||
return localVarFp.getTasks(limit, offset, status, direction, paramName, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 完了した文字起こしタスクを再開します(ステータスをPendingにします)
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
reopen(audioFileId: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.reopen(audioFileId, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* 指定した文字起こしタスクを一時中断します(ステータスをPendingにします)
|
||||
* @summary
|
||||
@ -7453,6 +7514,18 @@ export class TasksApi extends BaseAPI {
|
||||
return TasksApiFp(this.configuration).getTasks(limit, offset, status, direction, paramName, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 完了した文字起こしタスクを再開します(ステータスをPendingにします)
|
||||
* @summary
|
||||
* @param {number} audioFileId ODMS Cloud上の音声ファイルID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof TasksApi
|
||||
*/
|
||||
public reopen(audioFileId: number, options?: AxiosRequestConfig) {
|
||||
return TasksApiFp(this.configuration).reopen(audioFileId, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した文字起こしタスクを一時中断します(ステータスをPendingにします)
|
||||
* @summary
|
||||
|
||||
@ -450,6 +450,78 @@ export const cancelAsync = createAsyncThunk<
|
||||
}
|
||||
});
|
||||
|
||||
export const reopenAsync = createAsyncThunk<
|
||||
{
|
||||
/** empty */
|
||||
},
|
||||
{
|
||||
direction: DirectionType;
|
||||
paramName: SortableColumnType;
|
||||
audioFileId: number;
|
||||
isTypist: boolean;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("dictations/reopenAsync", async (args, thunkApi) => {
|
||||
const { audioFileId, direction, paramName, isTypist } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const accessToken = getAccessToken(state.auth);
|
||||
const config = new Configuration(configuration);
|
||||
const tasksApi = new TasksApi(config);
|
||||
const usersApi = new UsersApi(config);
|
||||
try {
|
||||
// ユーザーがタイピストである場合に、ソート条件を保存する
|
||||
if (isTypist) {
|
||||
await usersApi.updateSortCriteria(
|
||||
{ direction, paramName },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
}
|
||||
await tasksApi.reopen(audioFileId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
// ステータスが[Finished]以外、またはタスクが存在しない場合、またはtypistで自分のタスクでない場合
|
||||
if (error.code === "E010601" || error.code === "E010603") {
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("dictationPage.message.reopenFailedError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const listBackupPopupTasksAsync = createAsyncThunk<
|
||||
TasksResponse,
|
||||
{
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
selectIsLoading,
|
||||
playbackAsync,
|
||||
cancelAsync,
|
||||
reopenAsync,
|
||||
PRIORITY,
|
||||
deleteTaskAsync,
|
||||
isSortableColumnType,
|
||||
@ -491,6 +492,58 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
]
|
||||
);
|
||||
|
||||
const onReopen = useCallback(
|
||||
async (audioFileId: number) => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { meta } = await dispatch(
|
||||
reopenAsync({
|
||||
audioFileId,
|
||||
direction: sortDirection,
|
||||
paramName: sortableParamName,
|
||||
isTypist,
|
||||
})
|
||||
);
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
const filter = getFilter(
|
||||
filterUploaded,
|
||||
filterInProgress,
|
||||
filterPending,
|
||||
filterFinished,
|
||||
filterBackup
|
||||
);
|
||||
dispatch(
|
||||
listTasksAsync({
|
||||
limit: LIMIT_TASK_NUM,
|
||||
offset: 0,
|
||||
filter,
|
||||
direction: sortDirection,
|
||||
paramName: sortableParamName,
|
||||
})
|
||||
);
|
||||
dispatch(listTypistsAsync());
|
||||
dispatch(listTypistGroupsAsync());
|
||||
}
|
||||
},
|
||||
[
|
||||
dispatch,
|
||||
filterBackup,
|
||||
filterFinished,
|
||||
filterInProgress,
|
||||
filterPending,
|
||||
filterUploaded,
|
||||
isTypist,
|
||||
sortDirection,
|
||||
sortableParamName,
|
||||
t,
|
||||
]
|
||||
);
|
||||
|
||||
const onCloseBackupPopup = useCallback(() => {
|
||||
setIsBackupPopupOpen(false);
|
||||
}, []);
|
||||
@ -1263,6 +1316,27 @@ const DictationPage: React.FC = (): JSX.Element => {
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* タスクのステータスがFinishedかつ、ログインユーザーがAdminかTypistの場合、Change status to Pendingボタンを活性化する */}
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={
|
||||
x.status === STATUS.FINISHED &&
|
||||
(isAdmin || isTypist)
|
||||
? ""
|
||||
: styles.isDisable
|
||||
}
|
||||
onClick={() => {
|
||||
onReopen(x.audioFileId);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"dictationPage.label.reopenDictation"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
|
||||
@ -257,6 +257,7 @@
|
||||
"taskNotEditable": "Der Transkriptionist kann nicht geändert werden, da die Transkription bereits ausgeführt wird oder die Datei nicht vorhanden ist. Bitte aktualisieren Sie den Bildschirm und prüfen Sie den aktuellen Status.",
|
||||
"backupFailedError": "Der Prozess „Dateisicherung“ ist fehlgeschlagen. Bitte versuchen Sie es später noch einmal. Wenn der Fehler weiterhin besteht, wenden Sie sich an Ihren Systemadministrator.",
|
||||
"cancelFailedError": "Die Diktate konnten nicht gelöscht werden. Bitte aktualisieren Sie Ihren Bildschirm und versuchen Sie es erneut.",
|
||||
"reopenFailedError": "Der Status kann nicht in „Ausstehend“ geändert werden. Bitte aktualisieren Sie Ihren Bildschirm und versuchen Sie es erneut.",
|
||||
"deleteFailedError": "Die Aufgabe konnte nicht gelöscht werden. Bitte aktualisieren Sie den Bildschirm und überprüfen Sie ihn erneut.",
|
||||
"licenseNotAssignedError": "Die Transkription ist nicht möglich, da keine gültige Lizenz zugewiesen ist. Bitten Sie Ihren Administrator, eine gültige Lizenz zuzuweisen.",
|
||||
"licenseExpiredError": "Die Transkription ist nicht möglich, da Ihre Lizenz abgelaufen ist. Bitte bitten Sie Ihren Administrator, Ihnen eine gültige Lizenz zuzuweisen.",
|
||||
@ -310,7 +311,8 @@
|
||||
"applications": "Desktopanwendung",
|
||||
"cancelDictation": "Transkription abbrechen",
|
||||
"rawFileName": "Ursprünglicher Dateiname",
|
||||
"fileNameSave": "Führen Sie eine Dateiumbenennung durch"
|
||||
"fileNameSave": "Führen Sie eine Dateiumbenennung durch",
|
||||
"reopenDictation": "Status auf „Ausstehend“ ändern"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
|
||||
@ -257,6 +257,7 @@
|
||||
"taskNotEditable": "The transcriptionist cannot be changed because the transcription is already in progress or the file does not exist. Please refresh the screen and check the latest status.",
|
||||
"backupFailedError": "The \"File Backup\" process has failed. Please try again later. If the error continues, contact your system administrator.",
|
||||
"cancelFailedError": "Failed to delete the dictations. Please refresh your screen and try again.",
|
||||
"reopenFailedError": "The status could not be changed to Pending. Please refresh the screen to see the current status. Only files with Finished status can be operated.",
|
||||
"deleteFailedError": "Failed to delete the task. Please refresh the screen and check again.",
|
||||
"licenseNotAssignedError": "Transcription is not possible because a valid license is not assigned. Please ask your administrator to assign a valid license.",
|
||||
"licenseExpiredError": "Transcription is not possible because your license is expired. Please ask your administrator to assign a valid license.",
|
||||
@ -310,7 +311,8 @@
|
||||
"applications": "Desktop Application",
|
||||
"cancelDictation": "Cancel Transcription",
|
||||
"rawFileName": "Original File Name",
|
||||
"fileNameSave": "Execute file rename"
|
||||
"fileNameSave": "Execute file rename",
|
||||
"reopenDictation": "Change status to Pending"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
|
||||
@ -257,6 +257,7 @@
|
||||
"taskNotEditable": "No se puede cambiar el transcriptor porque la transcripción ya está en curso o el archivo no existe. Actualice la pantalla y verifique el estado más reciente.",
|
||||
"backupFailedError": "El proceso de \"Copia de seguridad de archivos\" ha fallado. Por favor, inténtelo de nuevo más tarde. Si el error continúa, comuníquese con el administrador del sistema.",
|
||||
"cancelFailedError": "No se pudieron eliminar los dictados. Actualice su pantalla e inténtelo nuevamente.",
|
||||
"reopenFailedError": "No se pudo cambiar el estado a Pendiente. Actualice la pantalla para ver el estado actual. Solo se pueden utilizar los archivos con estado Finalizado.",
|
||||
"deleteFailedError": "No se pudo eliminar la tarea. Actualice la pantalla y verifique nuevamente.",
|
||||
"licenseNotAssignedError": "La transcripción no es posible porque no se ha asignado una licencia válida. Solicite a su administrador que le asigne una licencia válida.",
|
||||
"licenseExpiredError": "La transcripción no es posible porque su licencia ha caducado. Solicite a su administrador que le asigne una licencia válida.",
|
||||
@ -310,7 +311,8 @@
|
||||
"applications": "Aplicación de escritorio",
|
||||
"cancelDictation": "Cancelar transcripción",
|
||||
"rawFileName": "Nombre de archivo original",
|
||||
"fileNameSave": "Ejecutar cambio de nombre de archivo"
|
||||
"fileNameSave": "Ejecutar cambio de nombre de archivo",
|
||||
"reopenDictation": "Cambiar el estado a Pendiente"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
|
||||
@ -257,6 +257,7 @@
|
||||
"taskNotEditable": "Le transcripteur ne peut pas être changé car la transcription est déjà en cours ou le fichier n'existe pas. Veuillez actualiser l'écran et vérifier le dernier statut.",
|
||||
"backupFailedError": "Le processus de « Sauvegarde de fichier » a échoué. Veuillez réessayer plus tard. Si l'erreur persiste, contactez votre administrateur système.",
|
||||
"cancelFailedError": "Échec de la suppression des dictées. Veuillez actualiser votre écran et réessayer.",
|
||||
"reopenFailedError": "Le statut n'a pas pu être modifié en Suspendu. Veuillez actualiser l'écran pour voir le statut actuel. Seuls les fichiers dont le statut est Terminé peuvent être traités.",
|
||||
"deleteFailedError": "Échec de la suppression de la tâche. Veuillez actualiser l'écran et vérifier à nouveau.",
|
||||
"licenseNotAssignedError": "La transcription n'est pas possible car aucune licence valide n'a été attribuée. Veuillez demander à votre administrateur d'attribuer une licence valide.",
|
||||
"licenseExpiredError": "La transcription n'est pas possible car votre licence est expirée. Veuillez demander à votre administrateur de vous attribuer une licence valide.",
|
||||
@ -310,7 +311,8 @@
|
||||
"applications": "Application de bureau",
|
||||
"cancelDictation": "Annuler la transcription",
|
||||
"rawFileName": "Nom du fichier d'origine",
|
||||
"fileNameSave": "Exécuter le changement de nom du fichier"
|
||||
"fileNameSave": "Exécuter le changement de nom du fichier",
|
||||
"reopenDictation": "Changer le statut en Suspendu"
|
||||
}
|
||||
},
|
||||
"cardLicenseIssuePopupPage": {
|
||||
|
||||
@ -2,5 +2,6 @@ DB_HOST=omds-mysql
|
||||
DB_PORT=3306
|
||||
DB_NAME=omds
|
||||
DB_NAME_CCB=omds_ccb
|
||||
DB_NAME_PH1ENHANCE=omds_ph1enhance
|
||||
DB_USERNAME=omdsdbuser
|
||||
DB_PASSWORD=omdsdbpass
|
||||
|
||||
@ -6,6 +6,10 @@ ccb:
|
||||
dialect: mysql
|
||||
dir: /app/dictation_server/db/migrations
|
||||
datasource: ${DB_USERNAME}:${DB_PASSWORD}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME_CCB}?charset=utf8mb4&collation=utf8mb4_0900_ai_ci&parseTime=true
|
||||
ph1_enhance:
|
||||
dialect: mysql
|
||||
dir: /app/dictation_server/db/migrations
|
||||
datasource: ${DB_USERNAME}:${DB_PASSWORD}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME_PH1ENHANCE}?charset=utf8mb4&collation=utf8mb4_0900_ai_ci&parseTime=true
|
||||
ci:
|
||||
dialect: mysql
|
||||
dir: ./dictation_server/db/migrations
|
||||
|
||||
@ -3524,6 +3524,68 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/tasks/{audioFileId}/reopen": {
|
||||
"post": {
|
||||
"operationId": "reopen",
|
||||
"summary": "",
|
||||
"description": "完了した文字起こしタスクを再開します(ステータスをPendingにします)",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "audioFileId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "ODMS Cloud上の音声ファイルID",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChangeStatusResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "不正なパラメータ",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "指定したIDの音声ファイルが存在しない場合",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["tasks"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/licenses/orders": {
|
||||
"post": {
|
||||
"operationId": "createOrders",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export type NotificationBody = {
|
||||
id: string;
|
||||
filename: string;
|
||||
authorId: string;
|
||||
priority: string;
|
||||
|
||||
@ -28,6 +28,10 @@ export class EnvValidator {
|
||||
@IsString()
|
||||
DB_NAME_CCB: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
DB_NAME_PH1ENHANCE: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
DB_USERNAME: string;
|
||||
|
||||
@ -203,13 +203,6 @@ export const TASK_LIST_SORTABLE_ATTRIBUTES = [
|
||||
*/
|
||||
export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const;
|
||||
|
||||
/**
|
||||
* 通知タグの最大個数
|
||||
* NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで
|
||||
* https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions
|
||||
*/
|
||||
export const TAG_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* 通知のプラットフォーム種別文字列
|
||||
*/
|
||||
|
||||
@ -367,10 +367,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -472,10 +473,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -737,10 +739,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -1408,10 +1411,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -1526,10 +1530,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -1654,10 +1659,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -1782,10 +1788,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -1900,10 +1907,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -2050,10 +2058,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -2199,10 +2208,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
@ -2349,10 +2359,11 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(notificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'file',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: '2023-05-26T11:22:33.444',
|
||||
},
|
||||
|
||||
@ -273,12 +273,17 @@ export class FilesService {
|
||||
this.logger.log(`[${context.getTrackingId()}] tags: ${tags}`);
|
||||
|
||||
// タグ対象に通知送信
|
||||
await this.notificationhubService.notify(context, tags, {
|
||||
authorId: authorId,
|
||||
filename: fileName.replace('.zip', ''),
|
||||
priority: priority === '00' ? 'Normal' : 'High',
|
||||
uploadedAt: uploadedDate,
|
||||
});
|
||||
await Promise.all(
|
||||
tags.map((tag) => {
|
||||
return this.notificationhubService.notify(context, tag, {
|
||||
id: tag.split('user_')[1],
|
||||
authorId: authorId,
|
||||
filename: fileName.replace('.zip', ''),
|
||||
priority: priority === '00' ? 'Normal' : 'High',
|
||||
uploadedAt: uploadedDate,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// 追加したタスクのJOBナンバーを返却
|
||||
return { jobNumber: task.job_number };
|
||||
|
||||
@ -831,4 +831,91 @@ export class TasksController {
|
||||
await this.taskService.deleteTask(context, userId, audioFileId);
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post(':audioFileId/reopen')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: ChangeStatusResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '不正なパラメータ',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.NOT_FOUND,
|
||||
description: '指定したIDの音声ファイルが存在しない場合',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'reopen',
|
||||
description:
|
||||
'終了した文字起こしタスクを再開します(ステータスをPendingにします)',
|
||||
})
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN, USER_ROLES.TYPIST],
|
||||
}),
|
||||
)
|
||||
@ApiBearerAuth()
|
||||
async reopen(
|
||||
@Req() req: Request,
|
||||
@Param() params: ChangeStatusRequest,
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
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(userId, requestId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
await this.taskService.reopen(context, audioFileId, userId, roles);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1132,10 +1132,11 @@ describe('changeCheckoutPermission', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId_2}`],
|
||||
`user_${typistUserId_2}`,
|
||||
{
|
||||
authorId: 'MY_AUTHOR_ID',
|
||||
filename: 'x',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
|
||||
},
|
||||
@ -1216,10 +1217,11 @@ describe('changeCheckoutPermission', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId_2}`],
|
||||
`user_${typistUserId_2}`,
|
||||
{
|
||||
authorId: 'MY_AUTHOR_ID',
|
||||
filename: 'x',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
|
||||
},
|
||||
@ -3529,10 +3531,11 @@ describe('cancel', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${typistUserId}`],
|
||||
`user_${typistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'x',
|
||||
id: '1',
|
||||
priority: 'High',
|
||||
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
|
||||
},
|
||||
@ -3639,10 +3642,11 @@ describe('cancel', () => {
|
||||
// 通知処理が想定通りの引数で呼ばれているか確認
|
||||
expect(NotificationHubService.notify).toHaveBeenCalledWith(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
[`user_${autoRoutingTypistUserId}`],
|
||||
`user_${autoRoutingTypistUserId}`,
|
||||
{
|
||||
authorId: 'AUTHOR_ID',
|
||||
filename: 'x',
|
||||
id: '2',
|
||||
priority: 'High',
|
||||
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
|
||||
},
|
||||
@ -5308,3 +5312,323 @@ describe('deleteTask', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('reopen', () => {
|
||||
let source: DataSource | null = null;
|
||||
beforeAll(async () => {
|
||||
if (source == null) {
|
||||
source = await (async () => {
|
||||
const s = new DataSource({
|
||||
type: 'mysql',
|
||||
host: 'test_mysql_db',
|
||||
port: 3306,
|
||||
username: 'user',
|
||||
password: 'password',
|
||||
database: 'odms',
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: false, // trueにすると自動的にmigrationが行われるため注意
|
||||
logger: new TestLogger('none'),
|
||||
logging: true,
|
||||
});
|
||||
return await s.initialize();
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
if (source) {
|
||||
await truncateAllTable(source);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await source?.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('API実行者のRoleがTypistの場合、自身が担当する完了済みの文字起こしタスクを再開できる', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: typistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Finished',
|
||||
typistUserId,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
await service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'typist-user-external-id',
|
||||
['typist', 'standard'],
|
||||
);
|
||||
const resultTask = await getTask(source, taskId);
|
||||
|
||||
expect(resultTask?.status).toEqual('Pending');
|
||||
expect(resultTask?.finished_at).toEqual(null);
|
||||
});
|
||||
|
||||
it('API実行者のRoleがAdminの場合、完了済みの文字起こしタスクを再開できる', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: typistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Finished',
|
||||
typistUserId,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, typistUserId);
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
await service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'typist-user-external-id',
|
||||
['admin', 'standard'],
|
||||
);
|
||||
|
||||
const resultTask = await getTask(source, taskId);
|
||||
|
||||
expect(resultTask?.status).toEqual('Pending');
|
||||
expect(resultTask?.finished_at).toEqual(null);
|
||||
});
|
||||
|
||||
it('タスクのステータスが[Finished]でない時、タスクを再開できない', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: typistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Uploaded',
|
||||
typistUserId,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, typistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
await expect(
|
||||
service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'typist-user-external-id',
|
||||
['admin', 'author'],
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('API実行者のRoleがTypistの場合、他人の終了済み文字起こしタスクを再開できない', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: anotherTypistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'another-typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Finished',
|
||||
anotherTypistUserId,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, anotherTypistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
await expect(
|
||||
service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'typist-user-external-id',
|
||||
['typist', 'standard'],
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('API実行者のRoleがAuthorの場合、他人が終了済み文字起こしタスクを再開できない', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: anotherTypistUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'another-typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
const { id: authorUserId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
const { taskId } = await createTask(
|
||||
source,
|
||||
accountId,
|
||||
authorUserId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Finished',
|
||||
anotherTypistUserId,
|
||||
);
|
||||
await createCheckoutPermissions(source, taskId, anotherTypistUserId);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
await expect(
|
||||
service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'author-user-external-id',
|
||||
['author', 'standard'],
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
});
|
||||
|
||||
it('タスクがない時、タスクを再開できない', async () => {
|
||||
if (!source) fail();
|
||||
const notificationhubServiceMockValue =
|
||||
makeDefaultNotificationhubServiceMockValue();
|
||||
const module = await makeTaskTestingModuleWithNotificaiton(
|
||||
source,
|
||||
notificationhubServiceMockValue,
|
||||
);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'typist-user-external-id',
|
||||
role: 'typist',
|
||||
});
|
||||
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
external_id: 'author-user-external-id',
|
||||
role: 'author',
|
||||
author_id: 'MY_AUTHOR_ID',
|
||||
});
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
|
||||
await expect(
|
||||
service.reopen(
|
||||
makeContext('trackingId', 'requestId'),
|
||||
1,
|
||||
'typist-user-external-id',
|
||||
['typist', 'standard'],
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -712,6 +712,80 @@ export class TasksService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した完了済みの音声ファイルに紐づくタスクを再開する(ステータスをPendingに変更する)
|
||||
* @param audioFileId
|
||||
* @param externalId
|
||||
* @param role
|
||||
* @returns reopen
|
||||
*/
|
||||
async reopen(
|
||||
context: Context,
|
||||
audioFileId: number,
|
||||
externalId: string,
|
||||
role: Roles[],
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.reopen.name
|
||||
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId}, role: ${role} };`,
|
||||
);
|
||||
let user: User;
|
||||
try {
|
||||
// ユーザー取得
|
||||
user = await this.usersRepository.findUserByExternalId(
|
||||
context,
|
||||
externalId,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.reopen.name}`);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// roleにAdminが含まれていれば、文字起こし担当でなくても再開できるため、ユーザーIDは指定しない
|
||||
await this.taskRepository.reopen(
|
||||
context,
|
||||
audioFileId,
|
||||
[TASK_STATUS.FINISHED],
|
||||
user.account_id,
|
||||
role.includes(ADMIN_ROLES.ADMIN) ? undefined : user.id,
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TasksNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010603'),
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
case StatusNotMatchError:
|
||||
case TypistUserNotMatchError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010601'),
|
||||
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.getTrackingId()}] ${this.reopen.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した音声ファイルに紐づくタスクをbackupする
|
||||
* @param context
|
||||
@ -1037,12 +1111,18 @@ export class TasksService {
|
||||
}
|
||||
|
||||
// タグ対象に通知送信
|
||||
await this.notificationhubService.notify(context, tags, {
|
||||
authorId: file.author_id,
|
||||
filename: file.file_name.replace('.zip', ''),
|
||||
priority: file.priority === '00' ? 'Normal' : 'High',
|
||||
uploadedAt: file.uploaded_at.toISOString(),
|
||||
});
|
||||
await Promise.all(
|
||||
tags.map((tag) => {
|
||||
return this.notificationhubService.notify(context, tag, {
|
||||
id: tag.split('user_')[1],
|
||||
authorId: file.author_id,
|
||||
filename: file.file_name.replace('.zip', ''),
|
||||
priority: file.priority === '00' ? 'Normal' : 'High',
|
||||
uploadedAt: file.uploaded_at.toISOString(),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.getTrackingId()}] ${this.sendNotify.name}`,
|
||||
);
|
||||
|
||||
@ -9,7 +9,6 @@ import {
|
||||
createAppleInstallation,
|
||||
createWindowsRawNotification,
|
||||
} from '@azure/notification-hubs';
|
||||
import { TAG_MAX_COUNT } from '../../constants';
|
||||
import { PNS } from '../../constants';
|
||||
import { Context } from '../../common/log';
|
||||
import { NotificationBody } from '../../common/notify/types/types';
|
||||
@ -83,65 +82,57 @@ export class NotificationhubService {
|
||||
/**
|
||||
* 指定したタグのユーザーに通知を送信する
|
||||
* @param context
|
||||
* @param tags
|
||||
* @param tag
|
||||
* @param bodyContent
|
||||
* @returns notify
|
||||
*/
|
||||
async notify(
|
||||
context: Context,
|
||||
tags: string[],
|
||||
tag: string,
|
||||
bodyContent: NotificationBody,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.getTrackingId()}] ${
|
||||
this.notify.name
|
||||
} | params: { tags: ${tags}, bodyContent: ${JSON.stringify(
|
||||
bodyContent,
|
||||
)} }`,
|
||||
} | params: { tag: ${tag}, bodyContent: ${JSON.stringify(bodyContent)} }`,
|
||||
);
|
||||
|
||||
try {
|
||||
// OR条件によるtag指定は20個までなので分割して送信する
|
||||
const chunkTags = splitArrayInChunks(tags, TAG_MAX_COUNT);
|
||||
const tagExpression = createTagExpression([tag]);
|
||||
|
||||
for (let index = 0; index < chunkTags.length; index++) {
|
||||
const currentTags = chunkTags[index];
|
||||
const tagExpression = createTagExpression(currentTags);
|
||||
|
||||
// Windows
|
||||
try {
|
||||
const body = {
|
||||
wns: {
|
||||
alert: '',
|
||||
},
|
||||
newDictation: bodyContent,
|
||||
};
|
||||
const notification = createWindowsRawNotification({
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const result = await this.client.sendNotification(notification, {
|
||||
tagExpression,
|
||||
});
|
||||
this.logger.log(`[${context.getTrackingId()}] ${result}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
}
|
||||
// Apple
|
||||
try {
|
||||
const body = createAppleNotificationBody({
|
||||
aps: {
|
||||
alert: '',
|
||||
},
|
||||
newDictation: bodyContent,
|
||||
});
|
||||
const notification = createAppleNotification({ body });
|
||||
const result = await this.client.sendNotification(notification, {
|
||||
tagExpression,
|
||||
});
|
||||
this.logger.log(`[${context.getTrackingId()}] ${result}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
}
|
||||
// Windows
|
||||
try {
|
||||
const body = {
|
||||
wns: {
|
||||
alert: '',
|
||||
},
|
||||
newDictation: bodyContent,
|
||||
};
|
||||
const notification = createWindowsRawNotification({
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const result = await this.client.sendNotification(notification, {
|
||||
tagExpression,
|
||||
});
|
||||
this.logger.log(`[${context.getTrackingId()}] ${result}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
}
|
||||
// Apple
|
||||
try {
|
||||
const body = createAppleNotificationBody({
|
||||
aps: {
|
||||
alert: '',
|
||||
},
|
||||
newDictation: bodyContent,
|
||||
});
|
||||
const notification = createAppleNotification({ body });
|
||||
const result = await this.client.sendNotification(notification, {
|
||||
tagExpression,
|
||||
});
|
||||
this.logger.log(`[${context.getTrackingId()}] ${result}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||
}
|
||||
} catch (e) {
|
||||
throw e;
|
||||
@ -150,11 +141,3 @@ export class NotificationhubService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const splitArrayInChunks = (arr: string[], size: number): string[][] => {
|
||||
const result: string[][] = [];
|
||||
for (let i = 0; i < arr.length; i += size) {
|
||||
result.push(arr.slice(i, i + size));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -506,6 +506,69 @@ export class TasksRepositoryService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 音声ファイルIDで指定した完了済みのタスクを再開する(Pendingに変更する)
|
||||
* @param audio_file_id 再開するタスクの音声ファイルID
|
||||
* @param permittedSourceStatus 再開可能なステータス
|
||||
* @param account_id 再開するタスクのアカウントID
|
||||
* @param user_id 再開するユーザーのID(API実行者がAdminのときは使用しない)
|
||||
* @returns reopen
|
||||
*/
|
||||
async reopen(
|
||||
context: Context,
|
||||
audio_file_id: number,
|
||||
permittedSourceStatus: TaskStatus[],
|
||||
account_id: number,
|
||||
user_id?: number | undefined,
|
||||
): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
const task = await taskRepo.findOne({
|
||||
where: {
|
||||
audio_file_id: audio_file_id,
|
||||
},
|
||||
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!task) {
|
||||
throw new TasksNotFoundError(
|
||||
`task not found. audio_file_id:${audio_file_id}`,
|
||||
);
|
||||
}
|
||||
if (!isTaskStatus(task.status)) {
|
||||
throw new Error('invalid task status.');
|
||||
}
|
||||
// ステータスチェック
|
||||
if (!permittedSourceStatus.includes(task.status)) {
|
||||
throw new StatusNotMatchError(
|
||||
`Unexpected task status. audio_file_id:${audio_file_id}, status:${task.status}`,
|
||||
);
|
||||
}
|
||||
if (task.account_id !== account_id) {
|
||||
throw new AccountNotMatchError(
|
||||
`task account_id not match. audio_file_id:${audio_file_id}, account_id(Task):${task.account_id}, account_id:${account_id}`,
|
||||
);
|
||||
}
|
||||
if (user_id && task.typist_user_id !== user_id) {
|
||||
throw new TypistUserNotMatchError(
|
||||
`TypistUser not match. audio_file_id:${audio_file_id}, typist_user_id:${task.typist_user_id}, user_id:${user_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 対象タスクのステータスをPendingに,文字起こし終了日時をnullに更新
|
||||
await updateEntity(
|
||||
taskRepo,
|
||||
{ audio_file_id: audio_file_id },
|
||||
{
|
||||
status: TASK_STATUS.PENDING,
|
||||
finished_at: null,
|
||||
},
|
||||
this.isCommentOut,
|
||||
context,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 音声ファイルIDで指定したタスクをbackupする
|
||||
* @param accountId バックアップするタスクのアカウントID
|
||||
@ -921,7 +984,7 @@ export class TasksRepositoryService {
|
||||
throw new Error(`JobNumber not exists. account_id:${account_id}`);
|
||||
}
|
||||
|
||||
let newJobNumber: string = '';
|
||||
let newJobNumber = '';
|
||||
if (currentJobNumberData.job_number === MAX_JOB_NUMBER) {
|
||||
// 末尾なら00000001に戻る
|
||||
newJobNumber = '00000001';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user