Merge branch 'develop' into main
This commit is contained in:
commit
20157341f6
@ -1,12 +1,29 @@
|
|||||||
# To enable ssh & remote debugging on app service change the base image to the one below
|
#ビルドイメージ
|
||||||
# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice
|
FROM node:18.17.1-buster AS build-container
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir dictation_function
|
||||||
|
COPY dictation_function/ dictation_function/
|
||||||
|
RUN npm install --force -g n && n 18.17.1 \
|
||||||
|
&& cd dictation_function \
|
||||||
|
&& npm ci \
|
||||||
|
&& npm run build \
|
||||||
|
&& cd ..
|
||||||
|
|
||||||
|
# 成果物イメージ
|
||||||
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
||||||
|
|
||||||
|
WORKDIR /home/site/wwwroot
|
||||||
|
RUN mkdir build \
|
||||||
|
&& mkdir dist \
|
||||||
|
&& mkdir node_modules
|
||||||
|
|
||||||
|
COPY --from=build-container app/dictation_function/dist/ dist/
|
||||||
|
COPY --from=build-container app/dictation_function/node_modules/ node_modules/
|
||||||
|
COPY --from=build-container app/dictation_function/.env ./
|
||||||
|
COPY --from=build-container app/dictation_function/host.json ./
|
||||||
|
COPY --from=build-container app/dictation_function/package.json ./
|
||||||
|
|
||||||
|
ARG BUILD_VERSION
|
||||||
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
|
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
|
||||||
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
|
AzureFunctionsJobHost__Logging__Console__IsEnabled=true \
|
||||||
|
BUILD_VERSION=${BUILD_VERSION}
|
||||||
COPY . /home/site/wwwroot
|
|
||||||
|
|
||||||
RUN cd /home/site/wwwroot && \
|
|
||||||
npm install && \
|
|
||||||
npm run build
|
|
||||||
@ -83,9 +83,24 @@ jobs:
|
|||||||
is_static_export: false
|
is_static_export: false
|
||||||
verbose: false
|
verbose: false
|
||||||
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
|
||||||
- job: smoke_test
|
- job: function_deploy
|
||||||
dependsOn: frontend_deploy
|
dependsOn: frontend_deploy
|
||||||
condition: succeeded('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-prod'
|
||||||
|
appName: 'func-odms-dictation-prod'
|
||||||
|
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
|
||||||
|
- job: smoke_test
|
||||||
|
dependsOn: function_deploy
|
||||||
|
condition: succeeded('function_deploy')
|
||||||
displayName: 'smoke test'
|
displayName: 'smoke test'
|
||||||
pool:
|
pool:
|
||||||
name: odms-deploy-pipeline
|
name: odms-deploy-pipeline
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import AccountPage from "pages/AccountPage";
|
|||||||
import AcceptToUsePage from "pages/TermsPage";
|
import AcceptToUsePage from "pages/TermsPage";
|
||||||
import { TemplateFilePage } from "pages/TemplateFilePage";
|
import { TemplateFilePage } from "pages/TemplateFilePage";
|
||||||
import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess";
|
import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess";
|
||||||
|
import SupportPage from "pages/SupportPage";
|
||||||
|
|
||||||
const AppRouter: React.FC = () => (
|
const AppRouter: React.FC = () => (
|
||||||
<Routes>
|
<Routes>
|
||||||
@ -81,6 +82,10 @@ const AppRouter: React.FC = () => (
|
|||||||
element={<RouteAuthGuard component={<PartnerPage />} />}
|
element={<RouteAuthGuard component={<PartnerPage />} />}
|
||||||
/>
|
/>
|
||||||
<Route path="/accountDeleteSuccess" element={<AccountDeleteSuccess />} />
|
<Route path="/accountDeleteSuccess" element={<AccountDeleteSuccess />} />
|
||||||
|
<Route
|
||||||
|
path="/support"
|
||||||
|
element={<RouteAuthGuard component={<SupportPage />} />}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@ -447,6 +447,12 @@ export interface CreateAccountRequest {
|
|||||||
* @memberof CreateAccountRequest
|
* @memberof CreateAccountRequest
|
||||||
*/
|
*/
|
||||||
'acceptedEulaVersion': string;
|
'acceptedEulaVersion': string;
|
||||||
|
/**
|
||||||
|
* 同意済みプライバシーポリシーのバージョン
|
||||||
|
* @type {string}
|
||||||
|
* @memberof CreateAccountRequest
|
||||||
|
*/
|
||||||
|
'acceptedPrivacyNoticeVersion': string;
|
||||||
/**
|
/**
|
||||||
* 同意済み利用規約のバージョン(DPA)
|
* 同意済み利用規約のバージョン(DPA)
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -746,6 +752,32 @@ export interface GetAuthorsResponse {
|
|||||||
*/
|
*/
|
||||||
'authors': Array<Author>;
|
'authors': Array<Author>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface GetCompanyNameRequest
|
||||||
|
*/
|
||||||
|
export interface GetCompanyNameRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof GetCompanyNameRequest
|
||||||
|
*/
|
||||||
|
'accountId': number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface GetCompanyNameResponse
|
||||||
|
*/
|
||||||
|
export interface GetCompanyNameResponse {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof GetCompanyNameResponse
|
||||||
|
*/
|
||||||
|
'companyName': string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
@ -2000,6 +2032,12 @@ export interface UpdateAcceptedVersionRequest {
|
|||||||
* @memberof UpdateAcceptedVersionRequest
|
* @memberof UpdateAcceptedVersionRequest
|
||||||
*/
|
*/
|
||||||
'acceptedEULAVersion': string;
|
'acceptedEULAVersion': string;
|
||||||
|
/**
|
||||||
|
* 更新バージョン(PrivacyNotice)
|
||||||
|
* @type {string}
|
||||||
|
* @memberof UpdateAcceptedVersionRequest
|
||||||
|
*/
|
||||||
|
'acceptedPrivacyNoticeVersion': string;
|
||||||
/**
|
/**
|
||||||
* 更新バージョン(DPA)
|
* 更新バージョン(DPA)
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -2727,6 +2765,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
|||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 指定したアカウントの会社名を取得します
|
||||||
|
* @summary
|
||||||
|
* @param {GetCompanyNameRequest} getCompanyNameRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getCompanyName: async (getCompanyNameRequest: GetCompanyNameRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'getCompanyNameRequest' is not null or undefined
|
||||||
|
assertParamExists('getCompanyName', 'getCompanyNameRequest', getCompanyNameRequest)
|
||||||
|
const localVarPath = `/accounts/company-name`;
|
||||||
|
// 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(getCompanyNameRequest, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary
|
* @summary
|
||||||
@ -3488,6 +3566,19 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
|||||||
const operationBasePath = operationServerMap['AccountsApi.getAuthors']?.[index]?.url;
|
const operationBasePath = operationServerMap['AccountsApi.getAuthors']?.[index]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 指定したアカウントの会社名を取得します
|
||||||
|
* @summary
|
||||||
|
* @param {GetCompanyNameRequest} getCompanyNameRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetCompanyNameResponse>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getCompanyName(getCompanyNameRequest, options);
|
||||||
|
const index = configuration?.serverIndex ?? 0;
|
||||||
|
const operationBasePath = operationServerMap['AccountsApi.getCompanyName']?.[index]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary
|
* @summary
|
||||||
@ -3804,6 +3895,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
|||||||
getAuthors(options?: any): AxiosPromise<GetAuthorsResponse> {
|
getAuthors(options?: any): AxiosPromise<GetAuthorsResponse> {
|
||||||
return localVarFp.getAuthors(options).then((request) => request(axios, basePath));
|
return localVarFp.getAuthors(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 指定したアカウントの会社名を取得します
|
||||||
|
* @summary
|
||||||
|
* @param {GetCompanyNameRequest} getCompanyNameRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: any): AxiosPromise<GetCompanyNameResponse> {
|
||||||
|
return localVarFp.getCompanyName(getCompanyNameRequest, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary
|
* @summary
|
||||||
@ -4092,6 +4193,18 @@ export class AccountsApi extends BaseAPI {
|
|||||||
return AccountsApiFp(this.configuration).getAuthors(options).then((request) => request(this.axios, this.basePath));
|
return AccountsApiFp(this.configuration).getAuthors(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定したアカウントの会社名を取得します
|
||||||
|
* @summary
|
||||||
|
* @param {GetCompanyNameRequest} getCompanyNameRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AccountsApi
|
||||||
|
*/
|
||||||
|
public getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: AxiosRequestConfig) {
|
||||||
|
return AccountsApiFp(this.configuration).getCompanyName(getCompanyNameRequest, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary
|
* @summary
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export const HEADER_MENUS_LICENSE = "License";
|
|||||||
export const HEADER_MENUS_DICTATIONS = "Dictations";
|
export const HEADER_MENUS_DICTATIONS = "Dictations";
|
||||||
export const HEADER_MENUS_WORKFLOW = "Workflow";
|
export const HEADER_MENUS_WORKFLOW = "Workflow";
|
||||||
export const HEADER_MENUS_PARTNER = "Partners";
|
export const HEADER_MENUS_PARTNER = "Partners";
|
||||||
|
export const HEADER_MENUS_SUPPORT = "Support";
|
||||||
|
|
||||||
export const HEADER_MENUS: {
|
export const HEADER_MENUS: {
|
||||||
key: HeaderMenus;
|
key: HeaderMenus;
|
||||||
@ -43,6 +44,11 @@ export const HEADER_MENUS: {
|
|||||||
label: getTranslationID("common.label.headerPartners"),
|
label: getTranslationID("common.label.headerPartners"),
|
||||||
path: "/partners",
|
path: "/partners",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: HEADER_MENUS_SUPPORT,
|
||||||
|
label: getTranslationID("common.label.headerSupport"),
|
||||||
|
path: "/support",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HEADER_NAME = getTranslationID("common.label.headerName");
|
export const HEADER_NAME = getTranslationID("common.label.headerName");
|
||||||
|
|||||||
@ -8,7 +8,8 @@ export type HeaderMenus =
|
|||||||
| "License"
|
| "License"
|
||||||
| "Dictations"
|
| "Dictations"
|
||||||
| "Workflow"
|
| "Workflow"
|
||||||
| "Partners";
|
| "Partners"
|
||||||
|
| "Support";
|
||||||
|
|
||||||
// ログイン後に遷移しうるパス
|
// ログイン後に遷移しうるパス
|
||||||
export type LoginedPaths =
|
export type LoginedPaths =
|
||||||
@ -17,4 +18,5 @@ export type LoginedPaths =
|
|||||||
| "/license"
|
| "/license"
|
||||||
| "/dictations"
|
| "/dictations"
|
||||||
| "/workflow"
|
| "/workflow"
|
||||||
| "/partners";
|
| "/partners"
|
||||||
|
| "/support";
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export const isLoginPaths = (d: string): d is LoginedPaths => {
|
|||||||
case "/dictations":
|
case "/dictations":
|
||||||
case "/workflow":
|
case "/workflow":
|
||||||
case "/partners":
|
case "/partners":
|
||||||
|
case "/support":
|
||||||
return true;
|
return true;
|
||||||
default: {
|
default: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|||||||
@ -131,6 +131,10 @@ export const dictationSlice = createSlice({
|
|||||||
});
|
});
|
||||||
state.domain.backup.tasks = tasks;
|
state.domain.backup.tasks = tasks;
|
||||||
},
|
},
|
||||||
|
openFilePropertyInfo: (state, action: PayloadAction<{ task: Task }>) => {
|
||||||
|
const { task } = action.payload;
|
||||||
|
state.apps.selectedFileTask = task;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(listTasksAsync.pending, (state) => {
|
builder.addCase(listTasksAsync.pending, (state) => {
|
||||||
@ -225,6 +229,7 @@ export const {
|
|||||||
changeAssignee,
|
changeAssignee,
|
||||||
changeBackupTaskChecked,
|
changeBackupTaskChecked,
|
||||||
changeBackupTaskAllCheched,
|
changeBackupTaskAllCheched,
|
||||||
|
openFilePropertyInfo,
|
||||||
} = dictationSlice.actions;
|
} = dictationSlice.actions;
|
||||||
|
|
||||||
export default dictationSlice.reducer;
|
export default dictationSlice.reducer;
|
||||||
|
|||||||
@ -34,6 +34,9 @@ export const selectParamName = (state: RootState) =>
|
|||||||
export const selectSelectedTask = (state: RootState) =>
|
export const selectSelectedTask = (state: RootState) =>
|
||||||
state.dictation.apps.selectedTask;
|
state.dictation.apps.selectedTask;
|
||||||
|
|
||||||
|
export const selectSelectedFileTask = (state: RootState) =>
|
||||||
|
state.dictation.apps.selectedFileTask;
|
||||||
|
|
||||||
export const selectSelectedTranscriptionists = (state: RootState) =>
|
export const selectSelectedTranscriptionists = (state: RootState) =>
|
||||||
state.dictation.apps.assignee.selected;
|
state.dictation.apps.assignee.selected;
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export interface Apps {
|
|||||||
direction: DirectionType;
|
direction: DirectionType;
|
||||||
paramName: SortableColumnType;
|
paramName: SortableColumnType;
|
||||||
selectedTask?: Task;
|
selectedTask?: Task;
|
||||||
|
selectedFileTask?: Task;
|
||||||
assignee: {
|
assignee: {
|
||||||
selected: Assignee[];
|
selected: Assignee[];
|
||||||
pool: Assignee[];
|
pool: Assignee[];
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { LicenseSummaryState } from "./state";
|
import { LicenseSummaryState } from "./state";
|
||||||
import { getLicenseSummaryAsync } from "./operations";
|
import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
|
||||||
|
|
||||||
const initialState: LicenseSummaryState = {
|
const initialState: LicenseSummaryState = {
|
||||||
domain: {
|
domain: {
|
||||||
totalLicense: 0,
|
licenseSummaryInfo: {
|
||||||
allocatedLicense: 0,
|
totalLicense: 0,
|
||||||
reusableLicense: 0,
|
allocatedLicense: 0,
|
||||||
freeLicense: 0,
|
reusableLicense: 0,
|
||||||
expiringWithin14daysLicense: 0,
|
freeLicense: 0,
|
||||||
issueRequesting: 0,
|
expiringWithin14daysLicense: 0,
|
||||||
numberOfRequesting: 0,
|
issueRequesting: 0,
|
||||||
shortage: 0,
|
numberOfRequesting: 0,
|
||||||
storageSize: 0,
|
shortage: 0,
|
||||||
usedSize: 0,
|
storageSize: 0,
|
||||||
isStorageAvailable: false,
|
usedSize: 0,
|
||||||
|
isStorageAvailable: false,
|
||||||
|
},
|
||||||
|
accountInfo: {
|
||||||
|
companyName: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
apps: {
|
apps: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@ -31,7 +36,10 @@ export const licenseSummarySlice = createSlice({
|
|||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
|
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
|
||||||
state.domain = action.payload;
|
state.domain.licenseSummaryInfo = action.payload;
|
||||||
|
});
|
||||||
|
builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
|
||||||
|
state.domain.accountInfo.companyName = action.payload.companyName;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { openSnackbar } from "features/ui/uiSlice";
|
|||||||
import { getAccessToken } from "features/auth";
|
import { getAccessToken } from "features/auth";
|
||||||
import {
|
import {
|
||||||
AccountsApi,
|
AccountsApi,
|
||||||
|
GetCompanyNameResponse,
|
||||||
GetLicenseSummaryResponse,
|
GetLicenseSummaryResponse,
|
||||||
PartnerLicenseInfo,
|
PartnerLicenseInfo,
|
||||||
} from "../../../api/api";
|
} from "../../../api/api";
|
||||||
@ -66,3 +67,59 @@ export const getLicenseSummaryAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getCompanyNameAsync = createAsyncThunk<
|
||||||
|
// 正常時の戻り値の型
|
||||||
|
GetCompanyNameResponse,
|
||||||
|
// 引数
|
||||||
|
{ selectedRow?: PartnerLicenseInfo },
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("licenses/getCompanyNameAsync", async (args, thunkApi) => {
|
||||||
|
// 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 accountsApi = new AccountsApi(config);
|
||||||
|
try {
|
||||||
|
const getMyAccountResponse = await accountsApi.getMyAccount({
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
const { selectedRow } = args;
|
||||||
|
// 引数がない場合は自分のアカウントID取得
|
||||||
|
const accountId =
|
||||||
|
selectedRow?.accountId ?? getMyAccountResponse?.data?.account?.accountId;
|
||||||
|
|
||||||
|
if (accountId !== undefined) {
|
||||||
|
const getCompanyNameResponse = await accountsApi.getCompanyName(
|
||||||
|
{ accountId },
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return getCompanyNameResponse.data;
|
||||||
|
}
|
||||||
|
throw new Error("accountId is undefined");
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
|
||||||
|
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||||
|
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: errorMessage,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import { RootState } from "app/store";
|
|||||||
|
|
||||||
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
|
||||||
export const selecLicenseSummaryInfo = (state: RootState) =>
|
export const selecLicenseSummaryInfo = (state: RootState) =>
|
||||||
state.licenseSummary.domain;
|
state.licenseSummary.domain.licenseSummaryInfo;
|
||||||
|
|
||||||
|
export const selectCompanyName = (state: RootState) =>
|
||||||
|
state.licenseSummary.domain.accountInfo.companyName;
|
||||||
|
|
||||||
export const selectIsLoading = (state: RootState) => state.license;
|
export const selectIsLoading = (state: RootState) => state.license;
|
||||||
|
|||||||
@ -4,17 +4,22 @@ export interface LicenseSummaryState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Domain {
|
export interface Domain {
|
||||||
totalLicense: number;
|
licenseSummaryInfo: {
|
||||||
allocatedLicense: number;
|
totalLicense: number;
|
||||||
reusableLicense: number;
|
allocatedLicense: number;
|
||||||
freeLicense: number;
|
reusableLicense: number;
|
||||||
expiringWithin14daysLicense: number;
|
freeLicense: number;
|
||||||
issueRequesting: number;
|
expiringWithin14daysLicense: number;
|
||||||
numberOfRequesting: number;
|
issueRequesting: number;
|
||||||
shortage: number;
|
numberOfRequesting: number;
|
||||||
storageSize: number;
|
shortage: number;
|
||||||
usedSize: number;
|
storageSize: number;
|
||||||
isStorageAvailable: boolean;
|
usedSize: number;
|
||||||
|
isStorageAvailable: boolean;
|
||||||
|
};
|
||||||
|
accountInfo: {
|
||||||
|
companyName: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Apps {
|
export interface Apps {
|
||||||
|
|||||||
@ -5,4 +5,5 @@
|
|||||||
export const TERMS_DOCUMENT_TYPE = {
|
export const TERMS_DOCUMENT_TYPE = {
|
||||||
DPA: "DPA",
|
DPA: "DPA",
|
||||||
EULA: "EULA",
|
EULA: "EULA",
|
||||||
|
PRIVACY_NOTICE: "PrivacyNotice",
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export const updateAcceptedVersionAsync = createAsyncThunk<
|
|||||||
updateAccceptVersions: {
|
updateAccceptVersions: {
|
||||||
acceptedVerDPA: string;
|
acceptedVerDPA: string;
|
||||||
acceptedVerEULA: string;
|
acceptedVerEULA: string;
|
||||||
|
acceptedVerPrivacyNotice: string;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -140,6 +141,8 @@ export const updateAcceptedVersionAsync = createAsyncThunk<
|
|||||||
{
|
{
|
||||||
idToken,
|
idToken,
|
||||||
acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA,
|
acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA,
|
||||||
|
acceptedPrivacyNoticeVersion:
|
||||||
|
updateAccceptVersions.acceptedVerPrivacyNotice,
|
||||||
acceptedDPAVersion: !(TIERS.TIER5 === tier.toString())
|
acceptedDPAVersion: !(TIERS.TIER5 === tier.toString())
|
||||||
? updateAccceptVersions.acceptedVerDPA
|
? updateAccceptVersions.acceptedVerDPA
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@ -14,7 +14,11 @@ export const selectTermVersions = (state: RootState) => {
|
|||||||
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA
|
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA
|
||||||
)?.version || "";
|
)?.version || "";
|
||||||
|
|
||||||
return { acceptedVerDPA, acceptedVerEULA };
|
const acceptedVerPrivacyNotice =
|
||||||
|
termsInfo.find(
|
||||||
|
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.PRIVACY_NOTICE
|
||||||
|
)?.version || "";
|
||||||
|
return { acceptedVerDPA, acceptedVerEULA, acceptedVerPrivacyNotice };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const selectTier = (state: RootState) => state.terms.domain.tier;
|
export const selectTier = (state: RootState) => state.terms.domain.tier;
|
||||||
|
|||||||
126
dictation_client/src/pages/DictationPage/filePropertyPopup.tsx
Normal file
126
dictation_client/src/pages/DictationPage/filePropertyPopup.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import styles from "styles/app.module.scss";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { selectSelectedFileTask, selectIsLoading } from "features/dictation";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import close from "../../assets/images/close.svg";
|
||||||
|
import lock from "../../assets/images/lock.svg";
|
||||||
|
|
||||||
|
interface FilePropertyPopupProps {
|
||||||
|
onClose: (isChanged: boolean) => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
|
||||||
|
const { onClose, isOpen } = props;
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
|
// ポップアップを閉じる処理
|
||||||
|
const closePopup = useCallback(() => {
|
||||||
|
onClose(false);
|
||||||
|
}, [onClose]);
|
||||||
|
const selectedFileTask = useSelector(selectSelectedFileTask);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||||
|
<div className={styles.modalBox}>
|
||||||
|
<p className={styles.modalTitle}>
|
||||||
|
{t(getTranslationID("dictationPage.label.fileProperty"))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={closePopup}
|
||||||
|
style={{ pointerEvents: isLoading ? "none" : "auto" }}
|
||||||
|
>
|
||||||
|
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<dl className={`${styles.formList} ${styles.property} ${styles.hasbg}`}>
|
||||||
|
<dt className={styles.formTitle}>
|
||||||
|
{t(getTranslationID("dictationPage.label.general"))}
|
||||||
|
</dt>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.fileName.replace(".zip", "") ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.fileSize"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.fileSize ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.fileLength"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.audioDuration ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.authorId"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.authorId ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.workType"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.workType ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.priority"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.priority ?? ""}</dd>
|
||||||
|
<dt>
|
||||||
|
{t(getTranslationID("dictationPage.label.recordingStartedDate"))}
|
||||||
|
</dt>
|
||||||
|
<dd>{selectedFileTask?.audioCreatedDate ?? ""}</dd>
|
||||||
|
<dt>
|
||||||
|
{t(getTranslationID("dictationPage.label.recordingFinishedDate"))}
|
||||||
|
</dt>
|
||||||
|
<dd>{selectedFileTask?.audioFinishedDate ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.uploadDate"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.audioUploadedDate ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.encryption"))}</dt>
|
||||||
|
<dd>
|
||||||
|
{selectedFileTask?.isEncrypted ? (
|
||||||
|
<img src={lock} alt="encrypted" />
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem1"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[0].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem2"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[1].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem3"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[2].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem4"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[3].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem5"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[4].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem6"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[5].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem7"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[6].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem8"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[7].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem9"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[8].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.optionItem10"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.optionItemList[9].optionItemValue}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.comment"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.comment ?? ""}</dd>
|
||||||
|
<dt className={styles.formTitle}>
|
||||||
|
{t(getTranslationID("dictationPage.label.job"))}
|
||||||
|
</dt>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.jobNumber"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.jobNumber ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.status"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.status ?? ""}</dd>
|
||||||
|
<dt>
|
||||||
|
{t(
|
||||||
|
getTranslationID("dictationPage.label.transcriptionStartedDate")
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd>{selectedFileTask?.transcriptionStartedDate ?? ""}</dd>
|
||||||
|
<dt>
|
||||||
|
{t(
|
||||||
|
getTranslationID("dictationPage.label.transcriptionFinishedDate")
|
||||||
|
)}
|
||||||
|
</dt>
|
||||||
|
<dd>{selectedFileTask?.transcriptionFinishedDate ?? ""}</dd>
|
||||||
|
<dt>{t(getTranslationID("dictationPage.label.transcriptionist"))}</dt>
|
||||||
|
<dd>{selectedFileTask?.typist?.name ?? ""}</dd>
|
||||||
|
<dd className={`${styles.full} ${styles.alignRight}`}>
|
||||||
|
<a href="" className={`${styles.buttonText}`}>
|
||||||
|
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||||
|
{t(getTranslationID("dictationPage.label.close"))}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
changeParamName,
|
changeParamName,
|
||||||
changeDirection,
|
changeDirection,
|
||||||
changeSelectedTask,
|
changeSelectedTask,
|
||||||
|
openFilePropertyInfo,
|
||||||
SortableColumnType,
|
SortableColumnType,
|
||||||
changeAssignee,
|
changeAssignee,
|
||||||
listTypistsAsync,
|
listTypistsAsync,
|
||||||
@ -48,6 +49,7 @@ import open_in_new from "../../assets/images/open_in_new.svg";
|
|||||||
import { DisPlayInfo } from "./displayInfo";
|
import { DisPlayInfo } from "./displayInfo";
|
||||||
import { ChangeTranscriptionistPopup } from "./changeTranscriptionistPopup";
|
import { ChangeTranscriptionistPopup } from "./changeTranscriptionistPopup";
|
||||||
import { BackupPopup } from "./backupPopup";
|
import { BackupPopup } from "./backupPopup";
|
||||||
|
import { FilePropertyPopup } from "./filePropertyPopup";
|
||||||
|
|
||||||
const DictationPage: React.FC = (): JSX.Element => {
|
const DictationPage: React.FC = (): JSX.Element => {
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
@ -63,6 +65,7 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
isChangeTranscriptionistPopupOpen,
|
isChangeTranscriptionistPopupOpen,
|
||||||
setIsChangeTranscriptionistPopupOpen,
|
setIsChangeTranscriptionistPopupOpen,
|
||||||
] = useState(false);
|
] = useState(false);
|
||||||
|
const [isFilePropertyPopupOpen, setIsFilePropertyPopupOpen] = useState(false);
|
||||||
const [isBackupPopupOpen, setIsBackupPopupOpen] = useState(false);
|
const [isBackupPopupOpen, setIsBackupPopupOpen] = useState(false);
|
||||||
|
|
||||||
const onChangeTranscriptionistPopupOpen = useCallback(
|
const onChangeTranscriptionistPopupOpen = useCallback(
|
||||||
@ -74,6 +77,13 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
[dispatch, setIsChangeTranscriptionistPopupOpen]
|
[dispatch, setIsChangeTranscriptionistPopupOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onClickFileProperty = useCallback(
|
||||||
|
(task: Task) => {
|
||||||
|
dispatch(openFilePropertyInfo({ task }));
|
||||||
|
setIsFilePropertyPopupOpen(true);
|
||||||
|
},
|
||||||
|
[dispatch, setIsFilePropertyPopupOpen]
|
||||||
|
);
|
||||||
// 各カラムの表示/非表示
|
// 各カラムの表示/非表示
|
||||||
const displayColumn = useSelector(selectDisplayInfo);
|
const displayColumn = useSelector(selectDisplayInfo);
|
||||||
|
|
||||||
@ -477,6 +487,10 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
setIsBackupPopupOpen(true);
|
setIsBackupPopupOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onCloseFilePropertyPopup = useCallback(() => {
|
||||||
|
setIsFilePropertyPopupOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const sortIconClass = (
|
const sortIconClass = (
|
||||||
currentParam: SortableColumnType,
|
currentParam: SortableColumnType,
|
||||||
currentDirection: DirectionType,
|
currentDirection: DirectionType,
|
||||||
@ -532,6 +546,10 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BackupPopup isOpen={isBackupPopupOpen} onClose={onCloseBackupPopup} />
|
<BackupPopup isOpen={isBackupPopupOpen} onClose={onCloseBackupPopup} />
|
||||||
|
<FilePropertyPopup
|
||||||
|
isOpen={isFilePropertyPopupOpen}
|
||||||
|
onClose={onCloseFilePropertyPopup}
|
||||||
|
/>
|
||||||
<ChangeTranscriptionistPopup
|
<ChangeTranscriptionistPopup
|
||||||
isOpen={isChangeTranscriptionistPopupOpen}
|
isOpen={isChangeTranscriptionistPopupOpen}
|
||||||
onClose={onClosePopup}
|
onClose={onClosePopup}
|
||||||
@ -1080,7 +1098,8 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a>
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
|
||||||
|
<a onClick={() => onClickFileProperty(x)}>
|
||||||
{t(
|
{t(
|
||||||
getTranslationID(
|
getTranslationID(
|
||||||
"dictationPage.label.fileProperty"
|
"dictationPage.label.fileProperty"
|
||||||
@ -1360,11 +1379,12 @@ const DictationPage: React.FC = (): JSX.Element => {
|
|||||||
<ul className={`${styles.menuAction} ${styles.alignRight}`}>
|
<ul className={`${styles.menuAction} ${styles.alignRight}`}>
|
||||||
<li className={styles.alignLeft}>
|
<li className={styles.alignLeft}>
|
||||||
<a
|
<a
|
||||||
href=""
|
// TODO: 将来的に正式なURLに変更する
|
||||||
|
href="/dictations"
|
||||||
className={`${styles.menuLink} ${styles.isActive}`}
|
className={`${styles.menuLink} ${styles.isActive}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Applications
|
{t(getTranslationID("dictationPage.label.applications"))}
|
||||||
<img
|
<img
|
||||||
src={open_in_new}
|
src={open_in_new}
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@ -8,8 +8,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
|
getCompanyNameAsync,
|
||||||
getLicenseSummaryAsync,
|
getLicenseSummaryAsync,
|
||||||
selecLicenseSummaryInfo,
|
selecLicenseSummaryInfo,
|
||||||
|
selectCompanyName,
|
||||||
} from "features/license/licenseSummary";
|
} from "features/license/licenseSummary";
|
||||||
import { selectSelectedRow } from "features/license/partnerLicense";
|
import { selectSelectedRow } from "features/license/partnerLicense";
|
||||||
import { selectDelegationAccessToken } from "features/auth/selectors";
|
import { selectDelegationAccessToken } from "features/auth/selectors";
|
||||||
@ -61,9 +63,11 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
|
|
||||||
// apiからの値取得関係
|
// apiからの値取得関係
|
||||||
const licenseSummaryInfo = useSelector(selecLicenseSummaryInfo);
|
const licenseSummaryInfo = useSelector(selecLicenseSummaryInfo);
|
||||||
|
const companyName = useSelector(selectCompanyName);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
dispatch(getLicenseSummaryAsync({ selectedRow }));
|
||||||
|
dispatch(getCompanyNameAsync({ selectedRow }));
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@ -118,7 +122,7 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
|
|||||||
</div>
|
</div>
|
||||||
<section className={styles.license}>
|
<section className={styles.license}>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="">{"会社名" /* TODO 会社名を表示する */}</h2>
|
<h2 className="">{companyName}</h2>
|
||||||
<ul className={styles.menuAction}>
|
<ul className={styles.menuAction}>
|
||||||
<li>
|
<li>
|
||||||
{/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */}
|
{/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
|||||||
adminMail,
|
adminMail,
|
||||||
adminPassword,
|
adminPassword,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion: "",
|
||||||
acceptedDpaVersion: "",
|
acceptedDpaVersion: "",
|
||||||
token: "",
|
token: "",
|
||||||
})
|
})
|
||||||
|
|||||||
90
dictation_client/src/pages/SupportPage/index.tsx
Normal file
90
dictation_client/src/pages/SupportPage/index.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Header from "components/header";
|
||||||
|
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
|
import styles from "styles/app.module.scss";
|
||||||
|
|
||||||
|
const SupportPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrap}>
|
||||||
|
<Header />
|
||||||
|
<UpdateTokenTimer />
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div>
|
||||||
|
<div className={styles.pageHeader}>
|
||||||
|
<h1 className={styles.pageTitle}>
|
||||||
|
{t(getTranslationID("supportPage.label.title"))}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className={styles.support}>
|
||||||
|
<div>
|
||||||
|
<h2>{t(getTranslationID("supportPage.label.howToUse"))}</h2>
|
||||||
|
|
||||||
|
<div className={styles.txContents}>
|
||||||
|
<ul className={styles.listDocument}>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
// TODO: 将来的に正式なURLに変更する
|
||||||
|
href="/support"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("supportPage.label.supportPageEnglish")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
// TODO: 将来的に正式なURLに変更する
|
||||||
|
href="/support"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("supportPage.label.supportPageGerman")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
// TODO: 将来的に正式なURLに変更する
|
||||||
|
href="/support"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("supportPage.label.supportPageFrench")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
// TODO: 将来的に正式なURLに変更する
|
||||||
|
href="/support"
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("supportPage.label.supportPageSpanish")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p className={styles.txNormal}>
|
||||||
|
{t(getTranslationID("supportPage.text.notResolved"))}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default SupportPage;
|
||||||
@ -29,9 +29,12 @@ const TermsPage: React.FC = (): JSX.Element => {
|
|||||||
const tier = useSelector(selectTier);
|
const tier = useSelector(selectTier);
|
||||||
|
|
||||||
const [isCheckedEula, setIsCheckedEula] = useState(false);
|
const [isCheckedEula, setIsCheckedEula] = useState(false);
|
||||||
|
const [isCheckedPrivacyNotice, setIsCheckedPrivacyNotice] = useState(false);
|
||||||
const [isCheckedDpa, setIsCheckedDpa] = useState(false);
|
const [isCheckedDpa, setIsCheckedDpa] = useState(false);
|
||||||
|
|
||||||
const [isClickedEulaLink, setIsClickedEulaLink] = useState(false);
|
const [isClickedEulaLink, setIsClickedEulaLink] = useState(false);
|
||||||
|
const [isClickedPrivacyNoticeLink, setIsClickedPrivacyNoticeLink] =
|
||||||
|
useState(false);
|
||||||
const [isClickedDpaLink, setIsClickedDpaLink] = useState(false);
|
const [isClickedDpaLink, setIsClickedDpaLink] = useState(false);
|
||||||
|
|
||||||
// 画面起動時
|
// 画面起動時
|
||||||
@ -52,9 +55,9 @@ const TermsPage: React.FC = (): JSX.Element => {
|
|||||||
// ボタン押下可否判定ロジック
|
// ボタン押下可否判定ロジック
|
||||||
const canClickButton = () => {
|
const canClickButton = () => {
|
||||||
if (isTier5()) {
|
if (isTier5()) {
|
||||||
return isCheckedEula;
|
return isCheckedEula && isCheckedPrivacyNotice;
|
||||||
}
|
}
|
||||||
return isCheckedEula && isCheckedDpa;
|
return isCheckedEula && isCheckedPrivacyNotice && isCheckedDpa;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ボタン押下時処理
|
// ボタン押下時処理
|
||||||
@ -62,7 +65,8 @@ const TermsPage: React.FC = (): JSX.Element => {
|
|||||||
if (
|
if (
|
||||||
localStorageKeyforIdToken &&
|
localStorageKeyforIdToken &&
|
||||||
updateAccceptVersions.acceptedVerDPA !== "" &&
|
updateAccceptVersions.acceptedVerDPA !== "" &&
|
||||||
updateAccceptVersions.acceptedVerEULA !== ""
|
updateAccceptVersions.acceptedVerEULA !== "" &&
|
||||||
|
updateAccceptVersions.acceptedVerPrivacyNotice !== ""
|
||||||
) {
|
) {
|
||||||
const { meta } = await dispatch(
|
const { meta } = await dispatch(
|
||||||
updateAcceptedVersionAsync({
|
updateAcceptedVersionAsync({
|
||||||
@ -132,7 +136,42 @@ const TermsPage: React.FC = (): JSX.Element => {
|
|||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
</dd>
|
</dd>
|
||||||
{/* 第五階層以外の場合はEulaのリンクをあわせて表示する */}
|
<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 PrivacyNotice用の利用規約リンクが決定したら設定を行う */
|
||||||
|
target="_blank"
|
||||||
|
className={styles.linkTx}
|
||||||
|
onClick={() => setIsClickedPrivacyNoticeLink(true)}
|
||||||
|
data-tag="open-pricacy-notice"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
getTranslationID("termsPage.label.linkOfPrivacyNotice")
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
{` ${t(getTranslationID("termsPage.label.forOdds"))}`}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={isCheckedPrivacyNotice}
|
||||||
|
className={styles.formCheck}
|
||||||
|
value=""
|
||||||
|
onChange={(e) =>
|
||||||
|
setIsCheckedPrivacyNotice(e.target.checked)
|
||||||
|
}
|
||||||
|
disabled={!isClickedPrivacyNoticeLink}
|
||||||
|
data-tag="accept-privacy-notice"
|
||||||
|
/>
|
||||||
|
{t(
|
||||||
|
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
{/* 第五階層以外の場合はDPAのリンクをあわせて表示する */}
|
||||||
{!isTier5() && (
|
{!isTier5() && (
|
||||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -2306,8 +2306,7 @@ tr.isSelected .menuInTable li a.isDisable {
|
|||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input + label:hover,
|
.formChange ul.chooseMember li input + label:hover,
|
||||||
.formChange ul.holdMember li input + label:hover {
|
.formChange ul.holdMember li input + label:hover {
|
||||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||||
center;
|
|
||||||
background-size: 1.3rem;
|
background-size: 1.3rem;
|
||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input:checked + label,
|
.formChange ul.chooseMember li input:checked + label,
|
||||||
@ -2318,8 +2317,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
|||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input:checked + label:hover,
|
.formChange ul.chooseMember li input:checked + label:hover,
|
||||||
.formChange ul.holdMember li input:checked + label:hover {
|
.formChange ul.holdMember li input:checked + label:hover {
|
||||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||||
right center;
|
center;
|
||||||
background-size: 1.3rem;
|
background-size: 1.3rem;
|
||||||
}
|
}
|
||||||
.formChange > p {
|
.formChange > p {
|
||||||
@ -2472,8 +2471,7 @@ tr.isSelected .menuInTable li a.isDisable {
|
|||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input + label:hover,
|
.formChange ul.chooseMember li input + label:hover,
|
||||||
.formChange ul.holdMember li input + label:hover {
|
.formChange ul.holdMember li input + label:hover {
|
||||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||||
center;
|
|
||||||
background-size: 1.3rem;
|
background-size: 1.3rem;
|
||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input:checked + label,
|
.formChange ul.chooseMember li input:checked + label,
|
||||||
@ -2484,8 +2482,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
|||||||
}
|
}
|
||||||
.formChange ul.chooseMember li input:checked + label:hover,
|
.formChange ul.chooseMember li input:checked + label:hover,
|
||||||
.formChange ul.holdMember li input:checked + label:hover {
|
.formChange ul.holdMember li input:checked + label:hover {
|
||||||
background: #e6e6e6 url(../images/arrow_circle_right.svg) no-repeat right
|
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||||
center;
|
right center;
|
||||||
background-size: 1.3rem;
|
background-size: 1.3rem;
|
||||||
}
|
}
|
||||||
.formChange > p {
|
.formChange > p {
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"headerDictations": "(de)Dictations",
|
"headerDictations": "(de)Dictations",
|
||||||
"headerWorkflow": "(de)Workflow",
|
"headerWorkflow": "(de)Workflow",
|
||||||
"headerPartners": "(de)Partners",
|
"headerPartners": "(de)Partners",
|
||||||
|
"headerSupport": "(de)Support",
|
||||||
"tier1": "(de)Admin",
|
"tier1": "(de)Admin",
|
||||||
"tier2": "(de)BC",
|
"tier2": "(de)BC",
|
||||||
"tier3": "(de)Distributor",
|
"tier3": "(de)Distributor",
|
||||||
@ -250,7 +251,11 @@
|
|||||||
"poolTranscriptionist": "Transkriptionsliste",
|
"poolTranscriptionist": "Transkriptionsliste",
|
||||||
"fileBackup": "(de)File Backup",
|
"fileBackup": "(de)File Backup",
|
||||||
"downloadForBackup": "(de)Download for backup",
|
"downloadForBackup": "(de)Download for backup",
|
||||||
"cancelDictation": "(de)Cancel Dictation"
|
"applications": "(de)Applications",
|
||||||
|
"cancelDictation": "(de)Cancel Dictation",
|
||||||
|
"general": "(de)General",
|
||||||
|
"job": "(de)Job",
|
||||||
|
"close": "(de)Close"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cardLicenseIssuePopupPage": {
|
"cardLicenseIssuePopupPage": {
|
||||||
@ -527,9 +532,31 @@
|
|||||||
"title": "(de)Terms of Use has updated. Please confirm again.",
|
"title": "(de)Terms of Use has updated. Please confirm again.",
|
||||||
"linkOfEula": "(de)Click here to read the terms of use.",
|
"linkOfEula": "(de)Click here to read the terms of use.",
|
||||||
"linkOfDpa": "(de)Click here to read the terms of use.",
|
"linkOfDpa": "(de)Click here to read the terms of use.",
|
||||||
|
"linkOfPrivacyNotice": "(de)Click here to read the terms of use.",
|
||||||
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
|
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
|
||||||
"forOdds": "(de)for ODDS.",
|
"forOdds": "(de)for ODDS.",
|
||||||
"button": "(de)Continue"
|
"button": "(de)Continue",
|
||||||
|
"linkOfPrivacyNotice": "(de)Click here to read the terms of use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supportPage": {
|
||||||
|
"label": {
|
||||||
|
"title": "(de)Support",
|
||||||
|
"howToUse": "(de)How to use the system",
|
||||||
|
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||||
|
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||||
|
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||||
|
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"notResolved": "(de)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filePropertyPopup": {
|
||||||
|
"label": {
|
||||||
|
"general": "(de)General",
|
||||||
|
"job": "(de)Job",
|
||||||
|
"close": "(de)Close"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"headerDictations": "Dictations",
|
"headerDictations": "Dictations",
|
||||||
"headerWorkflow": "Workflow",
|
"headerWorkflow": "Workflow",
|
||||||
"headerPartners": "Partners",
|
"headerPartners": "Partners",
|
||||||
|
"headerSupport": "Support",
|
||||||
"tier1": "Admin",
|
"tier1": "Admin",
|
||||||
"tier2": "BC",
|
"tier2": "BC",
|
||||||
"tier3": "Distributor",
|
"tier3": "Distributor",
|
||||||
@ -250,7 +251,11 @@
|
|||||||
"poolTranscriptionist": "Transcription List",
|
"poolTranscriptionist": "Transcription List",
|
||||||
"fileBackup": "File Backup",
|
"fileBackup": "File Backup",
|
||||||
"downloadForBackup": "Download for backup",
|
"downloadForBackup": "Download for backup",
|
||||||
"cancelDictation": "Cancel Dictation"
|
"applications": "Applications",
|
||||||
|
"cancelDictation": "Cancel Dictation",
|
||||||
|
"general": "General",
|
||||||
|
"job": "Job",
|
||||||
|
"close": "Close"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cardLicenseIssuePopupPage": {
|
"cardLicenseIssuePopupPage": {
|
||||||
@ -527,9 +532,31 @@
|
|||||||
"title": "Terms of Use has updated. Please confirm again.",
|
"title": "Terms of Use has updated. Please confirm again.",
|
||||||
"linkOfEula": "Click here to read the terms of use.",
|
"linkOfEula": "Click here to read the terms of use.",
|
||||||
"linkOfDpa": "Click here to read the terms of use.",
|
"linkOfDpa": "Click here to read the terms of use.",
|
||||||
|
"linkOfPrivacyNotice": "Click here to read the terms of use.",
|
||||||
"checkBoxForConsent": "Yes, I agree to the terms of use.",
|
"checkBoxForConsent": "Yes, I agree to the terms of use.",
|
||||||
"forOdds": "for ODDS.",
|
"forOdds": "for ODDS.",
|
||||||
"button": "Continue"
|
"button": "Continue",
|
||||||
|
"linkOfPrivacyNotice": "Click here to read the terms of use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supportPage": {
|
||||||
|
"label": {
|
||||||
|
"title": "Support",
|
||||||
|
"howToUse": "How to use the system",
|
||||||
|
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||||
|
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||||
|
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||||
|
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"notResolved": "If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filePropertyPopup": {
|
||||||
|
"label": {
|
||||||
|
"general": "General",
|
||||||
|
"job": "Job",
|
||||||
|
"close": "Close"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"headerDictations": "(es)Dictations",
|
"headerDictations": "(es)Dictations",
|
||||||
"headerWorkflow": "(es)Workflow",
|
"headerWorkflow": "(es)Workflow",
|
||||||
"headerPartners": "(es)Partners",
|
"headerPartners": "(es)Partners",
|
||||||
|
"headerSupport": "(es)Support",
|
||||||
"tier1": "(es)Admin",
|
"tier1": "(es)Admin",
|
||||||
"tier2": "(es)BC",
|
"tier2": "(es)BC",
|
||||||
"tier3": "(es)Distributor",
|
"tier3": "(es)Distributor",
|
||||||
@ -250,7 +251,11 @@
|
|||||||
"poolTranscriptionist": "Lista de transcriptor",
|
"poolTranscriptionist": "Lista de transcriptor",
|
||||||
"fileBackup": "(es)File Backup",
|
"fileBackup": "(es)File Backup",
|
||||||
"downloadForBackup": "(es)Download for backup",
|
"downloadForBackup": "(es)Download for backup",
|
||||||
"cancelDictation": "(es)Cancel Dictation"
|
"applications": "(es)Applications",
|
||||||
|
"cancelDictation": "(es)Cancel Dictation",
|
||||||
|
"general": "(es)General",
|
||||||
|
"job": "(es)Job",
|
||||||
|
"close": "(es)Close"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cardLicenseIssuePopupPage": {
|
"cardLicenseIssuePopupPage": {
|
||||||
@ -527,9 +532,31 @@
|
|||||||
"title": "(es)Terms of Use has updated. Please confirm again.",
|
"title": "(es)Terms of Use has updated. Please confirm again.",
|
||||||
"linkOfEula": "(es)Click here to read the terms of use.",
|
"linkOfEula": "(es)Click here to read the terms of use.",
|
||||||
"linkOfDpa": "(es)Click here to read the terms of use.",
|
"linkOfDpa": "(es)Click here to read the terms of use.",
|
||||||
|
"linkOfPrivacyNotice": "(es)Click here to read the terms of use.",
|
||||||
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
|
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
|
||||||
"forOdds": "(es)for ODDS.",
|
"forOdds": "(es)for ODDS.",
|
||||||
"button": "(es)Continue"
|
"button": "(es)Continue",
|
||||||
|
"linkOfPrivacyNotice": "(es)Click here to read the terms of use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supportPage": {
|
||||||
|
"label": {
|
||||||
|
"title": "(es)Support",
|
||||||
|
"howToUse": "(es)How to use the system",
|
||||||
|
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||||
|
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||||
|
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||||
|
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"notResolved": "(es)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filePropertyPopup": {
|
||||||
|
"label": {
|
||||||
|
"general": "(es)General",
|
||||||
|
"job": "(es)Job",
|
||||||
|
"close": "(es)Close"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
"headerDictations": "(fr)Dictations",
|
"headerDictations": "(fr)Dictations",
|
||||||
"headerWorkflow": "(fr)Workflow",
|
"headerWorkflow": "(fr)Workflow",
|
||||||
"headerPartners": "(fr)Partners",
|
"headerPartners": "(fr)Partners",
|
||||||
|
"headerSupport": "(fr)Support",
|
||||||
"tier1": "(fr)Admin",
|
"tier1": "(fr)Admin",
|
||||||
"tier2": "(fr)BC",
|
"tier2": "(fr)BC",
|
||||||
"tier3": "(fr)Distributor",
|
"tier3": "(fr)Distributor",
|
||||||
@ -250,7 +251,11 @@
|
|||||||
"poolTranscriptionist": "Liste de transcriptionniste",
|
"poolTranscriptionist": "Liste de transcriptionniste",
|
||||||
"fileBackup": "(fr)File Backup",
|
"fileBackup": "(fr)File Backup",
|
||||||
"downloadForBackup": "(fr)Download for backup",
|
"downloadForBackup": "(fr)Download for backup",
|
||||||
"cancelDictation": "(fr)Cancel Dictation"
|
"applications": "(fr)Applications",
|
||||||
|
"cancelDictation": "(fr)Cancel Dictation",
|
||||||
|
"general": "(fr)General",
|
||||||
|
"job": "(fr)Job",
|
||||||
|
"close": "(fr)Close"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cardLicenseIssuePopupPage": {
|
"cardLicenseIssuePopupPage": {
|
||||||
@ -527,9 +532,31 @@
|
|||||||
"title": "(fr)Terms of Use has updated. Please confirm again.",
|
"title": "(fr)Terms of Use has updated. Please confirm again.",
|
||||||
"linkOfEula": "(fr)Click here to read the terms of use.",
|
"linkOfEula": "(fr)Click here to read the terms of use.",
|
||||||
"linkOfDpa": "(fr)Click here to read the terms of use.",
|
"linkOfDpa": "(fr)Click here to read the terms of use.",
|
||||||
|
"linkOfPrivacyNotice": "(fr)Click here to read the terms of use.",
|
||||||
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
|
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
|
||||||
"forOdds": "(fr)for ODDS.",
|
"forOdds": "(fr)for ODDS.",
|
||||||
"button": "(fr)Continue"
|
"button": "(fr)Continue",
|
||||||
|
"linkOfPrivacyNotice": "(fr)Click here to read the terms of use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supportPage": {
|
||||||
|
"label": {
|
||||||
|
"title": "(fr)Support",
|
||||||
|
"howToUse": "(fr)How to use the system",
|
||||||
|
"supportPageEnglish": "OMDS Cloud User Guide",
|
||||||
|
"supportPageGerman": "OMDS Cloud Benutzerhandbuch",
|
||||||
|
"supportPageFrench": "OMDS Cloud Mode d'emploi",
|
||||||
|
"supportPageSpanish": "OMDS Cloud Guía del usuario"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"notResolved": "(fr)If the problem persists even after referring to the user guide, please contact a higher-level person in charge."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filePropertyPopup": {
|
||||||
|
"label": {
|
||||||
|
"general": "(fr)General",
|
||||||
|
"job": "(fr)Job",
|
||||||
|
"close": "(fr)Close"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
# To enable ssh & remote debugging on app service change the base image to the one below
|
|
||||||
# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice
|
|
||||||
FROM mcr.microsoft.com/azure-functions/node:4-node18
|
|
||||||
|
|
||||||
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
|
|
||||||
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
|
|
||||||
|
|
||||||
COPY . /home/site/wwwroot
|
|
||||||
|
|
||||||
RUN cd /home/site/wwwroot && \
|
|
||||||
npm install && \
|
|
||||||
npm run build
|
|
||||||
@ -11,5 +11,10 @@
|
|||||||
"extensionBundle": {
|
"extensionBundle": {
|
||||||
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
"id": "Microsoft.Azure.Functions.ExtensionBundle",
|
||||||
"version": "[4.*, 5.0.0)"
|
"version": "[4.*, 5.0.0)"
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"strategy": "fixedDelay",
|
||||||
|
"maxRetryCount": 3,
|
||||||
|
"delayInterval": "00:00:10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import { AdB2cResponse, AdB2cUser } from "./types/types";
|
|||||||
import { error } from "console";
|
import { error } from "console";
|
||||||
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
|
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { createRedisClient } from "../redis/redis";
|
|
||||||
import { InvocationContext } from "@azure/functions";
|
import { InvocationContext } from "@azure/functions";
|
||||||
|
import { RedisClient } from "redis";
|
||||||
|
|
||||||
export class Adb2cTooManyRequestsError extends Error {}
|
export class Adb2cTooManyRequestsError extends Error {}
|
||||||
|
|
||||||
@ -23,16 +23,24 @@ export class AdB2cService {
|
|||||||
) {
|
) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const credential = new ClientSecretCredential(
|
try {
|
||||||
process.env.ADB2C_TENANT_ID,
|
const credential = new ClientSecretCredential(
|
||||||
process.env.ADB2C_CLIENT_ID,
|
process.env.ADB2C_TENANT_ID,
|
||||||
process.env.ADB2C_CLIENT_SECRET
|
process.env.ADB2C_CLIENT_ID,
|
||||||
);
|
process.env.ADB2C_CLIENT_SECRET
|
||||||
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
);
|
||||||
scopes: ["https://graph.microsoft.com/.default"],
|
const authProvider = new TokenCredentialAuthenticationProvider(
|
||||||
});
|
credential,
|
||||||
|
{
|
||||||
|
scopes: ["https://graph.microsoft.com/.default"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.graphClient = Client.initWithMiddleware({ authProvider });
|
this.graphClient = Client.initWithMiddleware({ authProvider });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,9 +50,9 @@ export class AdB2cService {
|
|||||||
*/
|
*/
|
||||||
async getUsers(
|
async getUsers(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
externalIds: string[]
|
externalIds: string[]
|
||||||
): Promise<AdB2cUser[] | undefined> {
|
): Promise<AdB2cUser[]> {
|
||||||
const redisClient = createRedisClient();
|
|
||||||
try {
|
try {
|
||||||
const b2cUsers: AdB2cUser[] = [];
|
const b2cUsers: AdB2cUser[] = [];
|
||||||
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
|
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
|
||||||
@ -123,7 +131,7 @@ export class AdB2cService {
|
|||||||
|
|
||||||
return [...cachedUsers, ...b2cUsers];
|
return [...cachedUsers, ...b2cUsers];
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return b2cUsers;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { statusCode } = e;
|
const { statusCode } = e;
|
||||||
@ -132,7 +140,6 @@ export class AdB2cService {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
redisClient.quit;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,5 @@
|
|||||||
export const ADB2C_PREFIX = "adb2c-external-id:"
|
export const ADB2C_PREFIX = "adb2c-external-id:";
|
||||||
|
export const SEND_COMPLETE_PREFIX = "send-complete-id:";
|
||||||
|
export const MAIL_U103 = "[U103]";
|
||||||
|
export const MAIL_U104 = "[U104]";
|
||||||
|
export const DONE = "Done"; // メール送信成功時にredisにキャッシュする値
|
||||||
|
|||||||
25
dictation_function/src/common/cache/index.ts
vendored
25
dictation_function/src/common/cache/index.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import { ADB2C_PREFIX } from './constants';
|
import { ADB2C_PREFIX, SEND_COMPLETE_PREFIX } from "./constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ADB2Cのユーザー格納用のキーを生成する
|
* ADB2Cのユーザー格納用のキーを生成する
|
||||||
@ -6,8 +6,8 @@ import { ADB2C_PREFIX } from './constants';
|
|||||||
* @returns キャッシュのキー
|
* @returns キャッシュのキー
|
||||||
*/
|
*/
|
||||||
export const makeADB2CKey = (externalId: string): string => {
|
export const makeADB2CKey = (externalId: string): string => {
|
||||||
return `${ADB2C_PREFIX}${externalId}`;
|
return `${ADB2C_PREFIX}${externalId}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
|
||||||
@ -15,5 +15,20 @@ export const makeADB2CKey = (externalId: string): string => {
|
|||||||
* @returns 外部ユーザーID
|
* @returns 外部ユーザーID
|
||||||
*/
|
*/
|
||||||
export const restoreAdB2cID = (key: string): string => {
|
export const restoreAdB2cID = (key: string): string => {
|
||||||
return key.replace(ADB2C_PREFIX, '');
|
return key.replace(ADB2C_PREFIX, "");
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ライセンスアラートメール送信履歴格納用のキーを生成する
|
||||||
|
* @param formattedDate 当日の日付(YYYY:MM:DD)
|
||||||
|
* @param externalId 外部ユーザーID
|
||||||
|
* @param mail メール種別
|
||||||
|
* @returns キャッシュのキー
|
||||||
|
*/
|
||||||
|
export const makeSendCompKey = (
|
||||||
|
formattedDate: string,
|
||||||
|
externalId: string,
|
||||||
|
mail: string
|
||||||
|
): string => {
|
||||||
|
return `${SEND_COMPLETE_PREFIX}${formattedDate}${mail}${externalId}`;
|
||||||
|
};
|
||||||
|
|||||||
@ -19,36 +19,166 @@ import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents
|
|||||||
import { AdB2cService } from "../adb2c/adb2c";
|
import { AdB2cService } from "../adb2c/adb2c";
|
||||||
import { SendGridService } from "../sendgrid/sendgrid";
|
import { SendGridService } from "../sendgrid/sendgrid";
|
||||||
import { getMailFrom } from "../common/getEnv/getEnv";
|
import { getMailFrom } from "../common/getEnv/getEnv";
|
||||||
|
import { createRedisClient } from "../redis/redis";
|
||||||
|
import { RedisClient } from "redis";
|
||||||
|
import { promisify } from "util";
|
||||||
|
import { makeSendCompKey } from "../common/cache";
|
||||||
|
import {
|
||||||
|
MAIL_U103,
|
||||||
|
MAIL_U104,
|
||||||
|
SEND_COMPLETE_PREFIX,
|
||||||
|
DONE,
|
||||||
|
} from "../common/cache/constants";
|
||||||
|
|
||||||
export async function licenseAlertProcessing(
|
export async function licenseAlertProcessing(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
|
redisClient: RedisClient,
|
||||||
sendgrid: SendGridService,
|
sendgrid: SendGridService,
|
||||||
adb2c: AdB2cService
|
adb2c: AdB2cService
|
||||||
) {
|
) {
|
||||||
context.log("[IN]licenseAlertProcessing");
|
try {
|
||||||
const mailFrom = getMailFrom();
|
context.log("[IN]licenseAlertProcessing");
|
||||||
const accountRepository = datasource.getRepository(Account);
|
|
||||||
|
|
||||||
// 第五のアカウントを取得
|
// redisのキー用
|
||||||
const accounts = await accountRepository.find({
|
const currentDate = new DateWithZeroTime();
|
||||||
where: {
|
const formattedDate = `${currentDate.getFullYear()}-${(
|
||||||
tier: TIERS.TIER5,
|
currentDate.getMonth() + 1
|
||||||
},
|
).toString()}-${currentDate.getDate().toString()}`;
|
||||||
relations: {
|
const keysAsync = promisify(redisClient.keys).bind(redisClient);
|
||||||
primaryAdminUser: true,
|
|
||||||
secondaryAdminUser: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const licenseRepository = datasource.getRepository(License);
|
// メール送信対象のアカウント情報を取得
|
||||||
const currentDate = new DateWithZeroTime();
|
const sendTargetAccounts = await getAlertMailTargetAccount(
|
||||||
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
context,
|
||||||
const currentDateWithZeroTime = new DateWithZeroTime();
|
datasource
|
||||||
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
);
|
||||||
const sendTargetAccounts = [] as accountInfo[];
|
|
||||||
|
|
||||||
const counts = async () => {
|
// adb2cからメールアドレスを取得し、上記で取得したアカウントにマージする
|
||||||
|
const sendTargetAccountsMargedAdb2c = await createAccountInfo(
|
||||||
|
context,
|
||||||
|
redisClient,
|
||||||
|
adb2c,
|
||||||
|
sendTargetAccounts
|
||||||
|
);
|
||||||
|
|
||||||
|
// メール送信
|
||||||
|
await sendAlertMail(
|
||||||
|
context,
|
||||||
|
redisClient,
|
||||||
|
sendgrid,
|
||||||
|
sendTargetAccountsMargedAdb2c,
|
||||||
|
formattedDate
|
||||||
|
);
|
||||||
|
|
||||||
|
// 最後まで処理が正常に通ったら、redisに保存した送信情報を削除する
|
||||||
|
try {
|
||||||
|
const delAsync = promisify(redisClient.del).bind(redisClient);
|
||||||
|
|
||||||
|
const keys = await keysAsync(`${SEND_COMPLETE_PREFIX}${formattedDate}*`);
|
||||||
|
console.log(`delete terget:${keys}`);
|
||||||
|
if (keys.length > 0) {
|
||||||
|
const delResult = await delAsync(...keys);
|
||||||
|
console.log(`delete number:${delResult}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.log("redis delete failed");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
context.log("[OUT]licenseAlertProcessing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function licenseAlert(
|
||||||
|
myTimer: Timer,
|
||||||
|
context: InvocationContext
|
||||||
|
): Promise<void> {
|
||||||
|
context.log("[IN]licenseAlert");
|
||||||
|
|
||||||
|
dotenv.config({ path: ".env" });
|
||||||
|
dotenv.config({ path: ".env.local", override: true });
|
||||||
|
let datasource: DataSource;
|
||||||
|
try {
|
||||||
|
datasource = new DataSource({
|
||||||
|
type: "mysql",
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: Number(process.env.DB_PORT),
|
||||||
|
username: process.env.DB_USERNAME,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
entities: [User, Account, License],
|
||||||
|
});
|
||||||
|
await datasource.initialize();
|
||||||
|
} catch (e) {
|
||||||
|
context.log("database initialize failed.");
|
||||||
|
context.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
let redisClient: RedisClient;
|
||||||
|
try {
|
||||||
|
// redis接続
|
||||||
|
redisClient = createRedisClient();
|
||||||
|
} catch (e) {
|
||||||
|
context.log("redis client create failed.");
|
||||||
|
context.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const adb2c = new AdB2cService();
|
||||||
|
const sendgrid = new SendGridService();
|
||||||
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
datasource,
|
||||||
|
redisClient,
|
||||||
|
sendgrid,
|
||||||
|
adb2c
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
context.log("licenseAlertProcessing failed.");
|
||||||
|
context.error(e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
await datasource.destroy();
|
||||||
|
redisClient.quit;
|
||||||
|
context.log("[OUT]licenseAlert");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アラートメールを送信する対象のアカウントを取得する
|
||||||
|
* @param context
|
||||||
|
* @param datasource
|
||||||
|
* @returns accountInfo[] メール送信対象のアカウント情報
|
||||||
|
*/
|
||||||
|
async function getAlertMailTargetAccount(
|
||||||
|
context: InvocationContext,
|
||||||
|
datasource: DataSource
|
||||||
|
): Promise<accountInfo[]> {
|
||||||
|
try {
|
||||||
|
context.log("[IN]getAlertMailTargetAccount");
|
||||||
|
const currentDate = new DateWithZeroTime();
|
||||||
|
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
|
||||||
|
const currentDateWithZeroTime = new DateWithZeroTime();
|
||||||
|
const currentDateWithDayEndTime = new DateWithDayEndTime();
|
||||||
|
|
||||||
|
// 第五のアカウントを取得
|
||||||
|
const accountRepository = datasource.getRepository(Account);
|
||||||
|
const accounts = await accountRepository.find({
|
||||||
|
where: {
|
||||||
|
tier: TIERS.TIER5,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
primaryAdminUser: true,
|
||||||
|
secondaryAdminUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendTargetAccounts = [] as accountInfo[];
|
||||||
|
const licenseRepository = datasource.getRepository(License);
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
|
||||||
const allocatableLicenseWithMargin = await licenseRepository.count({
|
const allocatableLicenseWithMargin = await licenseRepository.count({
|
||||||
@ -109,6 +239,7 @@ export async function licenseAlertProcessing(
|
|||||||
let primaryAdminExternalId: string | undefined;
|
let primaryAdminExternalId: string | undefined;
|
||||||
let secondaryAdminExternalId: string | undefined;
|
let secondaryAdminExternalId: string | undefined;
|
||||||
let parentCompanyName: string | undefined;
|
let parentCompanyName: string | undefined;
|
||||||
|
|
||||||
if (shortage !== 0 || userCount !== 0) {
|
if (shortage !== 0 || userCount !== 0) {
|
||||||
primaryAdminExternalId = account.primaryAdminUser
|
primaryAdminExternalId = account.primaryAdminUser
|
||||||
? account.primaryAdminUser.external_id
|
? account.primaryAdminUser.external_id
|
||||||
@ -143,12 +274,33 @@ export async function licenseAlertProcessing(
|
|||||||
secondaryAdminEmail: undefined,
|
secondaryAdminEmail: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
return sendTargetAccounts;
|
||||||
await counts();
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log("getAlertMailTargetAccount failed.");
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
context.log("[OUT]getAlertMailTargetAccount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Azure AD B2Cからユーザ情報を取得し、アカウント情報を作成する
|
||||||
|
* @param context
|
||||||
|
* @param redisClient
|
||||||
|
* @param adb2c
|
||||||
|
* @param sendTargetAccounts RDBから取得したアカウント情報
|
||||||
|
* @returns accountInfo[] メール送信対象のアカウント情報
|
||||||
|
*/
|
||||||
|
async function createAccountInfo(
|
||||||
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
|
adb2c: AdB2cService,
|
||||||
|
sendTargetAccounts: accountInfo[]
|
||||||
|
): Promise<accountInfo[]> {
|
||||||
// ADB2Cからユーザーを取得する用の外部ID配列を作成
|
// ADB2Cからユーザーを取得する用の外部ID配列を作成
|
||||||
const externalIds = [] as string[];
|
const externalIds = [] as string[];
|
||||||
sendTargetAccounts.map((x) => {
|
sendTargetAccounts.forEach((x) => {
|
||||||
if (x.primaryAdminExternalId) {
|
if (x.primaryAdminExternalId) {
|
||||||
externalIds.push(x.primaryAdminExternalId);
|
externalIds.push(x.primaryAdminExternalId);
|
||||||
}
|
}
|
||||||
@ -156,11 +308,10 @@ export async function licenseAlertProcessing(
|
|||||||
externalIds.push(x.secondaryAdminExternalId);
|
externalIds.push(x.secondaryAdminExternalId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const adb2cUsers = await adb2c.getUsers(context, externalIds);
|
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
|
||||||
if (!adb2cUsers) {
|
if (adb2cUsers.length === 0) {
|
||||||
context.log("Target user not found");
|
context.log("Target user not found");
|
||||||
context.log("[OUT]licenseAlertProcessing");
|
return [];
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
|
||||||
sendTargetAccounts.map((info) => {
|
sendTargetAccounts.map((info) => {
|
||||||
@ -188,17 +339,54 @@ export async function licenseAlertProcessing(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return sendTargetAccounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アラートメールを送信する
|
||||||
|
* @param context
|
||||||
|
* @param redisClient
|
||||||
|
* @param sendgrid
|
||||||
|
* @param sendTargetAccounts メール送信対象のアカウント情報
|
||||||
|
* @param formattedDate redisのキーに使用する日付
|
||||||
|
* @returns ユーザ情報
|
||||||
|
*/
|
||||||
|
async function sendAlertMail(
|
||||||
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
|
sendgrid: SendGridService,
|
||||||
|
sendTargetAccounts: accountInfo[],
|
||||||
|
formattedDate: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
context.log("[IN]sendAlertMail");
|
||||||
|
|
||||||
|
// redis用
|
||||||
|
const getAsync = promisify(redisClient.get).bind(redisClient);
|
||||||
|
const setexAsync = promisify(redisClient.setex).bind(redisClient);
|
||||||
|
const ttl = process.env.ADB2C_CACHE_TTL;
|
||||||
|
const mailFrom = getMailFrom();
|
||||||
|
|
||||||
const sendMail = async () => {
|
|
||||||
for (const targetAccount of sendTargetAccounts) {
|
for (const targetAccount of sendTargetAccounts) {
|
||||||
// プライマリ管理者が入っているかチェック
|
// プライマリ管理者が入っているかチェック
|
||||||
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
|
||||||
if (targetAccount.primaryAdminExternalId) {
|
if (targetAccount.primaryAdminExternalId) {
|
||||||
// メール送信
|
// メール送信
|
||||||
// strictNullChecks対応
|
// strictNullChecks対応
|
||||||
if (targetAccount.primaryAdminEmail) {
|
if (!targetAccount.primaryAdminEmail) {
|
||||||
// ライセンス不足メール
|
continue;
|
||||||
if (targetAccount.shortage !== 0) {
|
}
|
||||||
|
// ライセンス不足メール
|
||||||
|
if (targetAccount.shortage !== 0) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseShortage(
|
await createMailContentOfLicenseShortage(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -217,45 +405,107 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
} catch {
|
// 送信成功時、成功履歴をredisに保存
|
||||||
|
try {
|
||||||
|
const key = makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
);
|
||||||
|
await setexAsync(key, ttl, DONE);
|
||||||
|
context.log(
|
||||||
|
"setex Result:",
|
||||||
|
`key:${key},ttl:${ttl},value:Done`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log(
|
||||||
|
"setex failed.",
|
||||||
|
`target: ${targetAccount.primaryAdminEmail}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
context.log(
|
context.log(
|
||||||
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
}
|
throw e;
|
||||||
|
|
||||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
|
||||||
if (targetAccount.secondaryAdminEmail) {
|
|
||||||
// ライセンス不足メール
|
|
||||||
if (targetAccount.shortage !== 0) {
|
|
||||||
const { subject, text, html } =
|
|
||||||
await createMailContentOfLicenseShortage(
|
|
||||||
targetAccount.companyName,
|
|
||||||
targetAccount.shortage,
|
|
||||||
targetAccount.parentCompanyName
|
|
||||||
);
|
|
||||||
// メールを送信
|
|
||||||
try {
|
|
||||||
await sendgrid.sendMail(
|
|
||||||
targetAccount.secondaryAdminEmail,
|
|
||||||
mailFrom,
|
|
||||||
subject,
|
|
||||||
text,
|
|
||||||
html
|
|
||||||
);
|
|
||||||
context.log(
|
|
||||||
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
context.log(
|
|
||||||
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ライセンス失効警告メール
|
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||||
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
if (
|
||||||
|
targetAccount.secondaryAdminEmail &&
|
||||||
|
targetAccount.secondaryAdminExternalId
|
||||||
|
) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.secondaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
|
const { subject, text, html } =
|
||||||
|
await createMailContentOfLicenseShortage(
|
||||||
|
targetAccount.companyName,
|
||||||
|
targetAccount.shortage,
|
||||||
|
targetAccount.parentCompanyName
|
||||||
|
);
|
||||||
|
// メールを送信
|
||||||
|
try {
|
||||||
|
await sendgrid.sendMail(
|
||||||
|
targetAccount.secondaryAdminEmail,
|
||||||
|
mailFrom,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html
|
||||||
|
);
|
||||||
|
context.log(
|
||||||
|
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
|
// 送信成功時、成功履歴をredisに保存
|
||||||
|
try {
|
||||||
|
const key = makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.secondaryAdminExternalId,
|
||||||
|
MAIL_U103
|
||||||
|
);
|
||||||
|
await setexAsync(key, ttl, DONE);
|
||||||
|
context.log(
|
||||||
|
"setex Result:",
|
||||||
|
`key:${key},ttl:${ttl},value:Done`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log(
|
||||||
|
"setex failed.",
|
||||||
|
`target: ${targetAccount.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log(
|
||||||
|
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ライセンス失効警告メール
|
||||||
|
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
|
||||||
|
// redisに送信履歴がない場合のみ送信する
|
||||||
|
const mailResult = await getAsync(
|
||||||
|
makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U104
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
const { subject, text, html } =
|
const { subject, text, html } =
|
||||||
await createMailContentOfLicenseExpiringSoon(
|
await createMailContentOfLicenseExpiringSoon(
|
||||||
targetAccount.companyName,
|
targetAccount.companyName,
|
||||||
@ -274,80 +524,99 @@ export async function licenseAlertProcessing(
|
|||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
} catch {
|
// 送信成功時、成功履歴をredisに保存
|
||||||
|
try {
|
||||||
|
const key = makeSendCompKey(
|
||||||
|
formattedDate,
|
||||||
|
targetAccount.primaryAdminExternalId,
|
||||||
|
MAIL_U104
|
||||||
|
);
|
||||||
|
await setexAsync(key, ttl, DONE);
|
||||||
|
context.log(
|
||||||
|
"setex Result:",
|
||||||
|
`key:${key},ttl:${ttl},value:Done`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log(
|
||||||
|
"setex failed.",
|
||||||
|
`target: ${targetAccount.primaryAdminEmail}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
|
||||||
);
|
);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
|
||||||
if (targetAccount.secondaryAdminEmail) {
|
if (
|
||||||
// ライセンス不足メール
|
targetAccount.secondaryAdminEmail &&
|
||||||
if (targetAccount.shortage !== 0) {
|
targetAccount.secondaryAdminExternalId
|
||||||
const { subject, text, html } =
|
) {
|
||||||
await createMailContentOfLicenseExpiringSoon(
|
// redisに送信履歴がない場合のみ送信する
|
||||||
targetAccount.companyName,
|
const mailResult = makeSendCompKey(
|
||||||
targetAccount.userCountOfLicenseExpiringSoon,
|
formattedDate,
|
||||||
targetAccount.parentCompanyName
|
targetAccount.secondaryAdminExternalId,
|
||||||
);
|
MAIL_U104
|
||||||
// メールを送信
|
);
|
||||||
|
if (mailResult !== DONE) {
|
||||||
|
const { subject, text, html } =
|
||||||
|
await createMailContentOfLicenseExpiringSoon(
|
||||||
|
targetAccount.companyName,
|
||||||
|
targetAccount.userCountOfLicenseExpiringSoon,
|
||||||
|
targetAccount.parentCompanyName
|
||||||
|
);
|
||||||
|
// メールを送信
|
||||||
|
try {
|
||||||
|
await sendgrid.sendMail(
|
||||||
|
targetAccount.secondaryAdminEmail,
|
||||||
|
mailFrom,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html
|
||||||
|
);
|
||||||
|
context.log(
|
||||||
|
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await sendgrid.sendMail(
|
const key = makeSendCompKey(
|
||||||
targetAccount.secondaryAdminEmail,
|
formattedDate,
|
||||||
mailFrom,
|
targetAccount.secondaryAdminExternalId,
|
||||||
subject,
|
MAIL_U104
|
||||||
text,
|
|
||||||
html
|
|
||||||
);
|
);
|
||||||
|
await setexAsync(key, ttl, DONE);
|
||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
|
"setex Result:",
|
||||||
|
`key:${key},ttl:${ttl},value:Done`
|
||||||
);
|
);
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
context.log(
|
context.log(
|
||||||
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
"setex failed.",
|
||||||
|
`target: ${targetAccount.secondaryAdminEmail}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
context.error(e);
|
||||||
|
context.log(
|
||||||
|
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
await sendMail();
|
|
||||||
|
|
||||||
context.log("[OUT]licenseAlertProcessing");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function licenseAlert(
|
|
||||||
myTimer: Timer,
|
|
||||||
context: InvocationContext
|
|
||||||
): Promise<void> {
|
|
||||||
context.log("[IN]licenseAlert");
|
|
||||||
|
|
||||||
dotenv.config({ path: ".env" });
|
|
||||||
dotenv.config({ path: ".env.local", override: true });
|
|
||||||
const datasource = new DataSource({
|
|
||||||
type: "mysql",
|
|
||||||
host: process.env.DB_HOST,
|
|
||||||
port: Number(process.env.DB_PORT),
|
|
||||||
username: process.env.DB_USERNAME,
|
|
||||||
password: process.env.DB_PASSWORD,
|
|
||||||
database: process.env.DB_NAME,
|
|
||||||
entities: [User, Account, License],
|
|
||||||
});
|
|
||||||
await datasource.initialize();
|
|
||||||
|
|
||||||
const adb2c = new AdB2cService();
|
|
||||||
const sendgrid = new SendGridService();
|
|
||||||
try {
|
|
||||||
await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.log("licenseAlertProcessing failed");
|
context.log("sendAlertMail failed.");
|
||||||
context.error(e);
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
await datasource.destroy();
|
context.log("[OUT]sendAlertMail");
|
||||||
context.log("[OUT]licenseAlert");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { createRedisClient } from "../redis/redis";
|
|
||||||
|
|
||||||
export async function redisTimerTest(
|
|
||||||
myTimer: Timer,
|
|
||||||
context: InvocationContext
|
|
||||||
): Promise<void> {
|
|
||||||
context.log("---Timer function processed request.");
|
|
||||||
|
|
||||||
dotenv.config({ path: ".env" });
|
|
||||||
dotenv.config({ path: ".env.local", override: true });
|
|
||||||
|
|
||||||
const redisClient = createRedisClient();
|
|
||||||
const setAsync = promisify(redisClient.set).bind(redisClient);
|
|
||||||
const getAsync = promisify(redisClient.get).bind(redisClient);
|
|
||||||
|
|
||||||
await setAsync("foo", "bar");
|
|
||||||
const value = await getAsync("foo");
|
|
||||||
context.log(`value=${value}`); // returns 'bar'
|
|
||||||
|
|
||||||
await redisClient.quit;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.timer("redisTimerTest", {
|
|
||||||
schedule: "*/30 * * * * *",
|
|
||||||
handler: redisTimerTest,
|
|
||||||
});
|
|
||||||
@ -20,12 +20,34 @@ export const createRedisClient = (): RedisClient => {
|
|||||||
host: host,
|
host: host,
|
||||||
port: port,
|
port: port,
|
||||||
password: password,
|
password: password,
|
||||||
|
retry_strategy: (options) => {
|
||||||
|
if (options.attempt <= 3) {
|
||||||
|
console.log(
|
||||||
|
`Retrying connection to Redis. Attempt ${options.attempt}`
|
||||||
|
);
|
||||||
|
return 10000; // ミリ秒単位でのリトライまでの間隔
|
||||||
|
} else {
|
||||||
|
console.log("Exceeded maximum number of connection attempts.");
|
||||||
|
return undefined; // リトライを終了
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
client = createClient({
|
client = createClient({
|
||||||
url: `rediss://${host}:${port}`,
|
url: `rediss://${host}:${port}`,
|
||||||
password: password,
|
password: password,
|
||||||
tls: {},
|
tls: {},
|
||||||
|
retry_strategy: (options) => {
|
||||||
|
if (options.attempt <= 3) {
|
||||||
|
console.log(
|
||||||
|
`Retrying connection to Redis. Attempt ${options.attempt}`
|
||||||
|
);
|
||||||
|
return 10000; // ミリ秒単位でのリトライまでの間隔
|
||||||
|
} else {
|
||||||
|
console.log("Exceeded maximum number of connection attempts.");
|
||||||
|
return undefined; // リトライを終了
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import { ADB2C_SIGN_IN_TYPE } from "../constants";
|
|||||||
import { SendGridService } from "../sendgrid/sendgrid";
|
import { SendGridService } from "../sendgrid/sendgrid";
|
||||||
import { AdB2cService } from "../adb2c/adb2c";
|
import { AdB2cService } from "../adb2c/adb2c";
|
||||||
import { InvocationContext } from "@azure/functions";
|
import { InvocationContext } from "@azure/functions";
|
||||||
|
import { RedisClient } from "redis";
|
||||||
|
import { createRedisClient } from "../redis/redis";
|
||||||
|
|
||||||
describe("licenseAlert", () => {
|
describe("licenseAlert", () => {
|
||||||
dotenv.config({ path: ".env" });
|
dotenv.config({ path: ".env" });
|
||||||
@ -40,6 +42,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
|
|
||||||
@ -63,8 +66,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(1);
|
expect(spySend.mock.calls).toHaveLength(1);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
|
||||||
@ -72,6 +82,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -96,8 +107,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(2);
|
expect(spySend.mock.calls).toHaveLength(2);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
|
||||||
@ -105,6 +123,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -142,8 +161,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(0);
|
expect(spySend.mock.calls).toHaveLength(0);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
|
||||||
@ -151,6 +177,7 @@ describe("licenseAlert", () => {
|
|||||||
const context = new InvocationContext();
|
const context = new InvocationContext();
|
||||||
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
const sendgridMock = new SendGridServiceMock() as SendGridService;
|
||||||
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
|
||||||
|
const redisClient = createRedisClient();
|
||||||
|
|
||||||
// 呼び出し回数でテスト成否を判定
|
// 呼び出し回数でテスト成否を判定
|
||||||
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
const spySend = jest.spyOn(sendgridMock, "sendMail");
|
||||||
@ -175,8 +202,15 @@ describe("licenseAlert", () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
|
await licenseAlertProcessing(
|
||||||
|
context,
|
||||||
|
source,
|
||||||
|
redisClient,
|
||||||
|
sendgridMock,
|
||||||
|
adb2cMock
|
||||||
|
);
|
||||||
expect(spySend.mock.calls).toHaveLength(1);
|
expect(spySend.mock.calls).toHaveLength(1);
|
||||||
|
redisClient.quit;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -211,6 +245,7 @@ export class AdB2cServiceMock {
|
|||||||
*/
|
*/
|
||||||
async getUsers(
|
async getUsers(
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
|
redisClient: RedisClient,
|
||||||
externalIds: string[]
|
externalIds: string[]
|
||||||
): Promise<AdB2cUser[]> {
|
): Promise<AdB2cUser[]> {
|
||||||
const AdB2cMockUsers: AdB2cUser[] = [
|
const AdB2cMockUsers: AdB2cUser[] = [
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
-- +migrate Up
|
||||||
|
ALTER TABLE `users` ADD COLUMN `accepted_privacy_notice_version` VARCHAR(255) COMMENT '同意済みプライバシーポリシーバージョン' AFTER `accepted_eula_version`;
|
||||||
|
ALTER TABLE `users_archive` ADD COLUMN `accepted_privacy_notice_version` VARCHAR(255) COMMENT '同意済みプライバシーポリシーバージョン' AFTER `accepted_eula_version`;
|
||||||
|
insert into terms(terms.document_type, terms.version) values('PrivacyNotice', 'V0.1');
|
||||||
|
commit;
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
ALTER TABLE `users` DROP COLUMN `accepted_privacy_notice_version`;
|
||||||
|
ALTER TABLE `users_archive` DROP COLUMN `accepted_privacy_notice_version`;
|
||||||
|
delete from terms where terms.document_type = 'PrivacyNotice' and terms.version = 'V0.1';
|
||||||
|
commit;
|
||||||
@ -1455,6 +1455,52 @@
|
|||||||
"tags": ["accounts"]
|
"tags": ["accounts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/accounts/company-name": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "getCompanyName",
|
||||||
|
"summary": "",
|
||||||
|
"description": "指定したアカウントの会社名を取得します",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/GetCompanyNameRequest" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功時のレスポンス",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GetCompanyNameResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "認証エラー",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "想定外のサーバーエラー",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": ["accounts"],
|
||||||
|
"security": [{ "bearer": [] }]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/users/confirm": {
|
"/users/confirm": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "confirmUser",
|
"operationId": "confirmUser",
|
||||||
@ -3491,6 +3537,10 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "同意済み利用規約のバージョン(EULA)"
|
"description": "同意済み利用規約のバージョン(EULA)"
|
||||||
},
|
},
|
||||||
|
"acceptedPrivacyNoticeVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "同意済みプライバシーポリシーのバージョン"
|
||||||
|
},
|
||||||
"acceptedDpaVersion": {
|
"acceptedDpaVersion": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "同意済み利用規約のバージョン(DPA)"
|
"description": "同意済み利用規約のバージョン(DPA)"
|
||||||
@ -3504,6 +3554,7 @@
|
|||||||
"adminMail",
|
"adminMail",
|
||||||
"adminPassword",
|
"adminPassword",
|
||||||
"acceptedEulaVersion",
|
"acceptedEulaVersion",
|
||||||
|
"acceptedPrivacyNoticeVersion",
|
||||||
"acceptedDpaVersion",
|
"acceptedDpaVersion",
|
||||||
"token"
|
"token"
|
||||||
]
|
]
|
||||||
@ -4009,6 +4060,16 @@
|
|||||||
"properties": { "tier": { "type": "number", "description": "階層" } },
|
"properties": { "tier": { "type": "number", "description": "階層" } },
|
||||||
"required": ["tier"]
|
"required": ["tier"]
|
||||||
},
|
},
|
||||||
|
"GetCompanyNameRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "accountId": { "type": "number" } },
|
||||||
|
"required": ["accountId"]
|
||||||
|
},
|
||||||
|
"GetCompanyNameResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "companyName": { "type": "string" } },
|
||||||
|
"required": ["companyName"]
|
||||||
|
},
|
||||||
"ConfirmRequest": {
|
"ConfirmRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": { "token": { "type": "string" } },
|
"properties": { "token": { "type": "string" } },
|
||||||
@ -4236,12 +4297,20 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "更新バージョン(EULA)"
|
"description": "更新バージョン(EULA)"
|
||||||
},
|
},
|
||||||
|
"acceptedPrivacyNoticeVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "更新バージョン(PrivacyNotice)"
|
||||||
|
},
|
||||||
"acceptedDPAVersion": {
|
"acceptedDPAVersion": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "更新バージョン(DPA)"
|
"description": "更新バージョン(DPA)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["idToken", "acceptedEULAVersion"]
|
"required": [
|
||||||
|
"idToken",
|
||||||
|
"acceptedEULAVersion",
|
||||||
|
"acceptedPrivacyNoticeVersion"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"UpdateAcceptedVersionResponse": { "type": "object", "properties": {} },
|
"UpdateAcceptedVersionResponse": { "type": "object", "properties": {} },
|
||||||
"GetMyUserResponse": {
|
"GetMyUserResponse": {
|
||||||
|
|||||||
@ -182,6 +182,8 @@ export const makeTestAccount = async (
|
|||||||
role: d?.role ?? 'admin none',
|
role: d?.role ?? 'admin none',
|
||||||
author_id: d?.author_id ?? undefined,
|
author_id: d?.author_id ?? undefined,
|
||||||
accepted_eula_version: d?.accepted_eula_version ?? '1.0',
|
accepted_eula_version: d?.accepted_eula_version ?? '1.0',
|
||||||
|
accepted_privacy_notice_version:
|
||||||
|
d?.accepted_privacy_notice_version ?? '1.0',
|
||||||
accepted_dpa_version: d?.accepted_dpa_version ?? '1.0',
|
accepted_dpa_version: d?.accepted_dpa_version ?? '1.0',
|
||||||
email_verified: d?.email_verified ?? true,
|
email_verified: d?.email_verified ?? true,
|
||||||
auto_renew: d?.auto_renew ?? true,
|
auto_renew: d?.auto_renew ?? true,
|
||||||
|
|||||||
@ -287,6 +287,7 @@ export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]';
|
|||||||
export const TERM_TYPE = {
|
export const TERM_TYPE = {
|
||||||
EULA: 'EULA',
|
EULA: 'EULA',
|
||||||
DPA: 'DPA',
|
DPA: 'DPA',
|
||||||
|
PRIVACY_NOTICE: 'PrivacyNotice',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -68,6 +68,8 @@ import {
|
|||||||
GetAccountInfoMinimalAccessResponse,
|
GetAccountInfoMinimalAccessResponse,
|
||||||
DeleteWorktypeRequestParam,
|
DeleteWorktypeRequestParam,
|
||||||
DeleteWorktypeResponse,
|
DeleteWorktypeResponse,
|
||||||
|
GetCompanyNameRequest,
|
||||||
|
GetCompanyNameResponse,
|
||||||
} from './types/types';
|
} from './types/types';
|
||||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||||
@ -116,6 +118,7 @@ export class AccountsController {
|
|||||||
adminPassword,
|
adminPassword,
|
||||||
adminName,
|
adminName,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
} = body;
|
} = body;
|
||||||
const role = USER_ROLES.NONE;
|
const role = USER_ROLES.NONE;
|
||||||
@ -132,6 +135,7 @@ export class AccountsController {
|
|||||||
adminName,
|
adminName,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1550,4 +1554,56 @@ export class AccountsController {
|
|||||||
);
|
);
|
||||||
return { tier };
|
return { tier };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
status: HttpStatus.OK,
|
||||||
|
type: GetCompanyNameResponse,
|
||||||
|
description: '成功時のレスポンス',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: HttpStatus.UNAUTHORIZED,
|
||||||
|
description: '認証エラー',
|
||||||
|
type: ErrorResponse,
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
description: '想定外のサーバーエラー',
|
||||||
|
type: ErrorResponse,
|
||||||
|
})
|
||||||
|
@ApiOperation({
|
||||||
|
operationId: 'getCompanyName',
|
||||||
|
description: '指定したアカウントの会社名を取得します',
|
||||||
|
})
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(AuthGuard)
|
||||||
|
@UseGuards(
|
||||||
|
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
|
||||||
|
)
|
||||||
|
@Post('company-name')
|
||||||
|
async getCompanyName(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Body() body: GetCompanyNameRequest,
|
||||||
|
): Promise<GetCompanyNameResponse> {
|
||||||
|
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 companyName = await this.accountService.getCompanyName(
|
||||||
|
context,
|
||||||
|
body.accountId,
|
||||||
|
);
|
||||||
|
return companyName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,6 +112,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -144,6 +145,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
// 作成したアカウントのIDが返ってくるか確認
|
// 作成したアカウントのIDが返ってくるか確認
|
||||||
@ -161,6 +163,9 @@ describe('createAccount', () => {
|
|||||||
expect(account?.primary_admin_user_id).toBe(user?.id);
|
expect(account?.primary_admin_user_id).toBe(user?.id);
|
||||||
expect(account?.secondary_admin_user_id).toBe(null);
|
expect(account?.secondary_admin_user_id).toBe(null);
|
||||||
expect(user?.accepted_eula_version).toBe(acceptedEulaVersion);
|
expect(user?.accepted_eula_version).toBe(acceptedEulaVersion);
|
||||||
|
expect(user?.accepted_privacy_notice_version).toBe(
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
|
);
|
||||||
expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion);
|
expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion);
|
||||||
expect(user?.account_id).toBe(accountId);
|
expect(user?.account_id).toBe(accountId);
|
||||||
expect(user?.role).toBe(role);
|
expect(user?.role).toBe(role);
|
||||||
@ -195,6 +200,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'admin none';
|
const role = 'admin none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -216,6 +222,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -264,6 +271,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'admin none';
|
const role = 'admin none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -286,6 +294,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -318,6 +327,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -345,6 +355,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -384,6 +395,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -411,6 +423,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -452,6 +465,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -480,6 +494,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -520,6 +535,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -551,6 +567,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -593,6 +610,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -641,6 +659,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -689,6 +708,7 @@ describe('createAccount', () => {
|
|||||||
const username = 'dummy_username';
|
const username = 'dummy_username';
|
||||||
const role = 'none';
|
const role = 'none';
|
||||||
const acceptedEulaVersion = '1.0.0';
|
const acceptedEulaVersion = '1.0.0';
|
||||||
|
const acceptedPrivacyNoticeVersion = '1.0.0';
|
||||||
const acceptedDpaVersion = '1.0.0';
|
const acceptedDpaVersion = '1.0.0';
|
||||||
|
|
||||||
overrideAdB2cService(service, {
|
overrideAdB2cService(service, {
|
||||||
@ -734,6 +754,7 @@ describe('createAccount', () => {
|
|||||||
username,
|
username,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -6694,3 +6715,60 @@ describe('getAccountInfoMinimalAccess', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('getCompanyName', () => {
|
||||||
|
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('アカウントIDから会社名が取得できること', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, {
|
||||||
|
tier: 5,
|
||||||
|
company_name: 'testCompany',
|
||||||
|
});
|
||||||
|
const context = makeContext(admin.external_id);
|
||||||
|
const response = await service.getCompanyName(context, account.id);
|
||||||
|
expect({ companyName: 'testCompany' }).toEqual(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('アカウントが存在しない場合、400エラーとなること', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<AccountsService>(AccountsService);
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account, admin } = await makeTestAccount(source, {
|
||||||
|
tier: 5,
|
||||||
|
company_name: 'testCompany',
|
||||||
|
});
|
||||||
|
const context = makeContext(admin.external_id);
|
||||||
|
try {
|
||||||
|
await service.getCompanyName(context, 123);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
PostWorktypeOptionItem,
|
PostWorktypeOptionItem,
|
||||||
Author,
|
Author,
|
||||||
Partner,
|
Partner,
|
||||||
|
GetCompanyNameResponse,
|
||||||
} from './types/types';
|
} from './types/types';
|
||||||
import {
|
import {
|
||||||
DateWithZeroTime,
|
DateWithZeroTime,
|
||||||
@ -175,6 +176,7 @@ export class AccountsService {
|
|||||||
username: string,
|
username: string,
|
||||||
role: string,
|
role: string,
|
||||||
acceptedEulaVersion: string,
|
acceptedEulaVersion: string,
|
||||||
|
acceptedPrivacyNoticeVersion: string,
|
||||||
acceptedDpaVersion: string,
|
acceptedDpaVersion: string,
|
||||||
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
|
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
@ -184,6 +186,7 @@ export class AccountsService {
|
|||||||
`dealerAccountId: ${dealerAccountId}, ` +
|
`dealerAccountId: ${dealerAccountId}, ` +
|
||||||
`role: ${role}, ` +
|
`role: ${role}, ` +
|
||||||
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
|
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
|
||||||
|
`acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` +
|
||||||
`acceptedDpaVersion: ${acceptedDpaVersion} };`,
|
`acceptedDpaVersion: ${acceptedDpaVersion} };`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
@ -232,6 +235,7 @@ export class AccountsService {
|
|||||||
externalUser.sub,
|
externalUser.sub,
|
||||||
role,
|
role,
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
);
|
);
|
||||||
account = newAccount;
|
account = newAccount;
|
||||||
@ -2151,4 +2155,51 @@ export class AccountsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 自アカウントの会社名を取得する
|
||||||
|
* @param accountId
|
||||||
|
* @returns CompanyName
|
||||||
|
*/
|
||||||
|
async getCompanyName(
|
||||||
|
context: Context,
|
||||||
|
accountId: number,
|
||||||
|
): Promise<GetCompanyNameResponse> {
|
||||||
|
this.logger.log(
|
||||||
|
`[IN] [${context.getTrackingId()}] ${
|
||||||
|
this.getCompanyName.name
|
||||||
|
} | params: { accountId: ${accountId}, };`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { company_name } = await this.accountRepository.findAccountById(
|
||||||
|
accountId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { companyName: company_name };
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
switch (e.constructor) {
|
||||||
|
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.getTrackingId()}] ${this.getCompanyName.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,8 @@ export class CreateAccountRequest {
|
|||||||
adminPassword: string;
|
adminPassword: string;
|
||||||
@ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' })
|
@ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' })
|
||||||
acceptedEulaVersion: string;
|
acceptedEulaVersion: string;
|
||||||
|
@ApiProperty({ description: '同意済みプライバシーポリシーのバージョン' })
|
||||||
|
acceptedPrivacyNoticeVersion: string;
|
||||||
@ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' })
|
@ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' })
|
||||||
acceptedDpaVersion: string;
|
acceptedDpaVersion: string;
|
||||||
@ApiProperty({ description: 'reCAPTCHA Token' })
|
@ApiProperty({ description: 'reCAPTCHA Token' })
|
||||||
@ -599,3 +601,13 @@ export class GetAccountInfoMinimalAccessResponse {
|
|||||||
@ApiProperty({ description: '階層' })
|
@ApiProperty({ description: '階層' })
|
||||||
tier: number;
|
tier: number;
|
||||||
}
|
}
|
||||||
|
export class GetCompanyNameRequest {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsInt()
|
||||||
|
@Type(() => Number)
|
||||||
|
accountId: number;
|
||||||
|
}
|
||||||
|
export class GetCompanyNameResponse {
|
||||||
|
@ApiProperty()
|
||||||
|
companyName: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -196,6 +196,7 @@ describe('checkIsAcceptedLatestVersion', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await createTermInfo(source, 'EULA', '1.0');
|
await createTermInfo(source, 'EULA', '1.0');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', '1.0');
|
||||||
await createTermInfo(source, 'DPA', '1.0');
|
await createTermInfo(source, 'DPA', '1.0');
|
||||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
@ -219,6 +220,7 @@ describe('checkIsAcceptedLatestVersion', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await createTermInfo(source, 'EULA', '1.0');
|
await createTermInfo(source, 'EULA', '1.0');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', '1.0');
|
||||||
await createTermInfo(source, 'DPA', '1.0');
|
await createTermInfo(source, 'DPA', '1.0');
|
||||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
@ -242,6 +244,7 @@ describe('checkIsAcceptedLatestVersion', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await createTermInfo(source, 'EULA', '1.1');
|
await createTermInfo(source, 'EULA', '1.1');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', '1.0');
|
||||||
await createTermInfo(source, 'DPA', '1.0');
|
await createTermInfo(source, 'DPA', '1.0');
|
||||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
@ -265,6 +268,7 @@ describe('checkIsAcceptedLatestVersion', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await createTermInfo(source, 'EULA', '1.1');
|
await createTermInfo(source, 'EULA', '1.1');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', '1.0');
|
||||||
await createTermInfo(source, 'DPA', '1.0');
|
await createTermInfo(source, 'DPA', '1.0');
|
||||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
@ -288,10 +292,35 @@ describe('checkIsAcceptedLatestVersion', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await createTermInfo(source, 'EULA', '1.0');
|
await createTermInfo(source, 'EULA', '1.0');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', '1.0');
|
||||||
await createTermInfo(source, 'DPA', '1.1');
|
await createTermInfo(source, 'DPA', '1.1');
|
||||||
const result = await service.isAcceptedLatestVersion(context, idToken);
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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, 'PrivacyNotice', '1.1');
|
||||||
|
await createTermInfo(source, 'DPA', '1.0');
|
||||||
|
const result = await service.isAcceptedLatestVersion(context, idToken);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateDelegationRefreshToken', () => {
|
describe('generateDelegationRefreshToken', () => {
|
||||||
|
|||||||
@ -689,28 +689,38 @@ export class AuthService {
|
|||||||
const {
|
const {
|
||||||
acceptedEulaVersion,
|
acceptedEulaVersion,
|
||||||
latestEulaVersion,
|
latestEulaVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
|
latestPrivacyNoticeVersion,
|
||||||
acceptedDpaVersion,
|
acceptedDpaVersion,
|
||||||
latestDpaVersion,
|
latestDpaVersion,
|
||||||
tier,
|
tier,
|
||||||
} = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub);
|
} = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub);
|
||||||
|
|
||||||
// 第五階層はEULAのみ判定
|
// 第五階層はEULAとPrivacyNoticeのみ判定
|
||||||
if (tier === TIERS.TIER5) {
|
if (tier === TIERS.TIER5) {
|
||||||
if (!acceptedEulaVersion) {
|
if (!acceptedEulaVersion || !acceptedPrivacyNoticeVersion) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 最新バージョンに同意済みか判定
|
// 最新バージョンに同意済みか判定
|
||||||
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
||||||
return eulaAccepted;
|
const privacyNoticeAccepted =
|
||||||
|
acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion;
|
||||||
|
return eulaAccepted && privacyNoticeAccepted;
|
||||||
} else {
|
} else {
|
||||||
// 第一~第四階層はEULA、DPAを判定
|
// 第一~第四階層はEULA、PrivacyNotice、DPAを判定
|
||||||
if (!acceptedEulaVersion || !acceptedDpaVersion) {
|
if (
|
||||||
|
!acceptedEulaVersion ||
|
||||||
|
!acceptedPrivacyNoticeVersion ||
|
||||||
|
!acceptedDpaVersion
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 最新バージョンに同意済みか判定
|
// 最新バージョンに同意済みか判定
|
||||||
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
|
||||||
|
const privacyNoticeAccepted =
|
||||||
|
acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion;
|
||||||
const dpaAccepted = acceptedDpaVersion === latestDpaVersion;
|
const dpaAccepted = acceptedDpaVersion === latestDpaVersion;
|
||||||
return eulaAccepted && dpaAccepted;
|
return eulaAccepted && privacyNoticeAccepted && dpaAccepted;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
|
||||||
|
|||||||
@ -22,8 +22,10 @@ export class AccessTokenRequest {}
|
|||||||
export type TermsCheckInfo = {
|
export type TermsCheckInfo = {
|
||||||
tier: number;
|
tier: number;
|
||||||
acceptedEulaVersion?: string;
|
acceptedEulaVersion?: string;
|
||||||
|
acceptedPrivacyNoticeVersion?: string;
|
||||||
acceptedDpaVersion?: string;
|
acceptedDpaVersion?: string;
|
||||||
latestEulaVersion: string;
|
latestEulaVersion: string;
|
||||||
|
latestPrivacyNoticeVersion: string;
|
||||||
latestDpaVersion: string;
|
latestDpaVersion: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -139,6 +139,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
|||||||
role: 'none',
|
role: 'none',
|
||||||
author_id: '',
|
author_id: '',
|
||||||
accepted_eula_version: '1.0',
|
accepted_eula_version: '1.0',
|
||||||
|
accepted_privacy_notice_version: '1.0',
|
||||||
accepted_dpa_version: '1.0',
|
accepted_dpa_version: '1.0',
|
||||||
email_verified: true,
|
email_verified: true,
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
|
|||||||
@ -470,6 +470,7 @@ const defaultTasksRepositoryMockValue: {
|
|||||||
external_id: 'userId',
|
external_id: 'userId',
|
||||||
role: 'typist',
|
role: 'typist',
|
||||||
accepted_eula_version: '',
|
accepted_eula_version: '',
|
||||||
|
accepted_privacy_notice_version: '',
|
||||||
accepted_dpa_version: '',
|
accepted_dpa_version: '',
|
||||||
email_verified: true,
|
email_verified: true,
|
||||||
auto_renew: true,
|
auto_renew: true,
|
||||||
|
|||||||
@ -34,6 +34,8 @@ describe('利用規約取得', () => {
|
|||||||
|
|
||||||
await createTermInfo(source, 'EULA', 'v1.0');
|
await createTermInfo(source, 'EULA', 'v1.0');
|
||||||
await createTermInfo(source, 'EULA', 'v1.1');
|
await createTermInfo(source, 'EULA', 'v1.1');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', 'v1.0');
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', 'v1.1');
|
||||||
await createTermInfo(source, 'DPA', 'v1.0');
|
await createTermInfo(source, 'DPA', 'v1.0');
|
||||||
await createTermInfo(source, 'DPA', 'v1.2');
|
await createTermInfo(source, 'DPA', 'v1.2');
|
||||||
|
|
||||||
@ -42,8 +44,10 @@ describe('利用規約取得', () => {
|
|||||||
|
|
||||||
expect(result[0].documentType).toBe('EULA');
|
expect(result[0].documentType).toBe('EULA');
|
||||||
expect(result[0].version).toBe('v1.1');
|
expect(result[0].version).toBe('v1.1');
|
||||||
expect(result[1].documentType).toBe('DPA');
|
expect(result[1].documentType).toBe('PrivacyNotice');
|
||||||
expect(result[1].version).toBe('v1.2');
|
expect(result[1].version).toBe('v1.1');
|
||||||
|
expect(result[2].documentType).toBe('DPA');
|
||||||
|
expect(result[2].version).toBe('v1.2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => {
|
it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => {
|
||||||
@ -75,6 +79,21 @@ describe('利用規約取得', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('利用規約情報(PrivacyNoticeのみ)が存在しない場合エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<TermsService>(TermsService);
|
||||||
|
await createTermInfo(source, 'PrivacyNotice', 'v1.0');
|
||||||
|
const context = makeContext(uuidv4());
|
||||||
|
await expect(service.getTermsInfo(context)).rejects.toEqual(
|
||||||
|
new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => {
|
it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => {
|
||||||
if (!source) fail();
|
if (!source) fail();
|
||||||
const module = await makeTestingModule(source);
|
const module = await makeTestingModule(source);
|
||||||
|
|||||||
@ -19,13 +19,17 @@ export class TermsService {
|
|||||||
`[IN] [${context.getTrackingId()}] ${this.getTermsInfo.name}`,
|
`[IN] [${context.getTrackingId()}] ${this.getTermsInfo.name}`,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const { eulaVersion, dpaVersion } =
|
const { eulaVersion, privacyNoticeVersion, dpaVersion } =
|
||||||
await this.termsRepository.getLatestTermsInfo();
|
await this.termsRepository.getLatestTermsInfo();
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
documentType: TERM_TYPE.EULA,
|
documentType: TERM_TYPE.EULA,
|
||||||
version: eulaVersion,
|
version: eulaVersion,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
documentType: TERM_TYPE.PRIVACY_NOTICE,
|
||||||
|
version: privacyNoticeVersion,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
documentType: TERM_TYPE.DPA,
|
documentType: TERM_TYPE.DPA,
|
||||||
version: dpaVersion,
|
version: dpaVersion,
|
||||||
|
|||||||
@ -13,5 +13,6 @@ export class GetTermsInfoResponse {
|
|||||||
|
|
||||||
export type TermsVersion = {
|
export type TermsVersion = {
|
||||||
eulaVersion: string;
|
eulaVersion: string;
|
||||||
|
privacyNoticeVersion: string;
|
||||||
dpaVersion: string;
|
dpaVersion: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -263,6 +263,8 @@ export class UpdateAcceptedVersionRequest {
|
|||||||
idToken: string;
|
idToken: string;
|
||||||
@ApiProperty({ description: '更新バージョン(EULA)' })
|
@ApiProperty({ description: '更新バージョン(EULA)' })
|
||||||
acceptedEULAVersion: string;
|
acceptedEULAVersion: string;
|
||||||
|
@ApiProperty({ description: '更新バージョン(PrivacyNotice)' })
|
||||||
|
acceptedPrivacyNoticeVersion: string;
|
||||||
@ApiProperty({ description: '更新バージョン(DPA)', required: false })
|
@ApiProperty({ description: '更新バージョン(DPA)', required: false })
|
||||||
acceptedDPAVersion?: string;
|
acceptedDPAVersion?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
|
Ip,
|
||||||
Post,
|
Post,
|
||||||
Query,
|
Query,
|
||||||
Req,
|
Req,
|
||||||
@ -136,6 +137,7 @@ export class UsersController {
|
|||||||
@Get()
|
@Get()
|
||||||
async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
|
async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
|
||||||
const accessToken = retrieveAuthorizationToken(req);
|
const accessToken = retrieveAuthorizationToken(req);
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
makeErrorResponse('E000107'),
|
makeErrorResponse('E000107'),
|
||||||
@ -627,7 +629,12 @@ export class UsersController {
|
|||||||
async updateAcceptedVersion(
|
async updateAcceptedVersion(
|
||||||
@Body() body: UpdateAcceptedVersionRequest,
|
@Body() body: UpdateAcceptedVersionRequest,
|
||||||
): Promise<UpdateAcceptedVersionResponse> {
|
): Promise<UpdateAcceptedVersionResponse> {
|
||||||
const { idToken, acceptedEULAVersion, acceptedDPAVersion } = body;
|
const {
|
||||||
|
idToken,
|
||||||
|
acceptedEULAVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
|
acceptedDPAVersion,
|
||||||
|
} = body;
|
||||||
|
|
||||||
const context = makeContext(uuidv4());
|
const context = makeContext(uuidv4());
|
||||||
|
|
||||||
@ -650,6 +657,7 @@ export class UsersController {
|
|||||||
context,
|
context,
|
||||||
verifiedIdToken.sub,
|
verifiedIdToken.sub,
|
||||||
acceptedEULAVersion,
|
acceptedEULAVersion,
|
||||||
|
acceptedPrivacyNoticeVersion,
|
||||||
acceptedDPAVersion,
|
acceptedDPAVersion,
|
||||||
);
|
);
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@ -208,6 +208,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
|||||||
account_id: 1,
|
account_id: 1,
|
||||||
role: 'None',
|
role: 'None',
|
||||||
accepted_eula_version: 'string',
|
accepted_eula_version: 'string',
|
||||||
|
accepted_privacy_notice_version: 'string',
|
||||||
accepted_dpa_version: 'string',
|
accepted_dpa_version: 'string',
|
||||||
email_verified: false,
|
email_verified: false,
|
||||||
created_by: 'string;',
|
created_by: 'string;',
|
||||||
@ -259,6 +260,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
|||||||
account_id: 1,
|
account_id: 1,
|
||||||
role: 'None',
|
role: 'None',
|
||||||
accepted_eula_version: 'string',
|
accepted_eula_version: 'string',
|
||||||
|
accepted_privacy_notice_version: 'string',
|
||||||
accepted_dpa_version: 'string',
|
accepted_dpa_version: 'string',
|
||||||
email_verified: false,
|
email_verified: false,
|
||||||
created_by: 'string;',
|
created_by: 'string;',
|
||||||
@ -306,6 +308,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
|||||||
account_id: 1,
|
account_id: 1,
|
||||||
role: 'None',
|
role: 'None',
|
||||||
accepted_eula_version: 'string',
|
accepted_eula_version: 'string',
|
||||||
|
accepted_privacy_notice_version: 'string',
|
||||||
accepted_dpa_version: 'string',
|
accepted_dpa_version: 'string',
|
||||||
email_verified: true,
|
email_verified: true,
|
||||||
created_by: 'string;',
|
created_by: 'string;',
|
||||||
@ -358,6 +361,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
|||||||
account_id: 1,
|
account_id: 1,
|
||||||
role: 'None',
|
role: 'None',
|
||||||
accepted_eula_version: 'string',
|
accepted_eula_version: 'string',
|
||||||
|
accepted_privacy_notice_version: 'string',
|
||||||
accepted_dpa_version: 'string',
|
accepted_dpa_version: 'string',
|
||||||
email_verified: false,
|
email_verified: false,
|
||||||
created_by: 'string;',
|
created_by: 'string;',
|
||||||
@ -2617,7 +2621,12 @@ describe('UsersService.updateAcceptedVersion', () => {
|
|||||||
const context = makeContext(uuidv4());
|
const context = makeContext(uuidv4());
|
||||||
|
|
||||||
const service = module.get<UsersService>(UsersService);
|
const service = module.get<UsersService>(UsersService);
|
||||||
await service.updateAcceptedVersion(context, admin.external_id, 'v2.0');
|
await service.updateAcceptedVersion(
|
||||||
|
context,
|
||||||
|
admin.external_id,
|
||||||
|
'v2.0',
|
||||||
|
'v2.0',
|
||||||
|
);
|
||||||
const user = await getUser(source, admin.id);
|
const user = await getUser(source, admin.id);
|
||||||
|
|
||||||
expect(user?.accepted_eula_version).toBe('v2.0');
|
expect(user?.accepted_eula_version).toBe('v2.0');
|
||||||
@ -2637,6 +2646,7 @@ describe('UsersService.updateAcceptedVersion', () => {
|
|||||||
context,
|
context,
|
||||||
admin.external_id,
|
admin.external_id,
|
||||||
'v2.0',
|
'v2.0',
|
||||||
|
'v2.0',
|
||||||
'v3.0',
|
'v3.0',
|
||||||
);
|
);
|
||||||
const user = await getUser(source, admin.id);
|
const user = await getUser(source, admin.id);
|
||||||
@ -2660,6 +2670,7 @@ describe('UsersService.updateAcceptedVersion', () => {
|
|||||||
context,
|
context,
|
||||||
admin.external_id,
|
admin.external_id,
|
||||||
'v2.0',
|
'v2.0',
|
||||||
|
'v2.0',
|
||||||
undefined,
|
undefined,
|
||||||
),
|
),
|
||||||
).rejects.toEqual(
|
).rejects.toEqual(
|
||||||
|
|||||||
@ -403,6 +403,7 @@ export class UsersService {
|
|||||||
role,
|
role,
|
||||||
accepted_dpa_version: null,
|
accepted_dpa_version: null,
|
||||||
accepted_eula_version: null,
|
accepted_eula_version: null,
|
||||||
|
accepted_privacy_notice_version: null,
|
||||||
encryption: false,
|
encryption: false,
|
||||||
encryption_password: null,
|
encryption_password: null,
|
||||||
prompt: false,
|
prompt: false,
|
||||||
@ -422,6 +423,7 @@ export class UsersService {
|
|||||||
prompt: prompt ?? false,
|
prompt: prompt ?? false,
|
||||||
accepted_dpa_version: null,
|
accepted_dpa_version: null,
|
||||||
accepted_eula_version: null,
|
accepted_eula_version: null,
|
||||||
|
accepted_privacy_notice_version: null,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
//不正なroleが指定された場合はログを出力してエラーを返す
|
//不正なroleが指定された場合はログを出力してエラーを返す
|
||||||
@ -538,6 +540,7 @@ export class UsersService {
|
|||||||
// DBから同一アカウントのユーザ一覧を取得する
|
// DBから同一アカウントのユーザ一覧を取得する
|
||||||
const dbUsers = await this.usersRepository.findSameAccountUsers(
|
const dbUsers = await this.usersRepository.findSameAccountUsers(
|
||||||
externalId,
|
externalId,
|
||||||
|
context,
|
||||||
);
|
);
|
||||||
|
|
||||||
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
|
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
|
||||||
@ -1044,12 +1047,14 @@ export class UsersService {
|
|||||||
* @param context
|
* @param context
|
||||||
* @param idToken
|
* @param idToken
|
||||||
* @param eulaVersion
|
* @param eulaVersion
|
||||||
|
* @param privacyNoticeVersion
|
||||||
* @param dpaVersion
|
* @param dpaVersion
|
||||||
*/
|
*/
|
||||||
async updateAcceptedVersion(
|
async updateAcceptedVersion(
|
||||||
context: Context,
|
context: Context,
|
||||||
externalId: string,
|
externalId: string,
|
||||||
eulaVersion: string,
|
eulaVersion: string,
|
||||||
|
privacyNoticeVersion: string,
|
||||||
dpaVersion?: string,
|
dpaVersion?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
@ -1058,6 +1063,7 @@ export class UsersService {
|
|||||||
} | params: { ` +
|
} | params: { ` +
|
||||||
`externalId: ${externalId}, ` +
|
`externalId: ${externalId}, ` +
|
||||||
`eulaVersion: ${eulaVersion}, ` +
|
`eulaVersion: ${eulaVersion}, ` +
|
||||||
|
`privacyNoticeVersion: ${privacyNoticeVersion}, ` +
|
||||||
`dpaVersion: ${dpaVersion}, };`,
|
`dpaVersion: ${dpaVersion}, };`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1065,6 +1071,7 @@ export class UsersService {
|
|||||||
await this.usersRepository.updateAcceptedTermsVersion(
|
await this.usersRepository.updateAcceptedTermsVersion(
|
||||||
externalId,
|
externalId,
|
||||||
eulaVersion,
|
eulaVersion,
|
||||||
|
privacyNoticeVersion,
|
||||||
dpaVersion,
|
dpaVersion,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -127,6 +127,7 @@ export class AccountsRepositoryService {
|
|||||||
adminExternalUserId: string,
|
adminExternalUserId: string,
|
||||||
adminUserRole: string,
|
adminUserRole: string,
|
||||||
adminUserAcceptedEulaVersion?: string,
|
adminUserAcceptedEulaVersion?: string,
|
||||||
|
adminUserAcceptedPrivacyNoticeVersion?: string,
|
||||||
adminUserAcceptedDpaVersion?: string,
|
adminUserAcceptedDpaVersion?: string,
|
||||||
): Promise<{ newAccount: Account; adminUser: User }> {
|
): Promise<{ newAccount: Account; adminUser: User }> {
|
||||||
return await this.dataSource.transaction(async (entityManager) => {
|
return await this.dataSource.transaction(async (entityManager) => {
|
||||||
@ -148,6 +149,8 @@ export class AccountsRepositoryService {
|
|||||||
user.external_id = adminExternalUserId;
|
user.external_id = adminExternalUserId;
|
||||||
user.role = adminUserRole;
|
user.role = adminUserRole;
|
||||||
user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
|
user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
|
||||||
|
user.accepted_privacy_notice_version =
|
||||||
|
adminUserAcceptedPrivacyNoticeVersion ?? null;
|
||||||
user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
|
user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
|
||||||
}
|
}
|
||||||
const usersRepo = entityManager.getRepository(User);
|
const usersRepo = entityManager.getRepository(User);
|
||||||
|
|||||||
@ -24,6 +24,14 @@ export class TermsRepositoryService {
|
|||||||
id: 'DESC',
|
id: 'DESC',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const latestPrivacyNoticeInfo = await termRepo.findOne({
|
||||||
|
where: {
|
||||||
|
document_type: TERM_TYPE.PRIVACY_NOTICE,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
const latestDpaInfo = await termRepo.findOne({
|
const latestDpaInfo = await termRepo.findOne({
|
||||||
where: {
|
where: {
|
||||||
document_type: TERM_TYPE.DPA,
|
document_type: TERM_TYPE.DPA,
|
||||||
@ -33,13 +41,16 @@ export class TermsRepositoryService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!latestEulaInfo || !latestDpaInfo) {
|
if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {
|
||||||
throw new TermInfoNotFoundError(
|
throw new TermInfoNotFoundError(
|
||||||
`Terms info is not found. latestEulaInfo: ${latestEulaInfo}, latestDpaInfo: ${latestDpaInfo}`,
|
`Terms info is not found. latestEulaInfo: ${latestEulaInfo},
|
||||||
|
latestPrivacyNoticeInfo: ${latestPrivacyNoticeInfo},
|
||||||
|
latestDpaInfo: ${latestDpaInfo}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
eulaVersion: latestEulaInfo.version,
|
eulaVersion: latestEulaInfo.version,
|
||||||
|
privacyNoticeVersion: latestEulaInfo.version,
|
||||||
dpaVersion: latestDpaInfo.version,
|
dpaVersion: latestDpaInfo.version,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -34,6 +34,9 @@ export class User {
|
|||||||
@Column({ nullable: true, type: 'varchar' })
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
accepted_eula_version: string | null;
|
accepted_eula_version: string | null;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
|
accepted_privacy_notice_version: string | null;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'varchar' })
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
accepted_dpa_version: string | null;
|
accepted_dpa_version: string | null;
|
||||||
|
|
||||||
@ -112,6 +115,9 @@ export class UserArchive {
|
|||||||
@Column({ nullable: true, type: 'varchar' })
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
accepted_eula_version: string | null;
|
accepted_eula_version: string | null;
|
||||||
|
|
||||||
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
|
accepted_privacy_notice_version: string | null;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'varchar' })
|
@Column({ nullable: true, type: 'varchar' })
|
||||||
accepted_dpa_version: string | null;
|
accepted_dpa_version: string | null;
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import {
|
|||||||
import { Account } from '../accounts/entity/account.entity';
|
import { Account } from '../accounts/entity/account.entity';
|
||||||
import { Workflow } from '../workflows/entity/workflow.entity';
|
import { Workflow } from '../workflows/entity/workflow.entity';
|
||||||
import { Worktype } from '../worktypes/entity/worktype.entity';
|
import { Worktype } from '../worktypes/entity/worktype.entity';
|
||||||
|
import { Context } from '../../common/log';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersRepositoryService {
|
export class UsersRepositoryService {
|
||||||
@ -340,7 +341,10 @@ export class UsersRepositoryService {
|
|||||||
* @param externalId
|
* @param externalId
|
||||||
* @returns User[]
|
* @returns User[]
|
||||||
*/
|
*/
|
||||||
async findSameAccountUsers(external_id: string): Promise<User[]> {
|
async findSameAccountUsers(
|
||||||
|
external_id: string,
|
||||||
|
context: Context,
|
||||||
|
): Promise<User[]> {
|
||||||
return await this.dataSource.transaction(async (entityManager) => {
|
return await this.dataSource.transaction(async (entityManager) => {
|
||||||
const repo = entityManager.getRepository(User);
|
const repo = entityManager.getRepository(User);
|
||||||
|
|
||||||
@ -359,8 +363,9 @@ export class UsersRepositoryService {
|
|||||||
license: true,
|
license: true,
|
||||||
},
|
},
|
||||||
where: { account_id: accountId },
|
where: { account_id: accountId },
|
||||||
|
comment: `${context.getTrackingId()}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return dbUsers;
|
return dbUsers;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -471,6 +476,14 @@ export class UsersRepositoryService {
|
|||||||
id: 'DESC',
|
id: 'DESC',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const latestPrivacyNoticeInfo = await termRepo.findOne({
|
||||||
|
where: {
|
||||||
|
document_type: TERM_TYPE.PRIVACY_NOTICE,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
const latestDpaInfo = await termRepo.findOne({
|
const latestDpaInfo = await termRepo.findOne({
|
||||||
where: {
|
where: {
|
||||||
document_type: TERM_TYPE.DPA,
|
document_type: TERM_TYPE.DPA,
|
||||||
@ -479,16 +492,18 @@ export class UsersRepositoryService {
|
|||||||
id: 'DESC',
|
id: 'DESC',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {
|
||||||
if (!latestEulaInfo || !latestDpaInfo) {
|
|
||||||
throw new TermInfoNotFoundError(`Terms info is not found.`);
|
throw new TermInfoNotFoundError(`Terms info is not found.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tier: user.account.tier,
|
tier: user.account.tier,
|
||||||
acceptedEulaVersion: user.accepted_eula_version ?? undefined,
|
acceptedEulaVersion: user.accepted_eula_version ?? undefined,
|
||||||
|
acceptedPrivacyNoticeVersion:
|
||||||
|
user.accepted_privacy_notice_version ?? undefined,
|
||||||
acceptedDpaVersion: user.accepted_dpa_version ?? undefined,
|
acceptedDpaVersion: user.accepted_dpa_version ?? undefined,
|
||||||
latestEulaVersion: latestEulaInfo.version,
|
latestEulaVersion: latestEulaInfo.version,
|
||||||
|
latestPrivacyNoticeVersion: latestPrivacyNoticeInfo.version,
|
||||||
latestDpaVersion: latestDpaInfo.version,
|
latestDpaVersion: latestDpaInfo.version,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -498,12 +513,14 @@ export class UsersRepositoryService {
|
|||||||
* 同意済み利用規約のバージョンを更新する
|
* 同意済み利用規約のバージョンを更新する
|
||||||
* @param externalId
|
* @param externalId
|
||||||
* @param eulaVersion
|
* @param eulaVersion
|
||||||
|
* @param privacyNoticeVersion
|
||||||
* @param dpaVersion
|
* @param dpaVersion
|
||||||
* @returns update
|
* @returns update
|
||||||
*/
|
*/
|
||||||
async updateAcceptedTermsVersion(
|
async updateAcceptedTermsVersion(
|
||||||
externalId: string,
|
externalId: string,
|
||||||
eulaVersion: string,
|
eulaVersion: string,
|
||||||
|
privacyNoticeVersion: string,
|
||||||
dpaVersion: string | undefined,
|
dpaVersion: string | undefined,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.dataSource.transaction(async (entityManager) => {
|
await this.dataSource.transaction(async (entityManager) => {
|
||||||
@ -531,6 +548,11 @@ export class UsersRepositoryService {
|
|||||||
if (!eulaVersion) {
|
if (!eulaVersion) {
|
||||||
throw new UpdateTermsVersionNotSetError(`EULA version param not set.`);
|
throw new UpdateTermsVersionNotSetError(`EULA version param not set.`);
|
||||||
}
|
}
|
||||||
|
if (!privacyNoticeVersion) {
|
||||||
|
throw new UpdateTermsVersionNotSetError(
|
||||||
|
`PrivacyNotice version param not set.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (user.account.tier !== TIERS.TIER5 && !dpaVersion) {
|
if (user.account.tier !== TIERS.TIER5 && !dpaVersion) {
|
||||||
throw new UpdateTermsVersionNotSetError(
|
throw new UpdateTermsVersionNotSetError(
|
||||||
`DPA version param not set. User's tier: ${user.account.tier}`,
|
`DPA version param not set. User's tier: ${user.account.tier}`,
|
||||||
@ -538,6 +560,8 @@ export class UsersRepositoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.accepted_eula_version = eulaVersion;
|
user.accepted_eula_version = eulaVersion;
|
||||||
|
user.accepted_privacy_notice_version =
|
||||||
|
privacyNoticeVersion ?? user.accepted_privacy_notice_version;
|
||||||
user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version;
|
user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version;
|
||||||
await userRepo.update({ id: user.id }, user);
|
await userRepo.update({ id: user.id }, user);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user