Merge branch 'develop' of https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud into develop
This commit is contained in:
commit
215ccbee0c
@ -58,6 +58,7 @@ jobs:
|
||||
npm run test
|
||||
env:
|
||||
JWT_PUBLIC_KEY: $(token-public-key)
|
||||
JWT_PRIVATE_KEY: $(token-private-key)
|
||||
SENDGRID_API_KEY: $(sendgrid-api-key)
|
||||
NOTIFICATION_HUB_NAME: $(notification-hub-name)
|
||||
NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string)
|
||||
@ -73,6 +74,12 @@ jobs:
|
||||
ADB2C_TENANT_ID: $(adb2c-tenant-id)
|
||||
ADB2C_CLIENT_ID: $(adb2c-client-id)
|
||||
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
|
||||
MAIL_FROM: xxxxxx
|
||||
APP_DOMAIN: xxxxxxxxx
|
||||
EMAIL_CONFIRM_LIFETIME : 0
|
||||
TENANT_NAME : xxxxxxxxxxxx
|
||||
SIGNIN_FLOW_NAME : xxxxxxxxxxxx
|
||||
STORAGE_TOKEN_EXPIRE_TIME : 0
|
||||
- task: Docker@0
|
||||
displayName: build
|
||||
inputs:
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import TopPage from "pages/TopPage";
|
||||
import AuthPage from "pages/AuthPage";
|
||||
import LoginPage from "pages/LoginPage";
|
||||
import SamplePage from "pages/SamplePage";
|
||||
import { AuthErrorPage } from "pages/ErrorPage";
|
||||
@ -20,18 +21,21 @@ import WorkflowPage from "pages/WorkflowPage";
|
||||
import TypistGroupSettingPage from "pages/TypistGroupSettingPage";
|
||||
import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage";
|
||||
import AccountPage from "pages/AccountPage";
|
||||
import AcceptToUsePage from "pages/TermsPage";
|
||||
import { TemplateFilePage } from "pages/TemplateFilePage";
|
||||
import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
<Routes>
|
||||
<Route path="/" element={<TopPage />} />
|
||||
<Route path="/auth" element={<AuthPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/authError" element={<AuthErrorPage />} />
|
||||
<Route
|
||||
path="/signup"
|
||||
element={<SignupPage completeTo="/signup/complete" />}
|
||||
/>
|
||||
<Route path="/terms" element={<AcceptToUsePage />} />
|
||||
<Route path="/signup/complete" element={<SignupCompletePage />} />
|
||||
<Route path="/mail-confirm/" element={<VerifyPage />} />
|
||||
<Route path="/mail-confirm/user" element={<UserVerifyPage />} />
|
||||
|
||||
@ -127,7 +127,7 @@ export interface AllocatableLicenseInfo {
|
||||
* @type {string}
|
||||
* @memberof AllocatableLicenseInfo
|
||||
*/
|
||||
'expiryDate': string;
|
||||
'expiryDate'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@ -2561,6 +2561,44 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorktype: async (id: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteWorktype', 'id', id)
|
||||
const localVarPath = `/accounts/worktypes/{id}/delete`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication bearer required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -3340,6 +3378,17 @@ export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccountAndData(deleteAccountRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteWorktype(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteWorktype(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -3616,6 +3665,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
|
||||
deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteAccountAndData(deleteAccountRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteWorktype(id: number, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.deleteWorktype(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
@ -3888,6 +3947,18 @@ export class AccountsApi extends BaseAPI {
|
||||
return AccountsApiFp(this.configuration).deleteAccountAndData(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {number} id Worktypeの内部ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AccountsApi
|
||||
*/
|
||||
public deleteWorktype(id: number, options?: AxiosRequestConfig) {
|
||||
return AccountsApiFp(this.configuration).deleteWorktype(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
|
||||
@ -18,6 +18,7 @@ import worktype from "features/workflow/worktype/worktypeSlice";
|
||||
import account from "features/account/accountSlice";
|
||||
import template from "features/workflow/template/templateSlice";
|
||||
import workflow from "features/workflow/workflowSlice";
|
||||
import terms from "features/terms/termsSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -40,6 +41,7 @@ export const store = configureStore({
|
||||
account,
|
||||
template,
|
||||
workflow,
|
||||
terms,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ export const errorCodes = [
|
||||
"E010206", // DBのTierが想定外の値エラー
|
||||
"E010207", // ユーザーのRole変更不可エラー
|
||||
"E010208", // ユーザーの暗号化パスワード不足エラー
|
||||
"E010209", // ユーザーの同意済み利用規約バージョンが最新でないエラー
|
||||
"E010301", // メールアドレス登録済みエラー
|
||||
"E010302", // authorId重複エラー
|
||||
"E010401", // PONumber重複エラー
|
||||
@ -55,6 +56,7 @@ export const errorCodes = [
|
||||
"E011001", // ワークタイプ重複エラー
|
||||
"E011002", // ワークタイプ登録上限超過エラー
|
||||
"E011003", // ワークタイプ不在エラー
|
||||
"E011004", // ワークタイプ使用中エラー
|
||||
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
"E013002", // ワークフロー不在エラー
|
||||
] as const;
|
||||
|
||||
@ -81,3 +81,21 @@ const isErrorResponse = (error: unknown): error is ErrorResponse => {
|
||||
|
||||
const isErrorCode = (errorCode: string): errorCode is ErrorCodeType =>
|
||||
errorCodes.includes(errorCode as ErrorCodeType);
|
||||
|
||||
export const isErrorObject = (
|
||||
data: unknown
|
||||
): data is { error: ErrorObject } => {
|
||||
if (
|
||||
data &&
|
||||
typeof data === "object" &&
|
||||
"error" in data &&
|
||||
typeof (data as { error: ErrorObject }).error === "object" &&
|
||||
typeof (data as { error: ErrorObject }).error.message === "string" &&
|
||||
typeof (data as { error: ErrorObject }).error.code === "string" &&
|
||||
(typeof (data as { error: ErrorObject }).error.statusCode === "number" ||
|
||||
(data as { error: ErrorObject }).error.statusCode === undefined)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ export const msalConfig: Configuration = {
|
||||
clientId: import.meta.env.VITE_B2C_CLIENTID,
|
||||
authority: import.meta.env.VITE_B2C_AUTHORITY,
|
||||
knownAuthorities: [import.meta.env.VITE_B2C_KNOWNAUTHORITIES],
|
||||
redirectUri: `${globalThis.location.origin}/login`,
|
||||
redirectUri: `${globalThis.location.origin}/auth`,
|
||||
navigateToLoginRequestUrl: false,
|
||||
},
|
||||
cache: {
|
||||
|
||||
@ -62,3 +62,16 @@ export const isIdToken = (arg: any): arg is IdToken => {
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getIdTokenFromLocalStorage = (
|
||||
localStorageKeyforIdToken: string
|
||||
): string | null => {
|
||||
const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
return idTokenObject.secret;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -1,17 +1,26 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { LoginState } from "./state";
|
||||
import { loginAsync } from "./operations";
|
||||
|
||||
const initialState: LoginState = {
|
||||
apps: {
|
||||
LoginApiCallStatus: "none",
|
||||
localStorageKeyforIdToken: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const loginSlice = createSlice({
|
||||
name: "login",
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
changeLocalStorageKeyforIdToken: (
|
||||
state,
|
||||
action: PayloadAction<{ localStorageKeyforIdToken: string }>
|
||||
) => {
|
||||
const { localStorageKeyforIdToken } = action.payload;
|
||||
state.apps.localStorageKeyforIdToken = localStorageKeyforIdToken;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(loginAsync.pending, (state) => {
|
||||
state.apps.LoginApiCallStatus = "pending";
|
||||
@ -25,4 +34,5 @@ export const loginSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeLocalStorageKeyforIdToken } = loginSlice.actions;
|
||||
export default loginSlice.reducer;
|
||||
|
||||
@ -3,6 +3,7 @@ import type { RootState } from "app/store";
|
||||
import { setToken } from "features/auth/authSlice";
|
||||
import { AuthApi } from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||
|
||||
export const loginAsync = createAsyncThunk<
|
||||
{
|
||||
@ -14,7 +15,7 @@ export const loginAsync = createAsyncThunk<
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
/* Empty Object */
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("login/loginAsync", async (args, thunkApi) => {
|
||||
@ -41,6 +42,8 @@ export const loginAsync = createAsyncThunk<
|
||||
|
||||
return {};
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue({});
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,3 +4,7 @@ export const selectLoginApiCallStatus = (
|
||||
state: RootState
|
||||
): "fulfilled" | "rejected" | "none" | "pending" =>
|
||||
state.login.apps.LoginApiCallStatus;
|
||||
|
||||
export const selectLocalStorageKeyforIdToken = (
|
||||
state: RootState
|
||||
): string | null => state.login.apps.localStorageKeyforIdToken;
|
||||
|
||||
@ -4,4 +4,5 @@ export interface LoginState {
|
||||
|
||||
export interface Apps {
|
||||
LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending";
|
||||
localStorageKeyforIdToken: string | null;
|
||||
}
|
||||
|
||||
@ -3,10 +3,12 @@ import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { getTranslationID } from "translation";
|
||||
import { closeSnackbar, openSnackbar } from "features/ui/uiSlice";
|
||||
import { TERMS_DOCUMENT_TYPE } from "features/terms/constants";
|
||||
import {
|
||||
AccountsApi,
|
||||
CreateAccountRequest,
|
||||
GetDealersResponse,
|
||||
TermsApi,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
@ -93,3 +95,42 @@ export const getDealersAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getLatestEulaVersionAsync = createAsyncThunk<
|
||||
string,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("login/getLatestEulaVersionAsync", async (args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const termsApi = new TermsApi(config);
|
||||
|
||||
try {
|
||||
const termsInfo = await termsApi.getTermsInfo();
|
||||
const latestEulaVersion = termsInfo.data.termsInfo.find(
|
||||
(val) => val.documentType === TERMS_DOCUMENT_TYPE.EULA
|
||||
);
|
||||
if (!latestEulaVersion) {
|
||||
throw new Error("EULA info is not found");
|
||||
}
|
||||
return latestEulaVersion.version;
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,3 +72,6 @@ export const selectSelectedDealer = (state: RootState) => {
|
||||
const { dealer } = state.signup.apps;
|
||||
return dealers.find((x: Dealer) => x.id === dealer);
|
||||
};
|
||||
|
||||
export const selectEulaVersion = (state: RootState) =>
|
||||
state.signup.domain.eulaVersion;
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { SignupState } from "./state";
|
||||
import { getDealersAsync, signupAsync } from "./operations";
|
||||
import {
|
||||
getDealersAsync,
|
||||
getLatestEulaVersionAsync,
|
||||
signupAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: SignupState = {
|
||||
apps: {
|
||||
@ -15,6 +19,7 @@ const initialState: SignupState = {
|
||||
},
|
||||
domain: {
|
||||
dealers: [],
|
||||
eulaVersion: "",
|
||||
},
|
||||
};
|
||||
|
||||
@ -74,6 +79,15 @@ export const signupSlice = createSlice({
|
||||
builder.addCase(getDealersAsync.rejected, () => {
|
||||
//
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.pending, () => {
|
||||
//
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.fulfilled, (state, action) => {
|
||||
state.domain.eulaVersion = action.payload;
|
||||
});
|
||||
builder.addCase(getLatestEulaVersionAsync.rejected, () => {
|
||||
//
|
||||
});
|
||||
},
|
||||
});
|
||||
export const {
|
||||
|
||||
@ -18,4 +18,5 @@ export interface Apps {
|
||||
|
||||
export interface Domain {
|
||||
dealers: Dealer[];
|
||||
eulaVersion: string;
|
||||
}
|
||||
|
||||
8
dictation_client/src/features/terms/constants.ts
Normal file
8
dictation_client/src/features/terms/constants.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 利用規約の種類
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERMS_DOCUMENT_TYPE = {
|
||||
DPA: "DPA",
|
||||
EULA: "EULA",
|
||||
} as const;
|
||||
4
dictation_client/src/features/terms/index.ts
Normal file
4
dictation_client/src/features/terms/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./termsSlice";
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
158
dictation_client/src/features/terms/operations.ts
Normal file
158
dictation_client/src/features/terms/operations.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { ErrorObject, createErrorObject } from "common/errors";
|
||||
import { getTranslationID } from "translation";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getIdTokenFromLocalStorage } from "common/token";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import {
|
||||
UsersApi,
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
AccountsApi,
|
||||
TermsApi,
|
||||
GetTermsInfoResponse,
|
||||
} from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
|
||||
export const getAccountInfoMinimalAccessAsync = createAsyncThunk<
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
{
|
||||
localStorageKeyforIdToken: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/getAccountInfoMinimalAccessAsync", async (args, thunkApi) => {
|
||||
const { localStorageKeyforIdToken } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
// IDトークンの取得
|
||||
const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken);
|
||||
|
||||
// IDトークンが取得できない場合エラーとする
|
||||
if (!idToken) {
|
||||
throw new Error("Unable to retrieve the ID token.");
|
||||
}
|
||||
const res = await accountApi.getAccountInfoMinimalAccess(
|
||||
{ idToken },
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const getTermsInfoAsync = createAsyncThunk<
|
||||
GetTermsInfoResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/getTermsInfoAsync", async (_args, thunkApi) => {
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const termsApi = new TermsApi(config);
|
||||
|
||||
try {
|
||||
const termsInfo = await termsApi.getTermsInfo({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
return termsInfo.data;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const updateAcceptedVersionAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
tier: number;
|
||||
localStorageKeyforIdToken: string;
|
||||
updateAccceptVersions: {
|
||||
acceptedVerDPA: string;
|
||||
acceptedVerEULA: string;
|
||||
};
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("accept/UpdateAcceptedVersionAsync", async (args, thunkApi) => {
|
||||
const { tier, localStorageKeyforIdToken, updateAccceptVersions } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const userApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
// IDトークンの取得
|
||||
const idToken = getIdTokenFromLocalStorage(localStorageKeyforIdToken);
|
||||
|
||||
// IDトークンが取得できない場合エラーとする
|
||||
if (!idToken) {
|
||||
throw new Error("Unable to retrieve the ID token.");
|
||||
}
|
||||
await userApi.updateAcceptedVersion(
|
||||
{
|
||||
idToken,
|
||||
acceptedEULAVersion: updateAccceptVersions.acceptedVerEULA,
|
||||
acceptedDPAVersion: !(TIERS.TIER5 === tier.toString())
|
||||
? updateAccceptVersions.acceptedVerDPA
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
20
dictation_client/src/features/terms/selectors.ts
Normal file
20
dictation_client/src/features/terms/selectors.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { RootState } from "app/store";
|
||||
import { TERMS_DOCUMENT_TYPE } from "features/terms/constants";
|
||||
|
||||
export const selectTermVersions = (state: RootState) => {
|
||||
const { termsInfo } = state.terms.domain;
|
||||
|
||||
const acceptedVerDPA =
|
||||
termsInfo.find(
|
||||
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.DPA
|
||||
)?.version || "";
|
||||
|
||||
const acceptedVerEULA =
|
||||
termsInfo.find(
|
||||
(termInfo) => termInfo.documentType === TERMS_DOCUMENT_TYPE.EULA
|
||||
)?.version || "";
|
||||
|
||||
return { acceptedVerDPA, acceptedVerEULA };
|
||||
};
|
||||
|
||||
export const selectTier = (state: RootState) => state.terms.domain.tier;
|
||||
15
dictation_client/src/features/terms/state.ts
Normal file
15
dictation_client/src/features/terms/state.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TermInfo } from "../../api/api";
|
||||
|
||||
export interface AcceptState {
|
||||
domain: Domain;
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
tier: number;
|
||||
termsInfo: TermInfo[];
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
64
dictation_client/src/features/terms/termsSlice.ts
Normal file
64
dictation_client/src/features/terms/termsSlice.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { AcceptState } from "./state";
|
||||
import {
|
||||
getAccountInfoMinimalAccessAsync,
|
||||
getTermsInfoAsync,
|
||||
updateAcceptedVersionAsync,
|
||||
} from "./operations";
|
||||
|
||||
const initialState: AcceptState = {
|
||||
domain: {
|
||||
tier: 0,
|
||||
termsInfo: [
|
||||
{
|
||||
documentType: "",
|
||||
version: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
apps: {
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const termsSlice = createSlice({
|
||||
name: "terms",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getAccountInfoMinimalAccessAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(
|
||||
getAccountInfoMinimalAccessAsync.fulfilled,
|
||||
(state, actions) => {
|
||||
state.apps.isLoading = false;
|
||||
state.domain.tier = actions.payload.tier;
|
||||
}
|
||||
);
|
||||
builder.addCase(getAccountInfoMinimalAccessAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.fulfilled, (state, actions) => {
|
||||
state.apps.isLoading = false;
|
||||
state.domain.termsInfo = actions.payload.termsInfo;
|
||||
});
|
||||
builder.addCase(getTermsInfoAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.fulfilled, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(updateAcceptedVersionAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default termsSlice.reducer;
|
||||
@ -87,7 +87,7 @@ export const userSlice = createSlice({
|
||||
action: PayloadAction<{ authorId: string | undefined }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
state.apps.addUser.authorId = authorId;
|
||||
state.apps.addUser.authorId = authorId?.toUpperCase();
|
||||
},
|
||||
changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => {
|
||||
const { autoRenew } = action.payload;
|
||||
@ -144,7 +144,7 @@ export const userSlice = createSlice({
|
||||
state.apps.updateUser.name = user.name;
|
||||
state.apps.updateUser.email = user.email;
|
||||
state.apps.updateUser.role = user.role as RoleType;
|
||||
state.apps.updateUser.authorId = user.authorId;
|
||||
state.apps.updateUser.authorId = user.authorId?.toUpperCase();
|
||||
state.apps.updateUser.encryption = user.encryption;
|
||||
state.apps.updateUser.encryptionPassword = undefined;
|
||||
state.apps.updateUser.prompt = user.prompt;
|
||||
@ -156,7 +156,7 @@ export const userSlice = createSlice({
|
||||
state.apps.selectedUser.name = user.name;
|
||||
state.apps.selectedUser.email = user.email;
|
||||
state.apps.selectedUser.role = user.role as RoleType;
|
||||
state.apps.selectedUser.authorId = user.authorId;
|
||||
state.apps.selectedUser.authorId = user.authorId?.toUpperCase();
|
||||
state.apps.selectedUser.encryption = user.encryption;
|
||||
state.apps.selectedUser.encryptionPassword = undefined;
|
||||
state.apps.selectedUser.prompt = user.prompt;
|
||||
@ -175,7 +175,7 @@ export const userSlice = createSlice({
|
||||
action: PayloadAction<{ authorId: string }>
|
||||
) => {
|
||||
const { authorId } = action.payload;
|
||||
state.apps.updateUser.authorId = authorId;
|
||||
state.apps.updateUser.authorId = authorId.toUpperCase();
|
||||
},
|
||||
changeUpdateEncryption: (
|
||||
state,
|
||||
@ -243,7 +243,8 @@ export const userSlice = createSlice({
|
||||
state.apps.licenseAllocateUser.id = selectedUser.id;
|
||||
state.apps.licenseAllocateUser.name = selectedUser.name;
|
||||
state.apps.licenseAllocateUser.email = selectedUser.email;
|
||||
state.apps.licenseAllocateUser.authorId = selectedUser.authorId;
|
||||
state.apps.licenseAllocateUser.authorId =
|
||||
selectedUser.authorId.toUpperCase();
|
||||
state.apps.licenseAllocateUser.licenseStatus = selectedUser.licenseStatus;
|
||||
state.apps.licenseAllocateUser.expiration = selectedUser.expiration;
|
||||
state.apps.licenseAllocateUser.remaining = selectedUser.remaining;
|
||||
|
||||
@ -342,3 +342,75 @@ export const updateActiveWorktypeAsync = createAsyncThunk<
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
export const deleteWorktypeAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{ worktypeId: number },
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("workflow/deleteWorktypeAsync", async (args, thunkApi) => {
|
||||
const { worktypeId } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const accountsApi = new AccountsApi(config);
|
||||
|
||||
try {
|
||||
await accountsApi.deleteWorktype(worktypeId, {
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
if (error.statusCode === 400) {
|
||||
if (error.code === "E011003") {
|
||||
// ワークタイプが削除済みの場合は成功扱いとする
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (error.code === "E011004") {
|
||||
// ワークタイプがワークフローで使用中の場合は削除できない
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID(
|
||||
"worktypeIdSetting.message.worktypeInUseError"
|
||||
),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
|
||||
78
dictation_client/src/pages/AuthPage/index.tsx
Normal file
78
dictation_client/src/pages/AuthPage/index.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AuthError } from "@azure/msal-browser";
|
||||
import { AppDispatch } from "app/store";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import {
|
||||
selectLoginApiCallStatus,
|
||||
changeLocalStorageKeyforIdToken,
|
||||
} from "features/login";
|
||||
import React, { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const AuthPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const status = useSelector(selectLoginApiCallStatus);
|
||||
|
||||
// TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。
|
||||
useEffect(() => {
|
||||
if (status !== "none") {
|
||||
// ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const loginResult = await instance.handleRedirectPromise();
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log({ loginResult }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
if (loginResult && loginResult.account) {
|
||||
const { homeAccountId, idTokenClaims } = loginResult.account;
|
||||
if (idTokenClaims && idTokenClaims.aud) {
|
||||
const localStorageKeyforIdToken = `${homeAccountId}-${
|
||||
import.meta.env.VITE_B2C_KNOWNAUTHORITIES
|
||||
}-idtoken-${idTokenClaims.aud}----`;
|
||||
|
||||
// AADB2Cログイン画面以外から本画面に遷移した場合用にIDトークン取得用キーをstateに保存
|
||||
dispatch(
|
||||
changeLocalStorageKeyforIdToken({
|
||||
localStorageKeyforIdToken,
|
||||
})
|
||||
);
|
||||
|
||||
// トークン取得と設定を行う
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.log({ e }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
// AAD B2Cの多要素認証画面やパスワードリセット画面で「cancel」をクリックすると、handleRedirectPromise()にてエラーが発生するため、
|
||||
// それをハンドリングして適切な画面遷移処理を行う。
|
||||
if (e instanceof AuthError) {
|
||||
// エラーコードはerrorMessageの中の一部として埋め込まれており完全一致で取得するのは筋が悪いため、部分一致で取得する。
|
||||
// TODO 他にもAADB2Cのエラーコードを使用する箇所が出てきた場合、定数化すること
|
||||
if (e.errorMessage.startsWith("AADB2C90091")) {
|
||||
navigate("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [instance, navigate, status, dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<h3>loading ...</h3>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthPage;
|
||||
@ -1,30 +1,39 @@
|
||||
import { useMsal } from "@azure/msal-react";
|
||||
import { AuthError } from "@azure/msal-browser";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { isIdToken } from "common/token";
|
||||
import { loadAccessToken, loadRefreshToken } from "features/auth/utils";
|
||||
import { loginAsync, selectLocalStorageKeyforIdToken } from "features/login";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import { loadAccessToken, loadRefreshToken } from "features/auth/utils";
|
||||
import { loginAsync, selectLoginApiCallStatus } from "features/login";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isErrorObject } from "common/errors";
|
||||
|
||||
const LoginPage: React.FC = (): JSX.Element => {
|
||||
const { instance } = useMsal();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [, i18n] = useTranslation();
|
||||
const status = useSelector(selectLoginApiCallStatus);
|
||||
const localStorageKeyforIdToken = useSelector(
|
||||
selectLocalStorageKeyforIdToken
|
||||
);
|
||||
|
||||
const login = useCallback(
|
||||
const tokenSet = useCallback(
|
||||
async (idToken: string) => {
|
||||
// ログイン処理呼び出し
|
||||
const { meta } = await dispatch(loginAsync({ idToken }));
|
||||
const { meta, payload } = await dispatch(loginAsync({ idToken }));
|
||||
|
||||
// ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する
|
||||
if (meta.requestStatus === "rejected") {
|
||||
if (isErrorObject(payload)) {
|
||||
// 未同意の規約がある場合は利用規約同意画面に遷移する
|
||||
if (payload.error.code === "E010209") {
|
||||
navigate("/terms");
|
||||
return;
|
||||
}
|
||||
}
|
||||
instance.logoutRedirect({
|
||||
postLogoutRedirectUri: "/AuthError",
|
||||
});
|
||||
@ -48,53 +57,26 @@ const LoginPage: React.FC = (): JSX.Element => {
|
||||
[dispatch, i18n.language, instance, navigate]
|
||||
);
|
||||
|
||||
// TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。
|
||||
useEffect(() => {
|
||||
if (status !== "none") {
|
||||
// ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない
|
||||
// AADB2Cのログイン画面とLoginPageを経由していない場合はトップページに遷移する
|
||||
if (!localStorageKeyforIdToken) {
|
||||
navigate("/");
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const loginResult = await instance.handleRedirectPromise();
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.log({ loginResult }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
if (loginResult && loginResult.account) {
|
||||
const { homeAccountId, idTokenClaims } = loginResult.account;
|
||||
if (idTokenClaims && idTokenClaims.aud) {
|
||||
// IDトークンの取得
|
||||
const idTokenString = localStorage.getItem(
|
||||
`${homeAccountId}-${
|
||||
import.meta.env.VITE_B2C_KNOWNAUTHORITIES
|
||||
}-idtoken-${idTokenClaims.aud}----`
|
||||
);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
await login(idTokenObject.secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line
|
||||
console.log({ e }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除(eslint-disable含めて)する。
|
||||
|
||||
// AAD B2Cの多要素認証画面やパスワードリセット画面で「cancel」をクリックすると、handleRedirectPromise()にてエラーが発生するため、
|
||||
// それをハンドリングして適切な画面遷移処理を行う。
|
||||
if (e instanceof AuthError) {
|
||||
// エラーコードはerrorMessageの中の一部として埋め込まれており完全一致で取得するのは筋が悪いため、部分一致で取得する。
|
||||
// TODO 他にもAADB2Cのエラーコードを使用する箇所が出てきた場合、定数化すること
|
||||
if (e.errorMessage.startsWith("AADB2C90091")) {
|
||||
navigate("/");
|
||||
}
|
||||
// IDトークンの取得
|
||||
const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
|
||||
if (idTokenString) {
|
||||
const idTokenObject = JSON.parse(idTokenString);
|
||||
if (isIdToken(idTokenObject)) {
|
||||
await tokenSet(idTokenObject.secret);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [instance, login, navigate, status]);
|
||||
// 画面描画後のみ実行するため引数を設定しない
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
selectEmail,
|
||||
selectPassword,
|
||||
selectSelectedDealer,
|
||||
selectEulaVersion,
|
||||
} from "../../features/signup/selectors";
|
||||
import { signupAsync } from "../../features/signup/operations";
|
||||
|
||||
@ -27,6 +28,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
const adminMail = useSelector(selectEmail);
|
||||
const adminPassword = useSelector(selectPassword);
|
||||
const dealer = useSelector(selectSelectedDealer);
|
||||
const acceptedEulaVersion = useSelector(selectEulaVersion);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(
|
||||
@ -37,7 +39,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
adminName,
|
||||
adminMail,
|
||||
adminPassword,
|
||||
acceptedEulaVersion: "",
|
||||
acceptedEulaVersion,
|
||||
acceptedDpaVersion: "",
|
||||
token: "",
|
||||
})
|
||||
@ -50,6 +52,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
|
||||
adminName,
|
||||
adminMail,
|
||||
adminPassword,
|
||||
acceptedEulaVersion,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@ -24,7 +24,10 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { getDealersAsync } from "features/signup/operations";
|
||||
import {
|
||||
getDealersAsync,
|
||||
getLatestEulaVersionAsync,
|
||||
} from "features/signup/operations";
|
||||
import { LANGUAGE_LIST } from "features/top/constants";
|
||||
import { openSnackbar } from "features/ui";
|
||||
import { COUNTRY_LIST } from "./constants";
|
||||
@ -84,6 +87,7 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
// 入力画面の初期化時の処理
|
||||
useEffect(() => {
|
||||
dispatch(getDealersAsync());
|
||||
dispatch(getLatestEulaVersionAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -264,10 +268,9 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
/>
|
||||
{isPushCreateButton && hasErrorEmptyAdminName && (
|
||||
<span className={styles.formError}>
|
||||
{" "}
|
||||
{t(
|
||||
{` ${t(
|
||||
getTranslationID("signupPage.message.inputEmptyError")
|
||||
)}
|
||||
)}`}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
@ -369,8 +372,9 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("signupPage.label.termsLink"))}
|
||||
</a>{" "}
|
||||
{t(getTranslationID("signupPage.label.termsLinkFor"))} <br />
|
||||
</a>
|
||||
{` ${t(getTranslationID("signupPage.label.termsLinkFor"))} `}
|
||||
<br />
|
||||
<label htmlFor="check-box">
|
||||
<input
|
||||
id="check-box"
|
||||
|
||||
188
dictation_client/src/pages/TermsPage/index.tsx
Normal file
188
dictation_client/src/pages/TermsPage/index.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
/* eslint-disable jsx-a11y/label-has-associated-control */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import Header from "components/header";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { TIERS } from "components/auth/constants";
|
||||
import Footer from "components/footer";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
getAccountInfoMinimalAccessAsync,
|
||||
getTermsInfoAsync,
|
||||
updateAcceptedVersionAsync,
|
||||
selectTier,
|
||||
selectTermVersions,
|
||||
} from "features//terms";
|
||||
import { selectLocalStorageKeyforIdToken } from "features/login";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const TermsPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const updateAccceptVersions = useSelector(selectTermVersions);
|
||||
const localStorageKeyforIdToken = useSelector(
|
||||
selectLocalStorageKeyforIdToken
|
||||
);
|
||||
const tier = useSelector(selectTier);
|
||||
|
||||
const [isCheckedEula, setIsCheckedEula] = useState(false);
|
||||
const [isCheckedDpa, setIsCheckedDpa] = useState(false);
|
||||
|
||||
const [isClickedEulaLink, setIsClickedEulaLink] = useState(false);
|
||||
const [isClickedDpaLink, setIsClickedDpaLink] = useState(false);
|
||||
|
||||
// 画面起動時
|
||||
useEffect(() => {
|
||||
dispatch(getTermsInfoAsync());
|
||||
if (localStorageKeyforIdToken) {
|
||||
dispatch(getAccountInfoMinimalAccessAsync({ localStorageKeyforIdToken }));
|
||||
} else {
|
||||
// ログイン画面を経由していないため、トップページに遷移する
|
||||
navigate("/");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// ユーザーが第5階層であるかどうかを判定する(アクセストークンから自分の階層を取得できないので自前で作成)
|
||||
const isTier5 = () => TIERS.TIER5.includes(tier.toString());
|
||||
|
||||
// ボタン押下可否判定ロジック
|
||||
const canClickButton = () => {
|
||||
if (isTier5()) {
|
||||
return isCheckedEula;
|
||||
}
|
||||
return isCheckedEula && isCheckedDpa;
|
||||
};
|
||||
|
||||
// ボタン押下時処理
|
||||
const onAcceptTermsOfUse = useCallback(async () => {
|
||||
if (
|
||||
localStorageKeyforIdToken &&
|
||||
updateAccceptVersions.acceptedVerDPA !== "" &&
|
||||
updateAccceptVersions.acceptedVerEULA !== ""
|
||||
) {
|
||||
const { meta } = await dispatch(
|
||||
updateAcceptedVersionAsync({
|
||||
tier,
|
||||
localStorageKeyforIdToken,
|
||||
updateAccceptVersions,
|
||||
})
|
||||
);
|
||||
|
||||
// 同意済バージョンが更新できたら、再度トークン生成を行う
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
}, [
|
||||
navigate,
|
||||
localStorageKeyforIdToken,
|
||||
updateAccceptVersions,
|
||||
tier,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.mainSmall}>
|
||||
<div>
|
||||
<h1 className={`${styles.marginBtm1} ${styles.alignCenter}`}>
|
||||
{t(getTranslationID("termsPage.label.title"))}
|
||||
</h1>
|
||||
</div>
|
||||
<section className={styles.form}>
|
||||
<form action="" name="" method="">
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
href="/" /* TODO Eula用の利用規約リンクが決定したら設定を行う */
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedEulaLink(true)}
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfEula"))}
|
||||
</a>
|
||||
{` ${t(getTranslationID("termsPage.label.forOdds"))}`}
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isCheckedEula}
|
||||
className={styles.formCheck}
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedEula(e.target.checked)}
|
||||
disabled={!isClickedEulaLink}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
)}
|
||||
</label>
|
||||
</p>
|
||||
</dd>
|
||||
{/* 第五階層以外の場合はEulaのリンクをあわせて表示する */}
|
||||
{!isTier5() && (
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
href="/" /* TODO Dpa用の利用規約リンクが決定したら設定を行う */
|
||||
target="_blank"
|
||||
className={styles.linkTx}
|
||||
onClick={() => setIsClickedDpaLink(true)}
|
||||
>
|
||||
{t(getTranslationID("termsPage.label.linkOfDpa"))}
|
||||
</a>
|
||||
{` ${t(getTranslationID("termsPage.label.forOdds"))}`}
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isCheckedDpa}
|
||||
className={styles.formCheck}
|
||||
value=""
|
||||
onChange={(e) => setIsCheckedDpa(e.target.checked)}
|
||||
disabled={!isClickedDpaLink}
|
||||
/>
|
||||
{t(
|
||||
getTranslationID("termsPage.label.checkBoxForConsent")
|
||||
)}
|
||||
</label>
|
||||
</p>
|
||||
</dd>
|
||||
)}
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<p>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(getTranslationID("termsPage.label.button"))}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||
canClickButton() ? styles.isActive : ""
|
||||
}`}
|
||||
onClick={onAcceptTermsOfUse}
|
||||
/>
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsPage;
|
||||
@ -200,7 +200,11 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
|
||||
className={styles.formInput}
|
||||
value={addUser.authorId ?? undefined}
|
||||
onChange={(e) => {
|
||||
dispatch(changeAuthorId({ authorId: e.target.value }));
|
||||
dispatch(
|
||||
changeAuthorId({
|
||||
authorId: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isPushCreateButton && hasErrorEmptyAuthorId && (
|
||||
|
||||
@ -184,7 +184,9 @@ export const UserUpdatePopup: React.FC<UserUpdatePopupProps> = (props) => {
|
||||
className={styles.formInput}
|
||||
onChange={(e) => {
|
||||
dispatch(
|
||||
changeUpdateAuthorId({ authorId: e.target.value })
|
||||
changeUpdateAuthorId({
|
||||
authorId: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
import undo from "assets/images/undo.svg";
|
||||
@ -18,6 +18,7 @@ import {
|
||||
selectIsLoading,
|
||||
selectWorktypes,
|
||||
selectActiveWorktypeId,
|
||||
deleteWorktypeAsync,
|
||||
} from "features/workflow/worktype";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
|
||||
@ -86,6 +87,23 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedActiveWorktypeId]);
|
||||
|
||||
// 削除ボタン押下時の処理
|
||||
const onDeleteWoktype = useCallback(
|
||||
async (worktypeId: number) => {
|
||||
if (
|
||||
/* eslint-disable-next-line no-alert */
|
||||
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { meta } = await dispatch(deleteWorktypeAsync({ worktypeId }));
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(listWorktypesAsync());
|
||||
}
|
||||
},
|
||||
[dispatch, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddWorktypeIdPopup
|
||||
@ -253,9 +271,10 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
// onClick={}
|
||||
onClick={() => onDeleteWoktype(worktype.id)}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
|
||||
@ -2266,8 +2266,7 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
||||
center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
@ -2278,8 +2277,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label:hover,
|
||||
.formChange ul.holdMember li input:checked + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||
right center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||
center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange > p {
|
||||
@ -2432,8 +2431,7 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input + label:hover,
|
||||
.formChange ul.holdMember li input + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left
|
||||
center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label,
|
||||
@ -2444,8 +2442,8 @@ tr.isSelected .menuInTable li a.isDisable {
|
||||
}
|
||||
.formChange ul.chooseMember li input:checked + label:hover,
|
||||
.formChange ul.holdMember li input:checked + label:hover {
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat
|
||||
right center;
|
||||
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
|
||||
center;
|
||||
background-size: 1.3rem;
|
||||
}
|
||||
.formChange > p {
|
||||
|
||||
@ -429,7 +429,8 @@
|
||||
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(de)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -499,13 +500,14 @@
|
||||
"backToTopPageLink": "(de)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"AgreeToUsePage": {
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(de)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(de)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(de)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(de)for ODDS."
|
||||
"forOdds": "(de)for ODDS.",
|
||||
"button": "(de)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,7 +429,8 @@
|
||||
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -499,13 +500,14 @@
|
||||
"backToTopPageLink": "Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"AgreeToUsePage": {
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "Click here to read the terms of use.",
|
||||
"linkOfDpa": "Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "Yes, I agree to the terms of use.",
|
||||
"forOdds": "for ODDS."
|
||||
"forOdds": "for ODDS.",
|
||||
"button": "Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,7 +429,8 @@
|
||||
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(es)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -499,13 +500,14 @@
|
||||
"backToTopPageLink": "(es)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"AgreeToUsePage": {
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(es)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(es)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(es)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(es)for ODDS."
|
||||
"forOdds": "(es)for ODDS.",
|
||||
"button": "(es)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -429,7 +429,8 @@
|
||||
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
|
||||
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
|
||||
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください"
|
||||
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
|
||||
"worktypeInUseError": "(fr)このWorktype IDはルーティングルールで使用されているため削除できません。"
|
||||
}
|
||||
},
|
||||
"templateFilePage": {
|
||||
@ -499,13 +500,14 @@
|
||||
"backToTopPageLink": "(fr)Back to TOP Page"
|
||||
}
|
||||
},
|
||||
"AgreeToUsePage": {
|
||||
"termsPage": {
|
||||
"label": {
|
||||
"title": "(fr)Terms of Use has updated. Please confirm again.",
|
||||
"linkOfEula": "(fr)Click here to read the terms of use.",
|
||||
"linkOfDpa": "(fr)Click here to read the terms of use.",
|
||||
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
|
||||
"forOdds": "(fr)for ODDS."
|
||||
"forOdds": "(fr)for ODDS.",
|
||||
"button": "(fr)Continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
-- +migrate Up
|
||||
ALTER TABLE `checkout_permission` DROP FOREIGN KEY `checkout_permission_fk_task_id`;
|
||||
ALTER TABLE `tasks` DROP FOREIGN KEY `tasks_fk_account_id`;
|
||||
ALTER TABLE `template_files` DROP FOREIGN KEY `template_files_fk_account_id`;
|
||||
ALTER TABLE `option_items` DROP FOREIGN KEY `option_items_fk_worktype_id`;
|
||||
ALTER TABLE `worktypes` DROP FOREIGN KEY `worktypes_fk_account_id`;
|
||||
ALTER TABLE `audio_option_items` DROP FOREIGN KEY `audio_option_items_fk_audio_file_id`;
|
||||
ALTER TABLE `audio_files` DROP FOREIGN KEY `audio_files_fk_account_id`;
|
||||
ALTER TABLE `user_group_member` DROP FOREIGN KEY `user_group_member_fk_user_group_id`;
|
||||
ALTER TABLE `user_group` DROP FOREIGN KEY `user_group_fk_account_id`;
|
||||
ALTER TABLE `license_allocation_history` DROP FOREIGN KEY `license_allocation_history_fk_account_id`;
|
||||
ALTER TABLE `card_licenses` DROP FOREIGN KEY `card_licenses_fk_license_id`;
|
||||
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_fk_account_id`;
|
||||
ALTER TABLE `license_orders` DROP FOREIGN KEY `license_orders_fk_from_account_id`;
|
||||
ALTER TABLE `sort_criteria` DROP FOREIGN KEY `sort_criteria_fk_user_id`;
|
||||
ALTER TABLE `users` DROP FOREIGN KEY `users_fk_account_id`;
|
||||
|
||||
-- +migrate Down
|
||||
ALTER TABLE `checkout_permission` ADD CONSTRAINT `checkout_permission_fk_task_id` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `tasks` ADD CONSTRAINT `tasks_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `template_files` ADD CONSTRAINT `template_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `option_items` ADD CONSTRAINT `option_items_fk_worktype_id` FOREIGN KEY (`worktype_id`) REFERENCES `worktypes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `worktypes` ADD CONSTRAINT `worktypes_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `audio_option_items` ADD CONSTRAINT `audio_option_items_fk_audio_file_id` FOREIGN KEY (`audio_file_id`) REFERENCES `audio_files` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `audio_files` ADD CONSTRAINT `audio_files_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `user_group_member` ADD CONSTRAINT `user_group_member_fk_user_group_id` FOREIGN KEY (`user_group_id`) REFERENCES `user_group` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `user_group` ADD CONSTRAINT `user_group_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `license_allocation_history` ADD CONSTRAINT `license_allocation_history_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `card_licenses` ADD CONSTRAINT `card_licenses_fk_license_id` FOREIGN KEY (`license_id`) REFERENCES `licenses` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `license_orders` ADD CONSTRAINT `license_orders_fk_from_account_id` FOREIGN KEY (`from_account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `sort_criteria` ADD CONSTRAINT `sort_criteria_fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE `users` ADD CONSTRAINT `users_fk_account_id` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,9 @@
|
||||
-- +migrate Up
|
||||
insert into terms(terms.document_type, terms.version) values('EURA', 'V0.1');
|
||||
insert into terms(terms.document_type, terms.version) values('DPA', 'V0.1');
|
||||
commit;
|
||||
|
||||
-- +migrate Down
|
||||
delete from terms where terms.document_type = 'EURA' and terms.version = 'V0.1';
|
||||
delete from terms where terms.document_type = 'DPA' and terms.version = 'V0.1';
|
||||
commit;
|
||||
@ -990,6 +990,59 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes/{id}/delete": {
|
||||
"post": {
|
||||
"operationId": "deleteWorktype",
|
||||
"summary": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Worktypeの内部ID",
|
||||
"schema": { "type": "number" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeleteWorktypeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/worktypes/{id}/option-items": {
|
||||
"get": {
|
||||
"operationId": "getOptionItems",
|
||||
@ -3706,6 +3759,7 @@
|
||||
"required": ["worktypeId"]
|
||||
},
|
||||
"UpdateWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"DeleteWorktypeResponse": { "type": "object", "properties": {} },
|
||||
"GetWorktypeOptionItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -4411,7 +4465,7 @@
|
||||
"licenseId": { "type": "number" },
|
||||
"expiryDate": { "format": "date-time", "type": "string" }
|
||||
},
|
||||
"required": ["licenseId", "expiryDate"]
|
||||
"required": ["licenseId"]
|
||||
},
|
||||
"GetAllocatableLicensesResponse": {
|
||||
"type": "object",
|
||||
|
||||
@ -57,6 +57,7 @@ export const ErrorCodes = [
|
||||
'E011001', // ワークタイプ重複エラー
|
||||
'E011002', // ワークタイプ登録上限超過エラー
|
||||
'E011003', // ワークタイプ不在エラー
|
||||
'E011004', // ワークタイプ使用中エラー
|
||||
'E012001', // テンプレートファイル不在エラー
|
||||
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
|
||||
'E013002', // ワークフロー不在エラー
|
||||
|
||||
@ -46,6 +46,7 @@ export const errors: Errors = {
|
||||
E011001: 'This WorkTypeID already used Error',
|
||||
E011002: 'WorkTypeID create limit exceeded Error',
|
||||
E011003: 'WorkTypeID not found Error',
|
||||
E011004: 'WorkTypeID is in use Error',
|
||||
E012001: 'Template file not found Error',
|
||||
E013001: 'AuthorId and WorktypeId pair already exists Error',
|
||||
E013002: 'Workflow not found Error',
|
||||
|
||||
@ -92,13 +92,15 @@ export class RoleGuard implements CanActivate {
|
||||
* @returns true/false
|
||||
*/
|
||||
checkRole(role: string): boolean {
|
||||
const { roles } = this.settings;
|
||||
|
||||
const settings = this.settings;
|
||||
if (!settings || !settings.roles) {
|
||||
return true;
|
||||
}
|
||||
const userRoles = role.split(' ');
|
||||
|
||||
// Role毎にAccessTokenの権限チェックを行う
|
||||
for (let i = 0; i < roles.length; i++) {
|
||||
const role = roles[i];
|
||||
for (let i = 0; i < settings.roles.length; i++) {
|
||||
const role = settings.roles[i];
|
||||
let isValid = false;
|
||||
if (Array.isArray(role)) {
|
||||
isValid = role.every((x) => userRoles.includes(x));
|
||||
@ -172,9 +174,12 @@ export class RoleGuard implements CanActivate {
|
||||
* @returns true/false
|
||||
*/
|
||||
checkTier(tier: number): boolean {
|
||||
const { tiers } = this.settings;
|
||||
const settings = this.settings;
|
||||
if (!settings || !settings.tiers) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 宣言された階層中にパラメータの内容が含まれていればtrue
|
||||
return tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
|
||||
return settings.tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ export const getPrivateKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PRIVATE_KEY')?.replace(/\\n/g, '\n') ?? ''
|
||||
configService.getOrThrow<string>('JWT_PRIVATE_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
|
||||
@ -140,6 +140,6 @@ export const getPublicKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n') ?? ''
|
||||
configService.getOrThrow<string>('JWT_PUBLIC_KEY').replace(/\\n/g, '\n')
|
||||
);
|
||||
};
|
||||
|
||||
@ -15,10 +15,9 @@ export const makePassword = (): string => {
|
||||
|
||||
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
|
||||
let valid = false;
|
||||
let autoGeneratedPassword: string;
|
||||
let autoGeneratedPassword: string = '';
|
||||
|
||||
while (!valid) {
|
||||
autoGeneratedPassword = '';
|
||||
// パスワードをランダムに決定
|
||||
while (autoGeneratedPassword.length < passLength) {
|
||||
// 上で決定したcharsの中からランダムに1文字ずつ追加
|
||||
|
||||
@ -3,6 +3,7 @@ import { DataSource } from 'typeorm';
|
||||
import { User, UserArchive } from '../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../repositories/accounts/entity/account.entity';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { License } from '../../repositories/licenses/entity/license.entity';
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
@ -57,11 +58,11 @@ export const makeHierarchicalAccounts = async (
|
||||
}
|
||||
// 第2階層を作成
|
||||
{
|
||||
const { account: tier1 } = state.tier1Accounts.slice().shift();
|
||||
const tier1 = state.tier1Accounts.slice().shift();
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
parent_account_id: tier1?.account.id,
|
||||
company_name: 'OMDS_US',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
@ -72,7 +73,7 @@ export const makeHierarchicalAccounts = async (
|
||||
{
|
||||
const { account, admin } = await makeTestAccount(datasource, {
|
||||
tier: 2,
|
||||
parent_account_id: tier1.id,
|
||||
parent_account_id: tier1?.account.id,
|
||||
company_name: 'OMDS_EU',
|
||||
});
|
||||
state.tier2Accounts.push({
|
||||
@ -201,7 +202,7 @@ export const makeTestAccount = async (
|
||||
}
|
||||
|
||||
// Accountの管理者を設定する
|
||||
let secondaryAdminUserId = null;
|
||||
let secondaryAdminUserId: number | null = null;
|
||||
if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
|
||||
secondaryAdminUserId = userId;
|
||||
}
|
||||
@ -224,6 +225,9 @@ export const makeTestAccount = async (
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
if (!account || !admin) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
|
||||
return {
|
||||
account: account,
|
||||
@ -263,7 +267,9 @@ export const makeTestSimpleAccount = async (
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
return account;
|
||||
};
|
||||
|
||||
@ -299,11 +305,15 @@ export const makeTestUser = async (
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
return await datasource.getRepository(User).findOne({
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error('Unexpected null');
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -312,7 +322,10 @@ export const makeTestUser = async (
|
||||
* @param id アカウントID
|
||||
* @returns 該当アカウント
|
||||
*/
|
||||
export const getAccount = async (dataSource: DataSource, id: number) => {
|
||||
export const getAccount = async (
|
||||
dataSource: DataSource,
|
||||
id: number,
|
||||
): Promise<Account | null> => {
|
||||
return await dataSource.getRepository(Account).findOne({
|
||||
where: { id: id },
|
||||
});
|
||||
@ -353,7 +366,7 @@ export const getUserFromExternalId = async (
|
||||
export const getUser = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<User> => {
|
||||
): Promise<User | null> => {
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
@ -381,3 +394,14 @@ export const getUserArchive = async (
|
||||
): Promise<UserArchive[]> => {
|
||||
return await dataSource.getRepository(UserArchive).find();
|
||||
};
|
||||
export const getLicenses = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
): Promise<License[]> => {
|
||||
const licenses = await datasource.getRepository(License).find({
|
||||
where: {
|
||||
account_id: account_id,
|
||||
},
|
||||
});
|
||||
return licenses;
|
||||
};
|
||||
|
||||
@ -244,7 +244,7 @@ export const OPTION_ITEM_VALUE_TYPE = {
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADB2C_SIGN_IN_TYPE = {
|
||||
EAMILADDRESS: 'emailAddress',
|
||||
EMAILADDRESS: 'emailAddress',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@ -66,6 +66,8 @@ import {
|
||||
GetAuthorsResponse,
|
||||
GetAccountInfoMinimalAccessRequest,
|
||||
GetAccountInfoMinimalAccessResponse,
|
||||
DeleteWorktypeRequestParam,
|
||||
DeleteWorktypeResponse,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -198,14 +200,26 @@ export class AccountsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('me')
|
||||
async getMyAccount(@Req() req: Request): Promise<GetMyAccountResponse> {
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const context = makeContext(payload.userId);
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
//アカウントID取得処理
|
||||
const accountInfo = await this.accountService.getAccountInfo(
|
||||
context,
|
||||
payload.userId,
|
||||
userId,
|
||||
);
|
||||
return accountInfo;
|
||||
}
|
||||
@ -235,8 +249,21 @@ export class AccountsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get('authors')
|
||||
async getAuthors(@Req() req: Request): Promise<GetAuthorsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
const authors = await this.accountService.getAuthors(context, userId);
|
||||
@ -268,10 +295,23 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typists')
|
||||
async getTypists(@Req() req: Request): Promise<GetTypistsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const typists = await this.accountService.getTypists(payload.userId);
|
||||
const typists = await this.accountService.getTypists(userId);
|
||||
|
||||
return { typists };
|
||||
}
|
||||
@ -300,12 +340,23 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('typist-groups')
|
||||
async getTypistGroups(@Req() req: Request): Promise<GetTypistGroupsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const typistGroups = await this.accountService.getTypistGroups(
|
||||
payload.userId,
|
||||
);
|
||||
const typistGroups = await this.accountService.getTypistGroups(userId);
|
||||
|
||||
return { typistGroups };
|
||||
}
|
||||
@ -346,8 +397,22 @@ export class AccountsController {
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -395,8 +460,22 @@ export class AccountsController {
|
||||
): Promise<CreateTypistGroupResponse> {
|
||||
const { typistGroupName, typistIds } = body;
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.createTypistGroup(
|
||||
context,
|
||||
@ -445,8 +524,22 @@ export class AccountsController {
|
||||
const { typistGroupId } = param;
|
||||
|
||||
// アクセストークン取得
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -496,10 +589,24 @@ export class AccountsController {
|
||||
@Body() body: CreatePartnerAccountRequest,
|
||||
): Promise<CreatePartnerAccountResponse> {
|
||||
const { companyName, country, email, adminName } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.createPartnerAccount(
|
||||
context,
|
||||
@ -507,8 +614,8 @@ export class AccountsController {
|
||||
country,
|
||||
email,
|
||||
adminName,
|
||||
payload.userId,
|
||||
payload.tier,
|
||||
userId,
|
||||
tier,
|
||||
);
|
||||
|
||||
return {};
|
||||
@ -624,15 +731,28 @@ export class AccountsController {
|
||||
): Promise<IssueLicenseResponse> {
|
||||
const { orderedAccountId, poNumber } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.issueLicense(
|
||||
context,
|
||||
orderedAccountId,
|
||||
accessToken.userId,
|
||||
accessToken.tier,
|
||||
userId,
|
||||
tier,
|
||||
poNumber,
|
||||
);
|
||||
return {};
|
||||
@ -692,14 +812,27 @@ export class AccountsController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelIssueRequest,
|
||||
): Promise<CancelIssueResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.cancelIssue(
|
||||
context,
|
||||
payload.userId,
|
||||
userId,
|
||||
body.poNumber,
|
||||
body.orderedAccountId,
|
||||
);
|
||||
@ -727,8 +860,21 @@ export class AccountsController {
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async getWorktypes(@Req() req: Request): Promise<GetWorktypesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const worktypes = await this.accountService.getWorktypes(context, userId);
|
||||
@ -766,8 +912,22 @@ export class AccountsController {
|
||||
@Body() body: CreateWorktypesRequest,
|
||||
): Promise<CreateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
await this.accountService.createWorktype(
|
||||
@ -812,8 +972,22 @@ export class AccountsController {
|
||||
): Promise<UpdateWorktypeResponse> {
|
||||
const { worktypeId, description } = body;
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -828,6 +1002,59 @@ export class AccountsController {
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/worktypes/:id/delete')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: DeleteWorktypeResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '指定WorktypeIDが削除済み / 指定WorktypeIDがWorkflowで使用中',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({ operationId: 'deleteWorktype' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
async deleteWorktype(
|
||||
@Req() req: Request,
|
||||
@Param() param: DeleteWorktypeRequestParam,
|
||||
): Promise<DeleteWorktypeResponse> {
|
||||
const { id } = param;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.deleteWorktype(context, userId, id);
|
||||
return {};
|
||||
}
|
||||
|
||||
@Get('/worktypes/:id/option-items')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
@ -858,8 +1085,22 @@ export class AccountsController {
|
||||
@Param() param: GetOptionItemsRequestParam,
|
||||
): Promise<GetOptionItemsResponse> {
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -904,8 +1145,22 @@ export class AccountsController {
|
||||
): Promise<UpdateOptionItemsResponse> {
|
||||
const { optionItems } = body;
|
||||
const { id } = param;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -949,8 +1204,22 @@ export class AccountsController {
|
||||
@Body() body: PostActiveWorktypeRequest,
|
||||
): Promise<PostActiveWorktypeResponse> {
|
||||
const { id } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -993,8 +1262,22 @@ export class AccountsController {
|
||||
@Query() query: GetPartnersRequest,
|
||||
): Promise<GetPartnersResponse> {
|
||||
const { limit, offset } = query;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
const response = await this.accountService.getPartners(
|
||||
@ -1046,8 +1329,22 @@ export class AccountsController {
|
||||
primaryAdminUserId,
|
||||
secondryAdminUserId,
|
||||
} = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId, tier } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, tier } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.updateAccountInfo(
|
||||
@ -1060,7 +1357,7 @@ export class AccountsController {
|
||||
secondryAdminUserId,
|
||||
);
|
||||
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/delete')
|
||||
@ -1092,12 +1389,26 @@ export class AccountsController {
|
||||
@Body() body: DeleteAccountRequest,
|
||||
): Promise<DeleteAccountResponse> {
|
||||
const { accountId } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.accountService.deleteAccountAndData(context, userId, accountId);
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('/minimal-access')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,6 +33,7 @@ import {
|
||||
GetPartnersResponse,
|
||||
PostWorktypeOptionItem,
|
||||
Author,
|
||||
Partner,
|
||||
} from './types/types';
|
||||
import {
|
||||
DateWithZeroTime,
|
||||
@ -65,12 +66,15 @@ import {
|
||||
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
|
||||
import {
|
||||
WorktypeIdAlreadyExistsError,
|
||||
WorktypeIdInUseError,
|
||||
WorktypeIdMaxCountError,
|
||||
WorktypeIdNotFoundError,
|
||||
} from '../../repositories/worktypes/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsService {
|
||||
private readonly mailFrom =
|
||||
this.configService.getOrThrow<string>('MAIL_FROM');
|
||||
constructor(
|
||||
private readonly accountRepository: AccountsRepositoryService,
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
@ -256,9 +260,6 @@ export class AccountsService {
|
||||
}
|
||||
|
||||
try {
|
||||
// メールの送信元を取得
|
||||
const from = this.configService.get<string>('MAIL_FROM') ?? '';
|
||||
|
||||
// メールの内容を構成
|
||||
const { subject, text, html } =
|
||||
await this.sendgridService.createMailContentFromEmailConfirm(
|
||||
@ -272,7 +273,7 @@ export class AccountsService {
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -393,7 +394,7 @@ export class AccountsService {
|
||||
userInfo.account_id,
|
||||
);
|
||||
|
||||
let parentInfo: Account;
|
||||
let parentInfo: Account | undefined;
|
||||
if (accountInfo.parent_account_id) {
|
||||
parentInfo = await this.accountRepository.findAccountById(
|
||||
accountInfo.parent_account_id,
|
||||
@ -480,14 +481,20 @@ export class AccountsService {
|
||||
const { account_id } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
const userGroup = await this.userGroupsRepository.getTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
);
|
||||
const { name, userGroupMembers } =
|
||||
await this.userGroupsRepository.getTypistGroup(
|
||||
account_id,
|
||||
typistGroupId,
|
||||
);
|
||||
if (!userGroupMembers) {
|
||||
throw new TypistGroupNotExistError(
|
||||
`Typist Group is not exist. typistGroupId: ${typistGroupId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
typistGroupName: userGroup.name,
|
||||
typistIds: userGroup.userGroupMembers.map((x) => x.user_id),
|
||||
typistGroupName: name,
|
||||
typistIds: userGroupMembers.map((x) => x.user_id),
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
@ -540,6 +547,11 @@ export class AccountsService {
|
||||
|
||||
const typists = typistUsers.map((x) => {
|
||||
const user = adb2cUsers.find((adb2c) => adb2c.id === x.external_id);
|
||||
if (!user) {
|
||||
throw new Error(
|
||||
`user not found. externalId: ${x.external_id}, userId: ${x.id}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
id: x.id,
|
||||
name: user.displayName,
|
||||
@ -585,6 +597,11 @@ export class AccountsService {
|
||||
);
|
||||
|
||||
const authors = authorUsers.map((x) => {
|
||||
if (!x.author_id) {
|
||||
throw new Error(
|
||||
`author_id is Not Found. externalId: ${x.external_id}, userId: ${x.id}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
id: x.id,
|
||||
authorId: x.author_id,
|
||||
@ -700,8 +717,8 @@ export class AccountsService {
|
||||
creatorAccountTier + 1,
|
||||
externalUser.sub,
|
||||
USER_ROLES.NONE,
|
||||
null,
|
||||
null,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
account = newAccount;
|
||||
user = adminUser;
|
||||
@ -742,7 +759,6 @@ export class AccountsService {
|
||||
}
|
||||
|
||||
try {
|
||||
const from = this.configService.get<string>('MAIL_FROM') || '';
|
||||
const { subject, text, html } =
|
||||
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
|
||||
account.id,
|
||||
@ -752,7 +768,7 @@ export class AccountsService {
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -835,11 +851,19 @@ export class AccountsService {
|
||||
// 各子アカウントのShortageを算出してreturn用の変数にマージする
|
||||
const childrenPartnerLicenses: PartnerLicenseInfo[] = [];
|
||||
for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) {
|
||||
let childShortage;
|
||||
const { allocatableLicenseWithMargin, expiringSoonLicense } =
|
||||
childPartnerLicenseFromRepository;
|
||||
let childShortage: number = 0;
|
||||
if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) {
|
||||
childShortage =
|
||||
childPartnerLicenseFromRepository.allocatableLicenseWithMargin -
|
||||
childPartnerLicenseFromRepository.expiringSoonLicense;
|
||||
if (
|
||||
allocatableLicenseWithMargin === undefined ||
|
||||
expiringSoonLicense === undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`Tier5 account has no allocatableLicenseWithMargin or expiringSoonLicense. accountId: ${accountId}`,
|
||||
);
|
||||
}
|
||||
childShortage = allocatableLicenseWithMargin - expiringSoonLicense;
|
||||
} else {
|
||||
childShortage =
|
||||
childPartnerLicenseFromRepository.stockLicense -
|
||||
@ -906,13 +930,13 @@ export class AccountsService {
|
||||
licenseOrder.issued_at !== null
|
||||
? new Date(licenseOrder.issued_at)
|
||||
.toISOString()
|
||||
.substr(0, 10)
|
||||
.substring(0, 10)
|
||||
.replace(/-/g, '/')
|
||||
: null,
|
||||
: undefined,
|
||||
numberOfOrder: licenseOrder.quantity,
|
||||
orderDate: new Date(licenseOrder.ordered_at)
|
||||
.toISOString()
|
||||
.substr(0, 10)
|
||||
.substring(0, 10)
|
||||
.replace(/-/g, '/'),
|
||||
poNumber: licenseOrder.po_number,
|
||||
status: licenseOrder.status,
|
||||
@ -1417,6 +1441,81 @@ export class AccountsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプを削除します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @param id
|
||||
* @returns worktype
|
||||
*/
|
||||
async deleteWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.deleteWorktype.name} | params: { ` +
|
||||
`externalId: ${externalId}, ` +
|
||||
`id: ${id} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
// 外部IDをもとにユーザー情報を取得する
|
||||
const { account, account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`account not found. externalId: ${externalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ワークタイプを削除する
|
||||
await this.worktypesRepository.deleteWorktype(accountId, id);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case UserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AccountNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010501'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
|
||||
case WorktypeIdNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011003'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
// 内部IDで指定されたWorktypeがWorkflowで使用中の場合は400エラーを返す
|
||||
case WorktypeIdInUseError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E011004'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
default:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.deleteWorktype.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ワークタイプに紐づいたオプションアイテム一覧を取得します
|
||||
* @param context
|
||||
@ -1558,7 +1657,7 @@ export class AccountsService {
|
||||
async updateActiveWorktype(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
id: number,
|
||||
id: number | undefined,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` +
|
||||
@ -1625,34 +1724,40 @@ export class AccountsService {
|
||||
const { account_id: accountId } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
const partners = await this.accountRepository.getPartners(
|
||||
const partnersRecords = await this.accountRepository.getPartners(
|
||||
accountId,
|
||||
limit,
|
||||
offset,
|
||||
);
|
||||
|
||||
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
|
||||
let externalIds = partners.partnersInfo.map(
|
||||
let externalIds = partnersRecords.partnersInfo.map(
|
||||
(x) => x.primaryAccountExternalId,
|
||||
);
|
||||
externalIds = externalIds.filter((item) => item !== undefined);
|
||||
const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
|
||||
|
||||
// DBから取得した情報とADB2Cから取得した情報をマージ
|
||||
const response = partners.partnersInfo.map((db) => {
|
||||
const partners = partnersRecords.partnersInfo.map((db): Partner => {
|
||||
const adb2cUser = adb2cUsers.find(
|
||||
(adb2c) => db.primaryAccountExternalId === adb2c.id,
|
||||
);
|
||||
|
||||
let primaryAdmin = undefined;
|
||||
let mail = undefined;
|
||||
if (adb2cUser) {
|
||||
primaryAdmin = adb2cUser.displayName;
|
||||
mail = adb2cUser.identities.find(
|
||||
(identity) =>
|
||||
identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
).issuerAssignedId;
|
||||
if (!adb2cUser) {
|
||||
throw new Error(
|
||||
`adb2c user not found. externalId: ${db.primaryAccountExternalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const primaryAdmin = adb2cUser.displayName;
|
||||
const mail = adb2cUser.identities?.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
)?.issuerAssignedId;
|
||||
if (!mail) {
|
||||
throw new Error(
|
||||
`adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
name: db.name,
|
||||
tier: db.tier,
|
||||
@ -1665,17 +1770,15 @@ export class AccountsService {
|
||||
});
|
||||
|
||||
return {
|
||||
total: partners.total,
|
||||
partners: response,
|
||||
total: partnersRecords.total,
|
||||
partners: partners,
|
||||
};
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`);
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export type LicensesRepositoryMockValue = {
|
||||
orderHistories: LicenseOrder[];
|
||||
}
|
||||
| Error;
|
||||
issueLicense: undefined | Error;
|
||||
issueLicense: void | Error;
|
||||
};
|
||||
export type UsersRepositoryMockValue = {
|
||||
findUserById: User | Error;
|
||||
@ -61,10 +61,12 @@ export type ConfigMockValue = {
|
||||
get: string | Error;
|
||||
};
|
||||
export type AccountsRepositoryMockValue = {
|
||||
getLicenseSummaryInfo: {
|
||||
licenseSummary: LicenseSummaryInfo;
|
||||
isStorageAvailable: boolean;
|
||||
};
|
||||
getLicenseSummaryInfo:
|
||||
| {
|
||||
licenseSummary: LicenseSummaryInfo;
|
||||
isStorageAvailable: boolean;
|
||||
}
|
||||
| Error;
|
||||
createAccount: { newAccount: Account; adminUser: User } | Error;
|
||||
};
|
||||
|
||||
@ -181,18 +183,7 @@ export const makeLicensesRepositoryMock = (
|
||||
issueLicense:
|
||||
issueLicense instanceof Error
|
||||
? jest.fn<Promise<void>, []>().mockRejectedValue(issueLicense)
|
||||
: jest
|
||||
.fn<
|
||||
Promise<{
|
||||
context: Context;
|
||||
orderedAccountId: number;
|
||||
myAccountId: number;
|
||||
tier: number;
|
||||
poNumber: string;
|
||||
}>,
|
||||
[]
|
||||
>()
|
||||
.mockResolvedValue(issueLicense),
|
||||
: jest.fn<Promise<void>, []>().mockResolvedValue(issueLicense),
|
||||
};
|
||||
};
|
||||
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
|
||||
@ -355,7 +346,7 @@ export const makeDefaultAccountsRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
return {
|
||||
getLicenseSummaryInfo: {
|
||||
licenseSummary: licenseSummaryInfo,
|
||||
@ -385,7 +376,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
|
||||
const typists: User[] = [];
|
||||
typists.push(
|
||||
@ -434,7 +425,7 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = null;
|
||||
user.updated_at = null;
|
||||
user.updated_at = new Date();
|
||||
|
||||
return {
|
||||
getUserGroups: [
|
||||
@ -444,6 +435,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
name: 'GroupA',
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -451,6 +446,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
name: 'GroupB',
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -23,14 +23,14 @@ export const getSortCriteria = async (dataSource: DataSource) => {
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -54,7 +54,7 @@ export const createLicense = async (
|
||||
export const createLicenseSetExpiryDateAndStatus = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
expiryDate: Date,
|
||||
expiryDate: Date | null,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
@ -171,19 +171,21 @@ export const createOptionItems = async (
|
||||
datasource: DataSource,
|
||||
worktypeId: number,
|
||||
): Promise<OptionItem[]> => {
|
||||
const optionItems = [];
|
||||
const optionItems: OptionItem[] = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
optionItems.push({
|
||||
worktype_id: worktypeId,
|
||||
item_label: '',
|
||||
default_value_type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
|
||||
initial_value: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const optionItem = new OptionItem();
|
||||
{
|
||||
optionItem.worktype_id = worktypeId;
|
||||
optionItem.item_label = '';
|
||||
optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT;
|
||||
optionItem.initial_value = '';
|
||||
optionItem.created_by = 'test_runner';
|
||||
optionItem.created_at = new Date();
|
||||
optionItem.updated_by = 'updater';
|
||||
optionItem.updated_at = new Date();
|
||||
}
|
||||
optionItems.push(optionItem);
|
||||
}
|
||||
|
||||
await datasource.getRepository(OptionItem).insert(optionItems);
|
||||
|
||||
@ -327,8 +327,8 @@ export class GetOrderHistoriesRequest {
|
||||
export class LicenseOrder {
|
||||
@ApiProperty({ description: '注文日付' })
|
||||
orderDate: string;
|
||||
@ApiProperty({ description: '発行日付' })
|
||||
issueDate: string;
|
||||
@ApiProperty({ description: '発行日付', required: false })
|
||||
issueDate?: string;
|
||||
@ApiProperty({ description: '注文数' })
|
||||
numberOfOrder: number;
|
||||
@ApiProperty({ description: 'POナンバー' })
|
||||
@ -497,6 +497,16 @@ export class UpdateWorktypeRequestParam {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class DeleteWorktypeRequestParam {
|
||||
@ApiProperty({ description: 'Worktypeの内部ID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class DeleteWorktypeResponse {}
|
||||
|
||||
export class PostActiveWorktypeRequest {
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
|
||||
@ -5,12 +5,19 @@ import {
|
||||
makeAdB2cServiceMock,
|
||||
makeDefaultAdB2cMockValue,
|
||||
} from './test/auth.service.mock';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
describe('AuthController', () => {
|
||||
let controller: AuthController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
|
||||
@ -3,6 +3,7 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import {
|
||||
makeAuthServiceMock,
|
||||
makeDefaultAdB2cMockValue,
|
||||
makeDefaultConfigValue,
|
||||
makeDefaultGetPublicKeyFromJwk,
|
||||
} from './test/auth.service.mock';
|
||||
import { DataSource } from 'typeorm';
|
||||
@ -16,7 +17,8 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
describe('AuthService', () => {
|
||||
it('IDトークンの検証とペイロードの取得に成功する', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -27,7 +29,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token = 'invalid.id.token';
|
||||
|
||||
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
|
||||
@ -37,7 +40,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -50,7 +54,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -63,7 +68,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
|
||||
|
||||
@ -74,7 +80,8 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
@ -87,8 +94,9 @@ describe('AuthService', () => {
|
||||
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(メタデータ)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getMetaData = new Error('failed get metadata');
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -101,8 +109,9 @@ describe('AuthService', () => {
|
||||
});
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(キーセット)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getSignKeySets = new Error('failed get keyset');
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -116,10 +125,11 @@ describe('AuthService', () => {
|
||||
|
||||
it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const configMockValue = makeDefaultConfigValue();
|
||||
adb2cParam.getSignKeySets = [
|
||||
{ kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
|
||||
];
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -133,7 +143,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
describe('checkIsAcceptedLatestVersion', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -146,11 +156,14 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
@ -171,7 +184,9 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
|
||||
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,
|
||||
@ -192,7 +207,9 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
@ -213,7 +230,9 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
|
||||
it('同意済み利用規約(EULA)バージョンが最新でないときにチェックが通らないこと(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
@ -234,7 +253,9 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョン(DPA)が最新でないときにチェックが通らないこと(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<AuthService>(AuthService);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
|
||||
@ -25,6 +25,13 @@ import { Context } from '../../common/log';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly refreshTokenLifetimeWeb =
|
||||
this.configService.getOrThrow<number>('REFRESH_TOKEN_LIFETIME_WEB');
|
||||
private readonly refreshTokenLifetimeDefault =
|
||||
this.configService.getOrThrow<number>('REFRESH_TOKEN_LIFETIME_DEFAULT');
|
||||
private readonly accessTokenlifetime = this.configService.getOrThrow<number>(
|
||||
'ACCESS_TOKEN_LIFETIME_WEB',
|
||||
);
|
||||
constructor(
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly configService: ConfigService,
|
||||
@ -68,10 +75,7 @@ export class AuthService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
const lifetimeWeb = this.configService.get('REFRESH_TOKEN_LIFETIME_WEB');
|
||||
const lifetimeDefault = this.configService.get(
|
||||
'REFRESH_TOKEN_LIFETIME_DEFAULT',
|
||||
);
|
||||
|
||||
let user: User;
|
||||
// ユーザー情報とユーザーが属しているアカウント情報を取得
|
||||
try {
|
||||
@ -106,7 +110,10 @@ export class AuthService {
|
||||
);
|
||||
}
|
||||
// 要求された環境用トークンの寿命を決定
|
||||
const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault;
|
||||
const refreshTokenLifetime =
|
||||
type === 'web'
|
||||
? this.refreshTokenLifetimeWeb
|
||||
: this.refreshTokenLifetimeDefault;
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
// ユーザーのロールを設定
|
||||
@ -165,7 +172,6 @@ export class AuthService {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateAccessToken.name}`,
|
||||
);
|
||||
const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB');
|
||||
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
@ -188,7 +194,7 @@ export class AuthService {
|
||||
tier: token.tier,
|
||||
userId: token.userId,
|
||||
},
|
||||
lifetime,
|
||||
this.accessTokenlifetime,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
@ -205,11 +211,14 @@ export class AuthService {
|
||||
async getVerifiedIdToken(token: string): Promise<IDToken> {
|
||||
this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`);
|
||||
|
||||
let kid = '';
|
||||
let kid: string | undefined = '';
|
||||
try {
|
||||
// JWTトークンのヘッダを見るため一度デコードする
|
||||
const decodedToken = jwt.decode(token, { complete: true });
|
||||
kid = decodedToken.header.kid;
|
||||
kid = decodedToken?.header.kid;
|
||||
if (!kid) {
|
||||
throw new Error('kid not found');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw new HttpException(
|
||||
@ -343,7 +352,7 @@ export class AuthService {
|
||||
): Promise<boolean> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` +
|
||||
`idToken: ${idToken}, };`,
|
||||
`idToken.sub: ${idToken.sub}, };`,
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@ -9,9 +9,13 @@ export type AdB2cMockValue = {
|
||||
getMetaData: B2cMetadata | Error;
|
||||
getSignKeySets: JwkSignKey[] | Error;
|
||||
};
|
||||
export type ConfigMockValue = {
|
||||
getOrThrow: number;
|
||||
};
|
||||
|
||||
export const makeAuthServiceMock = async (
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
configMockValue: ConfigMockValue,
|
||||
): Promise<AuthService> => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
@ -21,7 +25,7 @@ export const makeAuthServiceMock = async (
|
||||
case AdB2cService:
|
||||
return makeAdB2cServiceMock(adB2cMockValue);
|
||||
case ConfigService:
|
||||
return {};
|
||||
return makeConfigMock(configMockValue);
|
||||
case UsersRepositoryService:
|
||||
return {};
|
||||
}
|
||||
@ -80,3 +84,16 @@ export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => {
|
||||
'-----END PUBLIC KEY-----',
|
||||
].join('\n');
|
||||
};
|
||||
export const makeConfigMock = (value: ConfigMockValue) => {
|
||||
const { getOrThrow } = value;
|
||||
|
||||
return {
|
||||
getOrThrow: jest.fn<Promise<number>, []>().mockResolvedValue(getOrThrow),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultConfigValue = (): ConfigMockValue => {
|
||||
return {
|
||||
getOrThrow: 80000,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Query,
|
||||
@ -37,6 +38,7 @@ import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { Request } from 'express';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('files')
|
||||
@Controller('files')
|
||||
@ -75,10 +77,23 @@ export class FilesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: AudioUploadFinishedRequest,
|
||||
): Promise<AudioUploadFinishedResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const {
|
||||
url,
|
||||
@ -99,7 +114,7 @@ export class FilesController {
|
||||
|
||||
const res = await this.filesService.uploadFinished(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
url,
|
||||
authorId,
|
||||
fileName,
|
||||
@ -149,10 +164,23 @@ export class FilesController {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Query() _query: AudioUploadLocationRequest,
|
||||
): Promise<AudioUploadLocationResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishUploadSas(context, accessToken);
|
||||
return { url };
|
||||
@ -195,14 +223,27 @@ export class FilesController {
|
||||
): Promise<AudioDownloadLocationResponse> {
|
||||
const { audioFileId } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishAudioFileDownloadSas(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
audioFileId,
|
||||
);
|
||||
|
||||
@ -246,14 +287,27 @@ export class FilesController {
|
||||
): Promise<TemplateDownloadLocationResponse> {
|
||||
const { audioFileId } = body;
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const accessToken = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(accessToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const url = await this.filesService.publishTemplateFileDownloadSas(
|
||||
context,
|
||||
accessToken.userId,
|
||||
userId,
|
||||
audioFileId,
|
||||
);
|
||||
|
||||
@ -287,8 +341,21 @@ export class FilesController {
|
||||
async uploadTemplateLocation(
|
||||
@Req() req: Request,
|
||||
): Promise<TemplateUploadLocationResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -333,8 +400,21 @@ export class FilesController {
|
||||
@Body() body: TemplateUploadFinishedRequest,
|
||||
): Promise<TemplateUploadFinishedReqponse> {
|
||||
const { name, url } = body;
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
await this.filesService.templateUploadFinished(context, userId, url, name);
|
||||
|
||||
@ -35,11 +35,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
await service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
|
||||
@ -57,11 +56,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
await service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
|
||||
@ -78,11 +76,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
);
|
||||
@ -102,11 +99,10 @@ describe('音声ファイルアップロードURL取得', () => {
|
||||
blobParam.publishUploadSas = new Error('Azure service down');
|
||||
|
||||
await expect(
|
||||
service.publishUploadSas(makeContext('trackingId'), {
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
service.publishUploadSas(
|
||||
makeContext('trackingId'),
|
||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
);
|
||||
@ -295,7 +291,7 @@ describe('タスク作成', () => {
|
||||
});
|
||||
|
||||
describe('音声ファイルダウンロードURL取得', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -308,11 +304,13 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
@ -333,7 +331,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -341,6 +339,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
@ -353,6 +352,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -382,6 +382,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -396,6 +397,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -429,6 +431,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -443,6 +446,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -467,6 +471,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -481,6 +486,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -492,6 +498,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -506,6 +513,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const {
|
||||
external_id: externalId,
|
||||
@ -526,7 +534,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -534,6 +542,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -549,7 +558,7 @@ describe('音声ファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -562,11 +571,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
@ -586,7 +597,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -594,6 +605,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
expect(
|
||||
@ -606,6 +618,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -629,6 +642,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -643,6 +657,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -672,6 +687,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -686,6 +702,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -710,6 +727,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = true;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -724,6 +742,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('Taskが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -735,6 +754,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -749,6 +769,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId, author_id: authorId } = await makeTestUser(
|
||||
source,
|
||||
@ -768,7 +789,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
'test.zip',
|
||||
'InProgress',
|
||||
undefined,
|
||||
authorId,
|
||||
authorId ?? '',
|
||||
);
|
||||
|
||||
const blobParam = makeBlobstorageServiceMockValue();
|
||||
@ -776,6 +797,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
blobParam.fileExists = false;
|
||||
|
||||
const module = await makeTestingModuleWithBlob(source, blobParam);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
|
||||
await expect(
|
||||
@ -791,7 +813,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
||||
});
|
||||
|
||||
describe('publishTemplateFileUploadSas', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -804,12 +826,15 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -832,7 +857,9 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -858,7 +885,9 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -887,7 +916,7 @@ describe('publishTemplateFileUploadSas', () => {
|
||||
});
|
||||
|
||||
describe('templateUploadFinished', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -900,12 +929,15 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('アップロード完了後のテンプレートファイル情報をDBに保存できる(新規追加)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -937,7 +969,9 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
it('アップロード完了後のテンプレートファイル情報をDBに保存できる(更新)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
@ -975,7 +1009,9 @@ describe('templateUploadFinished', () => {
|
||||
});
|
||||
|
||||
it('DBへの保存に失敗した場合はエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<FilesService>(FilesService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
} from '../../repositories/tasks/errors/types';
|
||||
import { Context } from '../../common/log';
|
||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
@ -206,7 +207,7 @@ export class FilesService {
|
||||
*/
|
||||
async publishUploadSas(
|
||||
context: Context,
|
||||
token: AccessToken,
|
||||
externalId: string,
|
||||
): Promise<string> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
|
||||
@ -216,10 +217,11 @@ export class FilesService {
|
||||
let accountId: number;
|
||||
let country: string;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(
|
||||
token.userId,
|
||||
);
|
||||
accountId = user.account.id;
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account_id;
|
||||
country = user.account.country;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
@ -291,14 +293,17 @@ export class FilesService {
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
let authorId: string | undefined;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account.id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
authorId = user.author_id ?? undefined;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
|
||||
@ -321,7 +326,7 @@ export class FilesService {
|
||||
accountId,
|
||||
status,
|
||||
);
|
||||
const file = task.file;
|
||||
const { file } = task;
|
||||
|
||||
// タスクに紐づく音声ファイルだけが消される場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
@ -332,9 +337,9 @@ export class FilesService {
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
if (!isTypist && file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -425,14 +430,17 @@ export class FilesService {
|
||||
let userId: number;
|
||||
let country: string;
|
||||
let isTypist: boolean;
|
||||
let authorId: string;
|
||||
let authorId: string | undefined;
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
accountId = user.account.id;
|
||||
if (!user.account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
accountId = user.account_id;
|
||||
userId = user.id;
|
||||
country = user.account.country;
|
||||
isTypist = user.role === USER_ROLES.TYPIST;
|
||||
authorId = user.author_id;
|
||||
authorId = user.author_id ?? undefined;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.log(
|
||||
@ -454,6 +462,15 @@ export class FilesService {
|
||||
accountId,
|
||||
status,
|
||||
);
|
||||
const { file } = task;
|
||||
|
||||
// タスクに紐づく音声ファイルだけが消される場合がある。
|
||||
// その場合はダウンロード不可なので不在エラーとして扱う
|
||||
if (!file) {
|
||||
throw new AudioFileNotFoundError(
|
||||
`Audio file is not exists in DB. audio_file_id:${audioFileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const template_file = task.template_file;
|
||||
|
||||
@ -466,9 +483,9 @@ export class FilesService {
|
||||
}
|
||||
|
||||
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
|
||||
if (!isTypist && task.file.author_id !== authorId) {
|
||||
if (!isTypist && file.author_id !== authorId) {
|
||||
throw new AuthorUserNotMatchError(
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${task.file.author_id}, authorId:${authorId}`,
|
||||
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -515,6 +532,7 @@ export class FilesService {
|
||||
makeErrorResponse('E010603'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AudioFileNotFoundError:
|
||||
case TemplateFileNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010701'),
|
||||
@ -552,15 +570,18 @@ export class FilesService {
|
||||
`[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`,
|
||||
);
|
||||
try {
|
||||
const {
|
||||
account: { id: accountId, country },
|
||||
} = await this.usersRepository.findUserByExternalId(externalId);
|
||||
const { account } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError('account not found.');
|
||||
}
|
||||
|
||||
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
|
||||
const isContainerExists = await this.blobStorageService.containerExists(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
account.id,
|
||||
account.country,
|
||||
);
|
||||
if (!isContainerExists) {
|
||||
throw new Error('container not found.');
|
||||
@ -569,8 +590,8 @@ export class FilesService {
|
||||
// SASトークン発行
|
||||
const url = await this.blobStorageService.publishTemplateUploadSas(
|
||||
context,
|
||||
accountId,
|
||||
country,
|
||||
account.id,
|
||||
account.country,
|
||||
);
|
||||
|
||||
return url;
|
||||
|
||||
@ -134,12 +134,15 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
created_by: 'test',
|
||||
created_at: new Date(),
|
||||
updated_by: null,
|
||||
updated_at: null,
|
||||
updated_at: new Date(),
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
account: {
|
||||
id: 2,
|
||||
parent_account_id: 2,
|
||||
@ -154,7 +157,10 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
created_by: '',
|
||||
created_at: new Date(),
|
||||
updated_by: '',
|
||||
updated_at: null,
|
||||
updated_at: new Date(),
|
||||
active_worktype_id: null,
|
||||
secondary_admin_user_id: null,
|
||||
user: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -172,6 +178,14 @@ export const makeDefaultTasksRepositoryMockValue =
|
||||
status: 'Uploaded',
|
||||
priority: '01',
|
||||
created_at: new Date(),
|
||||
finished_at: null,
|
||||
started_at: null,
|
||||
template_file_id: null,
|
||||
typist_user_id: null,
|
||||
file: null,
|
||||
option_items: null,
|
||||
template_file: null,
|
||||
typist_user: null,
|
||||
},
|
||||
getTasksFromAccountId: {
|
||||
tasks: [],
|
||||
|
||||
@ -98,7 +98,7 @@ export const createTask = async (
|
||||
export const makeTestingModuleWithBlob = async (
|
||||
datasource: DataSource,
|
||||
blobStorageService: BlobstorageServiceMockValue,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
@ -34,6 +35,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('licenses')
|
||||
@Controller('licenses')
|
||||
@ -73,12 +75,25 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CreateOrdersRequest,
|
||||
): Promise<CreateOrdersResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
// ライセンス注文処理
|
||||
await this.licensesService.licenseOrders(
|
||||
payload,
|
||||
userId,
|
||||
body.poNumber,
|
||||
body.orderCount,
|
||||
);
|
||||
@ -111,11 +126,24 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: IssueCardLicensesRequest,
|
||||
): Promise<IssueCardLicensesResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
|
||||
payload.userId,
|
||||
userId,
|
||||
body.createCount,
|
||||
);
|
||||
|
||||
@ -154,11 +182,24 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: ActivateCardLicensesRequest,
|
||||
): Promise<ActivateCardLicensesResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
await this.licensesService.activateCardLicenseKey(
|
||||
payload.userId,
|
||||
userId,
|
||||
body.cardLicenseKey,
|
||||
);
|
||||
|
||||
@ -194,16 +235,26 @@ export class LicensesController {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Req() req: Request,
|
||||
): Promise<GetAllocatableLicensesResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const allocatableLicenses =
|
||||
await this.licensesService.getAllocatableLicenses(
|
||||
context,
|
||||
payload.userId,
|
||||
);
|
||||
await this.licensesService.getAllocatableLicenses(context, userId);
|
||||
|
||||
return allocatableLicenses;
|
||||
}
|
||||
@ -245,16 +296,25 @@ export class LicensesController {
|
||||
@Req() req: Request,
|
||||
@Body() body: CancelOrderRequest,
|
||||
): Promise<CancelOrderResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
await this.licensesService.cancelOrder(
|
||||
context,
|
||||
payload.userId,
|
||||
body.poNumber,
|
||||
);
|
||||
await this.licensesService.cancelOrder(context, userId, body.poNumber);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,11 +56,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
expect(
|
||||
await service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
await service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('ユーザID取得できなかった場合、エラーとなる', async () => {
|
||||
@ -78,11 +78,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '', role: '', tier: 5 };
|
||||
const userId = '';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -105,11 +105,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -130,11 +130,11 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
service.licenseOrders(token, body.poNumber, body.orderCount),
|
||||
service.licenseOrders(userId, body.poNumber, body.orderCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E010401'),
|
||||
@ -154,7 +154,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new IssueCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.createCount = 10;
|
||||
const issueCardLicensesResponse: IssueCardLicensesResponse = {
|
||||
cardLicenseKeys: [
|
||||
@ -171,7 +171,7 @@ describe('LicensesService', () => {
|
||||
],
|
||||
};
|
||||
expect(
|
||||
await service.issueCardLicenseKeys(token.userId, body.createCount),
|
||||
await service.issueCardLicenseKeys(userId, body.createCount),
|
||||
).toEqual(issueCardLicensesResponse);
|
||||
});
|
||||
it('カードライセンス発行に失敗した場合、エラーになる', async () => {
|
||||
@ -187,10 +187,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new IssueCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.createCount = 1000;
|
||||
await expect(
|
||||
service.issueCardLicenseKeys(token.userId, body.createCount),
|
||||
service.issueCardLicenseKeys(userId, body.createCount),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -210,10 +210,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
expect(
|
||||
await service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
await service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
it('カードライセンス取り込みに失敗した場合、エラーになる(DBエラー)', async () => {
|
||||
@ -229,10 +229,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -254,10 +254,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
@ -276,10 +276,10 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new ActivateCardLicensesRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
const userId = '0001';
|
||||
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
|
||||
await expect(
|
||||
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
|
||||
service.activateCardLicenseKey(userId, body.cardLicenseKey),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
@ -287,7 +287,7 @@ describe('LicensesService', () => {
|
||||
});
|
||||
|
||||
describe('DBテスト', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -300,12 +300,15 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
@ -323,7 +326,9 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
it('カードライセンス取り込みが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId } = await makeTestUser(source, {
|
||||
@ -362,13 +367,15 @@ describe('DBテスト', () => {
|
||||
);
|
||||
const dbSelectResultFromLicense = await selectLicense(source, license_id);
|
||||
expect(
|
||||
dbSelectResultFromCardLicense.cardLicense.activated_at,
|
||||
dbSelectResultFromCardLicense?.cardLicense?.activated_at,
|
||||
).toBeDefined();
|
||||
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId);
|
||||
expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId);
|
||||
});
|
||||
|
||||
it('取込可能なライセンスのみが取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const now = new Date();
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
@ -513,7 +520,7 @@ describe('DBテスト', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス割り当て', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -526,12 +533,15 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -567,11 +577,11 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
await service.allocateLicense(makeContext('trackingId'), userId, 1);
|
||||
const resultLicense = await selectLicense(source, 1);
|
||||
expect(resultLicense.license.allocated_user_id).toBe(userId);
|
||||
expect(resultLicense.license.status).toBe(
|
||||
expect(resultLicense.license?.allocated_user_id).toBe(userId);
|
||||
expect(resultLicense.license?.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||
);
|
||||
expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual(
|
||||
expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual(
|
||||
expiry_date.setMilliseconds(0),
|
||||
);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -579,22 +589,24 @@ describe('ライセンス割り当て', () => {
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
true,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
});
|
||||
|
||||
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -630,30 +642,32 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
await service.allocateLicense(makeContext('trackingId'), userId, 1);
|
||||
const result = await selectLicense(source, 1);
|
||||
expect(result.license.allocated_user_id).toBe(userId);
|
||||
expect(result.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result.license.expiry_date).toEqual(date);
|
||||
expect(result.license?.allocated_user_id).toBe(userId);
|
||||
expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result.license?.expiry_date).toEqual(date);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
true,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
});
|
||||
|
||||
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -705,32 +719,32 @@ describe('ライセンス割り当て', () => {
|
||||
|
||||
// もともと割り当てられていたライセンスの状態確認
|
||||
const result1 = await selectLicense(source, 1);
|
||||
expect(result1.license.allocated_user_id).toBe(null);
|
||||
expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
|
||||
expect(result1.license.expiry_date).toEqual(date);
|
||||
expect(result1.license?.allocated_user_id).toBe(null);
|
||||
expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
|
||||
expect(result1.license?.expiry_date).toEqual(date);
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
source,
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
false,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(false);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
|
||||
// 新たに割り当てたライセンスの状態確認
|
||||
const result2 = await selectLicense(source, 2);
|
||||
expect(result2.license.allocated_user_id).toBe(userId);
|
||||
expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual(
|
||||
expect(result2.license?.allocated_user_id).toBe(userId);
|
||||
expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
|
||||
expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual(
|
||||
expiry_date.setMilliseconds(0),
|
||||
);
|
||||
const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -738,22 +752,24 @@ describe('ライセンス割り当て', () => {
|
||||
userId,
|
||||
2,
|
||||
);
|
||||
expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.license_id,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.license_id,
|
||||
).toBe(2);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.is_allocated,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(true);
|
||||
expect(
|
||||
newlicenseAllocationHistory.licenseAllocationHistory.account_id,
|
||||
newlicenseAllocationHistory.licenseAllocationHistory?.account_id,
|
||||
).toBe(accountId);
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がNORMALのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -806,12 +822,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('NONE');
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がCARDのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -864,12 +882,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('CARD');
|
||||
});
|
||||
|
||||
it('割り当て時にライセンス履歴テーブルへの登録が完了する(元がTRIALのとき)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -922,12 +942,14 @@ describe('ライセンス割り当て', () => {
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('TRIAL');
|
||||
});
|
||||
|
||||
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -961,7 +983,9 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1013,7 +1037,7 @@ describe('ライセンス割り当て', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス割り当て解除', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1026,12 +1050,15 @@ describe('ライセンス割り当て解除', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ライセンスの割り当て解除が完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1068,11 +1095,11 @@ describe('ライセンス割り当て解除', () => {
|
||||
|
||||
// 割り当て解除したライセンスの状態確認
|
||||
const deallocatedLicense = await selectLicense(source, 1);
|
||||
expect(deallocatedLicense.license.allocated_user_id).toBe(null);
|
||||
expect(deallocatedLicense.license.status).toBe(
|
||||
expect(deallocatedLicense.license?.allocated_user_id).toBe(null);
|
||||
expect(deallocatedLicense.license?.status).toBe(
|
||||
LICENSE_ALLOCATED_STATUS.REUSABLE,
|
||||
);
|
||||
expect(deallocatedLicense.license.expiry_date).toEqual(date);
|
||||
expect(deallocatedLicense.license?.expiry_date).toEqual(date);
|
||||
|
||||
// ライセンス履歴テーブルの状態確認
|
||||
const licenseAllocationHistory = await selectLicenseAllocationHistory(
|
||||
@ -1080,25 +1107,27 @@ describe('ライセンス割り当て解除', () => {
|
||||
userId,
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
|
||||
userId,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
|
||||
1,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
|
||||
false,
|
||||
);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe(
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
|
||||
).toBe(false);
|
||||
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
|
||||
accountId,
|
||||
);
|
||||
expect(
|
||||
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
|
||||
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
|
||||
).toBe('NONE');
|
||||
});
|
||||
|
||||
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
@ -1158,7 +1187,7 @@ describe('ライセンス割り当て解除', () => {
|
||||
});
|
||||
|
||||
describe('ライセンス注文キャンセル', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1171,12 +1200,15 @@ describe('ライセンス注文キャンセル', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ライセンス注文のキャンセルが完了する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
@ -1185,7 +1217,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Issue Requesting',
|
||||
@ -1195,7 +1227,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
@ -1214,12 +1246,14 @@ describe('ライセンス注文キャンセル', () => {
|
||||
tier2Accounts[0].account.id,
|
||||
poNumber,
|
||||
);
|
||||
expect(orderRecord.orderLicense.canceled_at).toBeDefined();
|
||||
expect(orderRecord.orderLicense.status).toBe('Order Canceled');
|
||||
expect(orderRecord.orderLicense?.canceled_at).toBeDefined();
|
||||
expect(orderRecord.orderLicense?.status).toBe('Order Canceled');
|
||||
});
|
||||
|
||||
it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
);
|
||||
@ -1228,7 +1262,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Issued',
|
||||
@ -1247,7 +1281,9 @@ describe('ライセンス注文キャンセル', () => {
|
||||
});
|
||||
|
||||
it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
|
||||
source,
|
||||
@ -1257,7 +1293,7 @@ describe('ライセンス注文キャンセル', () => {
|
||||
source,
|
||||
poNumber,
|
||||
tier2Accounts[0].account.id,
|
||||
tier2Accounts[0].account.parent_account_id,
|
||||
tier2Accounts[0].account.parent_account_id ?? 0,
|
||||
null,
|
||||
10,
|
||||
'Order Canceled',
|
||||
|
||||
@ -33,20 +33,20 @@ export class LicensesService {
|
||||
* @param body
|
||||
*/
|
||||
async licenseOrders(
|
||||
accessToken: AccessToken,
|
||||
externalId: string,
|
||||
poNumber: string,
|
||||
orderCount: number,
|
||||
): Promise<void> {
|
||||
//アクセストークンからユーザーIDを取得する
|
||||
this.logger.log(`[IN] ${this.licenseOrders.name}`);
|
||||
const userId = accessToken.userId;
|
||||
let myAccountId: number;
|
||||
let parentAccountId: number;
|
||||
let parentAccountId: number | undefined;
|
||||
|
||||
// ユーザIDからアカウントIDを取得する
|
||||
try {
|
||||
myAccountId = (await this.usersRepository.findUserByExternalId(userId))
|
||||
.account_id;
|
||||
myAccountId = (
|
||||
await this.usersRepository.findUserByExternalId(externalId)
|
||||
).account_id;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
@ -65,9 +65,13 @@ export class LicensesService {
|
||||
|
||||
// 親アカウントIDを取得
|
||||
try {
|
||||
parentAccountId = (
|
||||
await this.accountsRepository.findAccountById(myAccountId)
|
||||
).parent_account_id;
|
||||
parentAccountId =
|
||||
(await this.accountsRepository.findAccountById(myAccountId))
|
||||
.parent_account_id ?? undefined;
|
||||
// 親アカウントIDが取得できない場合はエラー
|
||||
if (parentAccountId === undefined) {
|
||||
throw new Error('parent account id is undefined');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
switch (e.constructor) {
|
||||
|
||||
@ -124,11 +124,11 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user1.notification = false;
|
||||
user1.encryption = false;
|
||||
user1.prompt = false;
|
||||
user1.deleted_at = undefined;
|
||||
user1.deleted_at = null;
|
||||
user1.created_by = 'test';
|
||||
user1.created_at = new Date();
|
||||
user1.updated_by = undefined;
|
||||
user1.updated_at = undefined;
|
||||
user1.updated_by = null;
|
||||
user1.updated_at = new Date();
|
||||
|
||||
return {
|
||||
findUserByExternalId: user1,
|
||||
|
||||
@ -12,14 +12,14 @@ import {
|
||||
export const createLicense = async (
|
||||
datasource: DataSource,
|
||||
licenseId: number,
|
||||
expiry_date: Date,
|
||||
expiry_date: Date | null,
|
||||
accountId: number,
|
||||
type: string,
|
||||
status: string,
|
||||
allocated_user_id: number,
|
||||
order_id: number,
|
||||
deleted_at: Date,
|
||||
delete_order_id: number,
|
||||
allocated_user_id: number | null,
|
||||
order_id: number | null,
|
||||
deleted_at: Date | null,
|
||||
delete_order_id: number | null,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(License).insert({
|
||||
id: licenseId,
|
||||
@ -107,7 +107,7 @@ export const createOrder = async (
|
||||
poNumber: string,
|
||||
fromId: number,
|
||||
toId: number,
|
||||
issuedAt: Date,
|
||||
issuedAt: Date | null,
|
||||
quantity: number,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
@ -138,7 +138,7 @@ export const selectCardLicensesCount = async (
|
||||
export const selectCardLicense = async (
|
||||
datasource: DataSource,
|
||||
cardLicenseKey: string,
|
||||
): Promise<{ cardLicense: CardLicense }> => {
|
||||
): Promise<{ cardLicense: CardLicense | null }> => {
|
||||
const cardLicense = await datasource.getRepository(CardLicense).findOne({
|
||||
where: {
|
||||
card_license_key: cardLicenseKey,
|
||||
@ -150,7 +150,7 @@ export const selectCardLicense = async (
|
||||
export const selectLicense = async (
|
||||
datasource: DataSource,
|
||||
id: number,
|
||||
): Promise<{ license: License }> => {
|
||||
): Promise<{ license: License | null }> => {
|
||||
const license = await datasource.getRepository(License).findOne({
|
||||
where: {
|
||||
id: id,
|
||||
@ -163,7 +163,7 @@ export const selectLicenseAllocationHistory = async (
|
||||
datasource: DataSource,
|
||||
userId: number,
|
||||
licence_id: number,
|
||||
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => {
|
||||
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
|
||||
const licenseAllocationHistory = await datasource
|
||||
.getRepository(LicenseAllocationHistory)
|
||||
.findOne({
|
||||
@ -182,7 +182,7 @@ export const selectOrderLicense = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
poNumber: string,
|
||||
): Promise<{ orderLicense: LicenseOrder }> => {
|
||||
): Promise<{ orderLicense: LicenseOrder | null }> => {
|
||||
const orderLicense = await datasource.getRepository(LicenseOrder).findOne({
|
||||
where: {
|
||||
from_account_id: accountId,
|
||||
|
||||
@ -47,8 +47,8 @@ export class GetAllocatableLicensesRequest {}
|
||||
export class AllocatableLicenseInfo {
|
||||
@ApiProperty()
|
||||
licenseId: number;
|
||||
@ApiProperty()
|
||||
expiryDate: Date;
|
||||
@ApiProperty({ required: false })
|
||||
expiryDate?: Date;
|
||||
}
|
||||
export class GetAllocatableLicensesResponse {
|
||||
@ApiProperty({ type: [AllocatableLicenseInfo] })
|
||||
|
||||
@ -77,14 +77,14 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user.external_id = 'external_id';
|
||||
user.account_id = 123;
|
||||
user.role = 'none';
|
||||
user.author_id = undefined;
|
||||
user.author_id = null;
|
||||
user.accepted_eula_version = '1.0';
|
||||
user.accepted_dpa_version = '1.0';
|
||||
user.email_verified = true;
|
||||
user.auto_renew = false;
|
||||
user.license_alert = false;
|
||||
user.notification = false;
|
||||
user.deleted_at = undefined;
|
||||
user.deleted_at = null;
|
||||
user.created_by = 'test';
|
||||
user.created_at = new Date();
|
||||
user.updated_by = 'test';
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
Controller,
|
||||
Get,
|
||||
Headers,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
@ -32,6 +33,8 @@ import {
|
||||
TasksResponse,
|
||||
} from './types/types';
|
||||
import {
|
||||
SortDirection,
|
||||
TaskListSortableAttribute,
|
||||
isSortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
@ -43,6 +46,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { Roles } from '../../common/types/role';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('tasks')
|
||||
@Controller('tasks')
|
||||
@ -80,22 +84,38 @@ export class TasksController {
|
||||
@Req() req,
|
||||
@Query() body: TasksRequest,
|
||||
): Promise<TasksResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
const context = makeContext(decodedToken.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
const { limit, offset, status } = body;
|
||||
const paramName = isTaskListSortableAttribute(body.paramName)
|
||||
? body.paramName
|
||||
const paramName = isTaskListSortableAttribute(body.paramName ?? '')
|
||||
? (body.paramName as TaskListSortableAttribute)
|
||||
: undefined;
|
||||
const direction = isSortDirection(body.direction)
|
||||
? body.direction
|
||||
const direction = isSortDirection(body.direction ?? '')
|
||||
? (body.direction as SortDirection)
|
||||
: undefined;
|
||||
|
||||
const { tasks, total } = await this.taskService.getTasks(
|
||||
context,
|
||||
decodedToken,
|
||||
userId,
|
||||
roles,
|
||||
offset,
|
||||
limit,
|
||||
// statusが指定されていない場合は全てのステータスを取得する
|
||||
@ -183,10 +203,22 @@ export class TasksController {
|
||||
@Param() param: ChangeStatusRequest,
|
||||
): Promise<ChangeStatusResponse> {
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { role, userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
@ -241,10 +273,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -296,10 +340,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId, role } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
@ -353,10 +409,22 @@ export class TasksController {
|
||||
): Promise<ChangeStatusResponse> {
|
||||
const { audioFileId } = params;
|
||||
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -491,11 +559,22 @@ export class TasksController {
|
||||
@Body() body: PostCheckoutPermissionRequest,
|
||||
): Promise<PostCheckoutPermissionResponse> {
|
||||
const { assignees } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
|
||||
const { role, userId } = jwt.decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, role } = decodedAccessToken as AccessToken;
|
||||
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
|
||||
const roles = role.split(' ') as Roles[];
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -45,10 +45,10 @@ export class TasksService {
|
||||
private readonly notificationhubService: NotificationhubService,
|
||||
) {}
|
||||
|
||||
// TODO [Task2244] 引数にAccessTokenがあるのは不適切なのでController側で分解したい
|
||||
async getTasks(
|
||||
context: Context,
|
||||
accessToken: AccessToken,
|
||||
userId: string,
|
||||
roles: Roles[],
|
||||
offset: number,
|
||||
limit: number,
|
||||
status?: string[],
|
||||
@ -59,10 +59,6 @@ export class TasksService {
|
||||
`[IN] [${context.trackingId}] ${this.getTasks.name} | params: { offset: ${offset}, limit: ${limit}, status: ${status}, paramName: ${paramName}, direction: ${direction} };`,
|
||||
);
|
||||
|
||||
const { role, userId } = accessToken;
|
||||
// TODO [Task2244] Roleに型で定義されている値が入っているかをチェックして異常値を弾く実装に修正する
|
||||
const roles = role.split(' ');
|
||||
|
||||
// パラメータが省略された場合のデフォルト値: 保存するソート条件の値の初期値と揃える
|
||||
const defaultParamName: TaskListSortableAttribute = 'JOB_NUMBER';
|
||||
const defaultDirection: SortDirection = 'ASC';
|
||||
@ -95,6 +91,10 @@ export class TasksService {
|
||||
return { tasks: tasks, total: result.count };
|
||||
}
|
||||
if (roles.includes(USER_ROLES.AUTHOR)) {
|
||||
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (!author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
const result =
|
||||
await this.taskRepository.getTasksFromAuthorIdAndAccountId(
|
||||
author_id,
|
||||
@ -179,6 +179,10 @@ export class TasksService {
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
|
||||
if (roles.includes(USER_ROLES.AUTHOR)) {
|
||||
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (!author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
await this.taskRepository.getTaskFromAudioFileId(
|
||||
audioFileId,
|
||||
account_id,
|
||||
@ -407,30 +411,21 @@ export class TasksService {
|
||||
permissions: CheckoutPermission[],
|
||||
): Promise<AdB2cUser[]> {
|
||||
// 割り当て候補の外部IDを列挙
|
||||
const assigneesExternalIds = permissions.map((x) => {
|
||||
if (x.user) {
|
||||
return x.user.external_id;
|
||||
}
|
||||
});
|
||||
const assigneesExternalIds = permissions.flatMap((permission) =>
|
||||
permission.user ? [permission.user.external_id] : [],
|
||||
);
|
||||
// 割り当てられているタイピストの外部IDを列挙
|
||||
const typistExternalIds = tasks.flatMap((x) => {
|
||||
if (x.typist_user) {
|
||||
return x.typist_user.external_id;
|
||||
}
|
||||
});
|
||||
const typistExternalIds = tasks.flatMap((task) =>
|
||||
task.typist_user ? [task.typist_user.external_id] : [],
|
||||
);
|
||||
|
||||
//重複をなくす
|
||||
const distinctedExternalIds = [
|
||||
...new Set(assigneesExternalIds.concat(typistExternalIds)),
|
||||
];
|
||||
|
||||
// undefinedがあった場合、取り除く
|
||||
const filteredExternalIds: string[] = distinctedExternalIds.filter(
|
||||
(x): x is string => x !== undefined,
|
||||
);
|
||||
|
||||
// B2Cからユーザー名を取得する
|
||||
return await this.adB2cService.getUsers(context, filteredExternalIds);
|
||||
return await this.adB2cService.getUsers(context, distinctedExternalIds);
|
||||
}
|
||||
/**
|
||||
* 文字起こし候補を変更する
|
||||
@ -451,10 +446,14 @@ export class TasksService {
|
||||
);
|
||||
const { author_id, account_id } =
|
||||
await this.usersRepository.findUserByExternalId(externalId);
|
||||
// RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
|
||||
if (role.includes(USER_ROLES.AUTHOR) && !author_id) {
|
||||
throw new Error('AuthorID not found');
|
||||
}
|
||||
|
||||
await this.taskRepository.changeCheckoutPermission(
|
||||
audioFileId,
|
||||
author_id,
|
||||
author_id ?? undefined,
|
||||
account_id,
|
||||
role,
|
||||
assignees,
|
||||
@ -462,11 +461,16 @@ export class TasksService {
|
||||
|
||||
// すべての割り当て候補ユーザーを取得する
|
||||
const assigneesGroupIds = assignees
|
||||
.filter((x) => x.typistGroupId)
|
||||
.map((x) => x.typistGroupId);
|
||||
.filter((assignee) => assignee.typistGroupId)
|
||||
.flatMap((assignee) =>
|
||||
assignee.typistGroupId ? [assignee.typistGroupId] : [],
|
||||
);
|
||||
|
||||
const assigneesUserIds = assignees
|
||||
.filter((x) => x.typistUserId)
|
||||
.map((x) => x.typistUserId);
|
||||
.filter((assignee) => assignee.typistUserId)
|
||||
.flatMap((assignee) =>
|
||||
assignee.typistUserId ? [assignee.typistUserId] : [],
|
||||
);
|
||||
|
||||
const groupMembers =
|
||||
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
|
||||
|
||||
@ -263,6 +263,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@ -270,6 +275,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 2,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
@ -277,6 +287,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
@ -284,6 +299,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 1,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
@ -291,6 +311,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
|
||||
user_id: 3,
|
||||
created_by: 'test',
|
||||
updated_by: 'test',
|
||||
created_at: new Date(),
|
||||
deleted_at: null,
|
||||
updated_at: null,
|
||||
user: null,
|
||||
userGroup: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -307,7 +332,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user1.auto_renew = false;
|
||||
user1.license_alert = false;
|
||||
user1.notification = false;
|
||||
user1.deleted_at = undefined;
|
||||
user1.deleted_at = null;
|
||||
user1.created_by = 'test';
|
||||
user1.created_at = new Date();
|
||||
user1.author_id = 'abcdef';
|
||||
@ -331,66 +356,82 @@ const defaultTasksRepositoryMockValue: {
|
||||
status: 'Uploaded',
|
||||
priority: '00',
|
||||
created_at: new Date('2023-01-01T01:01:01.000Z'),
|
||||
finished_at: null,
|
||||
started_at: null,
|
||||
typist_user_id: null,
|
||||
template_file_id: null,
|
||||
typist_user: null,
|
||||
template_file: null,
|
||||
option_items: [
|
||||
{
|
||||
id: 1,
|
||||
audio_file_id: 1,
|
||||
label: 'label01',
|
||||
value: 'value01',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
audio_file_id: 1,
|
||||
label: 'label02',
|
||||
value: 'value02',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
audio_file_id: 1,
|
||||
label: 'label03',
|
||||
value: 'value03',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
audio_file_id: 1,
|
||||
label: 'label04',
|
||||
value: 'value04',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
audio_file_id: 1,
|
||||
label: 'label05',
|
||||
value: 'value05',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
audio_file_id: 1,
|
||||
label: 'label06',
|
||||
value: 'value06',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
audio_file_id: 1,
|
||||
label: 'label07',
|
||||
value: 'value07',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
audio_file_id: 1,
|
||||
label: 'label08',
|
||||
value: 'value08',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
audio_file_id: 1,
|
||||
label: 'label09',
|
||||
value: 'value09',
|
||||
task: null,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
audio_file_id: 1,
|
||||
label: 'label10',
|
||||
value: 'value10',
|
||||
task: null,
|
||||
},
|
||||
],
|
||||
file: {
|
||||
@ -410,6 +451,8 @@ const defaultTasksRepositoryMockValue: {
|
||||
audio_format: 'DS',
|
||||
comment: 'comment',
|
||||
is_encrypted: true,
|
||||
deleted_at: null,
|
||||
task: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -435,7 +478,16 @@ const defaultTasksRepositoryMockValue: {
|
||||
created_at: new Date(),
|
||||
updated_by: 'test',
|
||||
updated_at: new Date(),
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
},
|
||||
task: null,
|
||||
user_group_id: null,
|
||||
user_group: null,
|
||||
},
|
||||
],
|
||||
count: 1,
|
||||
|
||||
@ -39,10 +39,10 @@ import {
|
||||
makeNotificationhubServiceMock,
|
||||
} from './tasks.service.mock';
|
||||
|
||||
export const makeTaskTestingModule = async (
|
||||
export const makeTaskTestingModuleWithNotificaiton = async (
|
||||
datasource: DataSource,
|
||||
notificationhubServiceMockValue: NotificationhubServiceMockValue,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
@ -205,7 +205,7 @@ export const createUserGroup = async (
|
||||
export const getTask = async (
|
||||
datasource: DataSource,
|
||||
task_id: number,
|
||||
): Promise<Task> => {
|
||||
): Promise<Task | null> => {
|
||||
const task = await datasource.getRepository(Task).findOne({
|
||||
where: {
|
||||
id: task_id,
|
||||
|
||||
@ -45,7 +45,7 @@ const createTask = (
|
||||
const assignees = createAssignees(permissions, b2cUserInfo);
|
||||
|
||||
// RepositoryDTO => ControllerDTOに変換
|
||||
const typist: Typist =
|
||||
const typist: Typist | undefined =
|
||||
typist_user != null
|
||||
? convertUserToTypist(typist_user, b2cUserInfo)
|
||||
: undefined;
|
||||
@ -113,7 +113,10 @@ const convertUserToAssignee = (
|
||||
): Assignee => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
)?.displayName;
|
||||
if (!typistName) {
|
||||
throw new Error('typistName not found.');
|
||||
}
|
||||
return {
|
||||
typistUserId: user.id,
|
||||
typistName,
|
||||
@ -135,7 +138,10 @@ const convertUserToTypist = (
|
||||
): Typist => {
|
||||
const typistName = b2cUserInfo.find(
|
||||
(x) => x.id === user.external_id,
|
||||
).displayName;
|
||||
)?.displayName;
|
||||
if (!typistName) {
|
||||
throw new Error('typistName not found.');
|
||||
}
|
||||
return {
|
||||
id: user.id,
|
||||
name: typistName,
|
||||
|
||||
@ -66,7 +66,7 @@ describe('getTemplates', () => {
|
||||
expect(templates[1].id).toBe(template2.id);
|
||||
expect(templates[1].name).toBe(template2.file_name);
|
||||
}
|
||||
}, 6000000);
|
||||
});
|
||||
|
||||
it('テンプレートファイル一覧を取得できる(0件)', async () => {
|
||||
if (!source) fail();
|
||||
|
||||
@ -23,6 +23,9 @@ export const createTemplateFile = async (
|
||||
id: template.id,
|
||||
},
|
||||
});
|
||||
if (!templateFile) {
|
||||
fail();
|
||||
}
|
||||
|
||||
return templateFile;
|
||||
};
|
||||
|
||||
@ -8,7 +8,7 @@ import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
describe('利用規約取得', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -21,12 +21,15 @@ describe('利用規約取得', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('最新の利用規約情報が取得できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<TermsService>(TermsService);
|
||||
|
||||
await createTermInfo(source, 'EULA', 'v1.0');
|
||||
@ -44,7 +47,9 @@ describe('利用規約取得', () => {
|
||||
});
|
||||
|
||||
it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<TermsService>(TermsService);
|
||||
const context = makeContext(uuidv4());
|
||||
await expect(service.getTermsInfo(context)).rejects.toEqual(
|
||||
@ -56,7 +61,9 @@ describe('利用規約取得', () => {
|
||||
});
|
||||
|
||||
it('利用規約情報(EULAのみ)が存在しない場合エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<TermsService>(TermsService);
|
||||
await createTermInfo(source, 'DPA', 'v1.0');
|
||||
const context = makeContext(uuidv4());
|
||||
@ -69,7 +76,9 @@ describe('利用規約取得', () => {
|
||||
});
|
||||
|
||||
it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<TermsService>(TermsService);
|
||||
await createTermInfo(source, 'EULA', 'v1.0');
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
@ -64,7 +64,7 @@ export type ConfigMockValue = {
|
||||
|
||||
export const makeUsersServiceMock = async (
|
||||
usersRepositoryMockValue: UsersRepositoryMockValue,
|
||||
licensesRepositoryMockValue: LicensesRepositoryMockValue,
|
||||
licensesRepositoryMockValue: LicensesRepositoryMockValue | null,
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
sendGridMockValue: SendGridMockValue,
|
||||
configMockValue: ConfigMockValue,
|
||||
@ -368,7 +368,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user1.created_by = 'test';
|
||||
user1.created_at = new Date();
|
||||
user1.updated_by = null;
|
||||
user1.updated_at = null;
|
||||
user1.updated_at = new Date();
|
||||
|
||||
const user2 = new User();
|
||||
user2.id = 3;
|
||||
@ -388,7 +388,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
||||
user2.created_by = 'test';
|
||||
user2.created_at = new Date();
|
||||
user2.updated_by = null;
|
||||
user2.updated_at = null;
|
||||
user2.updated_at = new Date();
|
||||
|
||||
return {
|
||||
updateUserVerified: undefined,
|
||||
@ -406,7 +406,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
|
||||
displayName: 'test1',
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'issuer',
|
||||
issuerAssignedId: 'test1@mail.com',
|
||||
},
|
||||
@ -417,7 +417,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
|
||||
displayName: 'test2',
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'issuer',
|
||||
issuerAssignedId: 'test2@mail.com',
|
||||
},
|
||||
@ -428,7 +428,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
|
||||
displayName: 'test3',
|
||||
identities: [
|
||||
{
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: 'issuer',
|
||||
issuerAssignedId: 'test3@mail.com',
|
||||
},
|
||||
|
||||
@ -107,7 +107,7 @@ export const createLicense = async (
|
||||
export const makeTestingModuleWithAdb2c = async (
|
||||
datasource: DataSource,
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
): Promise<TestingModule> => {
|
||||
): Promise<TestingModule | undefined> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
|
||||
@ -130,10 +130,23 @@ export class UsersController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get()
|
||||
async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const users = await this.usersService.getUsers(decodedToken.userId);
|
||||
const users = await this.usersService.getUsers(userId);
|
||||
return { users };
|
||||
}
|
||||
|
||||
@ -179,15 +192,28 @@ export class UsersController {
|
||||
prompt,
|
||||
} = body;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(payload.userId);
|
||||
const context = makeContext(userId);
|
||||
|
||||
//ユーザ作成処理
|
||||
await this.usersService.createUser(
|
||||
context,
|
||||
payload,
|
||||
userId,
|
||||
name,
|
||||
role as UserRoles,
|
||||
email,
|
||||
@ -225,8 +251,21 @@ export class UsersController {
|
||||
@UseGuards(AuthGuard)
|
||||
@Get('relations')
|
||||
async getRelations(@Req() req: Request): Promise<GetRelationsResponse> {
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(token, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -265,8 +304,22 @@ export class UsersController {
|
||||
@Req() req: Request,
|
||||
): Promise<PostSortCriteriaResponse> {
|
||||
const { direction, paramName } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
//型チェック
|
||||
if (
|
||||
@ -278,11 +331,7 @@ export class UsersController {
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
await this.usersService.updateSortCriteria(
|
||||
paramName,
|
||||
direction,
|
||||
decodedToken,
|
||||
);
|
||||
await this.usersService.updateSortCriteria(paramName, direction, userId);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -313,11 +362,25 @@ export class UsersController {
|
||||
@Req() req: Request,
|
||||
): Promise<GetSortCriteriaResponse> {
|
||||
const {} = query;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const { direction, paramName } = await this.usersService.getSortCriteria(
|
||||
decodedToken,
|
||||
userId,
|
||||
);
|
||||
return { direction, paramName };
|
||||
}
|
||||
@ -367,7 +430,20 @@ export class UsersController {
|
||||
} = body;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
@ -421,8 +497,21 @@ export class UsersController {
|
||||
@Body() body: AllocateLicenseRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<AllocateLicenseResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
await this.usersService.allocateLicense(
|
||||
@ -467,8 +556,21 @@ export class UsersController {
|
||||
@Body() body: DeallocateLicenseRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<DeallocateLicenseResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const accessToken = retrieveAuthorizationToken(req) as string;
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(userId);
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ import {
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
describe('UsersService.confirmUser', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -57,12 +57,15 @@ describe('UsersService.confirmUser', () => {
|
||||
return source.initialize();
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになり、トライアルライセンスが100件作成される', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = (await makeTestAccount(source)).account;
|
||||
const { id: userId } = await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -110,9 +113,10 @@ describe('UsersService.confirmUser', () => {
|
||||
allocated_user_id: resultLicenses[0].allocated_user_id,
|
||||
order_id: resultLicenses[0].order_id,
|
||||
delete_order_id: resultLicenses[0].delete_order_id,
|
||||
user: resultLicenses[0].user ?? null,
|
||||
};
|
||||
|
||||
expect(resultUser.email_verified).toBe(true);
|
||||
expect(resultUser?.email_verified).toBe(true);
|
||||
expect(resultLicenses.length).toBe(100);
|
||||
expect(resultLicensesCheckParam).toEqual({
|
||||
id: 0,
|
||||
@ -123,11 +127,14 @@ describe('UsersService.confirmUser', () => {
|
||||
allocated_user_id: null,
|
||||
order_id: null,
|
||||
delete_order_id: null,
|
||||
user: null,
|
||||
});
|
||||
});
|
||||
}, 600000);
|
||||
|
||||
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const token = 'invalid.id.token';
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
await expect(service.confirmUser(token)).rejects.toEqual(
|
||||
@ -136,7 +143,9 @@ describe('UsersService.confirmUser', () => {
|
||||
});
|
||||
|
||||
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { id: accountId } = (await makeTestAccount(source)).account;
|
||||
await makeTestUser(source, {
|
||||
account_id: accountId,
|
||||
@ -168,7 +177,9 @@ describe('UsersService.confirmUser', () => {
|
||||
);
|
||||
});
|
||||
it('ユーザーが存在しない場合は、想定外のエラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
|
||||
@ -201,6 +212,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
};
|
||||
const licensesRepositoryMockValue = null;
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
@ -246,6 +263,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
};
|
||||
const licensesRepositoryMockValue = null;
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
@ -287,6 +310,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
};
|
||||
const licensesRepositoryMockValue = null;
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
@ -332,6 +361,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
||||
notification: true,
|
||||
encryption: false,
|
||||
prompt: false,
|
||||
account: null,
|
||||
author_id: null,
|
||||
deleted_at: null,
|
||||
encryption_password: null,
|
||||
license: null,
|
||||
userGroupMembers: null,
|
||||
};
|
||||
const licensesRepositoryMockValue = null;
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
@ -362,7 +397,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
|
||||
});
|
||||
|
||||
describe('UsersService.createUser', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -375,12 +410,15 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
|
||||
@ -433,7 +471,7 @@ describe('UsersService.createUser', () => {
|
||||
expect(
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -446,16 +484,16 @@ describe('UsersService.createUser', () => {
|
||||
// 追加されたユーザーが正しくDBに登録されていることを確認
|
||||
const user = await getUserFromExternalId(source, externalId);
|
||||
expect(user).not.toBeNull();
|
||||
expect(user.account_id).toEqual(accountId);
|
||||
expect(user.role).toEqual(role);
|
||||
expect(user.author_id).toEqual(null);
|
||||
expect(user.email_verified).toEqual(false);
|
||||
expect(user.auto_renew).toEqual(autoRenew);
|
||||
expect(user.license_alert).toEqual(licenseAlert);
|
||||
expect(user.notification).toEqual(notification);
|
||||
expect(user.encryption).toEqual(false);
|
||||
expect(user.encryption_password).toEqual(null);
|
||||
expect(user.prompt).toEqual(false);
|
||||
expect(user?.account_id).toEqual(accountId);
|
||||
expect(user?.role).toEqual(role);
|
||||
expect(user?.author_id).toEqual(null);
|
||||
expect(user?.email_verified).toEqual(false);
|
||||
expect(user?.auto_renew).toEqual(autoRenew);
|
||||
expect(user?.license_alert).toEqual(licenseAlert);
|
||||
expect(user?.notification).toEqual(notification);
|
||||
expect(user?.encryption).toEqual(false);
|
||||
expect(user?.encryption_password).toEqual(null);
|
||||
expect(user?.prompt).toEqual(false);
|
||||
|
||||
// 他にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
@ -463,7 +501,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -519,7 +559,7 @@ describe('UsersService.createUser', () => {
|
||||
expect(
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -536,16 +576,16 @@ describe('UsersService.createUser', () => {
|
||||
// 追加されたユーザーが正しくDBに登録されていることを確認
|
||||
const user = await getUserFromExternalId(source, externalId);
|
||||
expect(user).not.toBeNull();
|
||||
expect(user.account_id).toEqual(accountId);
|
||||
expect(user.role).toEqual(role);
|
||||
expect(user.author_id).toEqual(authorId);
|
||||
expect(user.email_verified).toEqual(false);
|
||||
expect(user.auto_renew).toEqual(autoRenew);
|
||||
expect(user.license_alert).toEqual(licenseAlert);
|
||||
expect(user.notification).toEqual(notification);
|
||||
expect(user.encryption).toEqual(encryption);
|
||||
expect(user.encryption_password).toEqual(encryptionPassword);
|
||||
expect(user.prompt).toEqual(prompt);
|
||||
expect(user?.account_id).toEqual(accountId);
|
||||
expect(user?.role).toEqual(role);
|
||||
expect(user?.author_id).toEqual(authorId);
|
||||
expect(user?.email_verified).toEqual(false);
|
||||
expect(user?.auto_renew).toEqual(autoRenew);
|
||||
expect(user?.license_alert).toEqual(licenseAlert);
|
||||
expect(user?.notification).toEqual(notification);
|
||||
expect(user?.encryption).toEqual(encryption);
|
||||
expect(user?.encryption_password).toEqual(encryptionPassword);
|
||||
expect(user?.prompt).toEqual(prompt);
|
||||
|
||||
// 他にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
@ -553,7 +593,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -608,7 +650,7 @@ describe('UsersService.createUser', () => {
|
||||
expect(
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -625,16 +667,16 @@ describe('UsersService.createUser', () => {
|
||||
// 追加されたユーザーが正しくDBに登録されていることを確認
|
||||
const user = await getUserFromExternalId(source, externalId);
|
||||
expect(user).not.toBeNull();
|
||||
expect(user.account_id).toEqual(accountId);
|
||||
expect(user.role).toEqual(role);
|
||||
expect(user.author_id).toEqual(authorId);
|
||||
expect(user.email_verified).toEqual(false);
|
||||
expect(user.auto_renew).toEqual(autoRenew);
|
||||
expect(user.license_alert).toEqual(licenseAlert);
|
||||
expect(user.notification).toEqual(notification);
|
||||
expect(user.encryption).toEqual(encryption);
|
||||
expect(user.encryption_password).toBeNull();
|
||||
expect(user.prompt).toEqual(prompt);
|
||||
expect(user?.account_id).toEqual(accountId);
|
||||
expect(user?.role).toEqual(role);
|
||||
expect(user?.author_id).toEqual(authorId);
|
||||
expect(user?.email_verified).toEqual(false);
|
||||
expect(user?.auto_renew).toEqual(autoRenew);
|
||||
expect(user?.license_alert).toEqual(licenseAlert);
|
||||
expect(user?.notification).toEqual(notification);
|
||||
expect(user?.encryption).toEqual(encryption);
|
||||
expect(user?.encryption_password).toBeNull();
|
||||
expect(user?.prompt).toEqual(prompt);
|
||||
|
||||
// 他にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
@ -642,7 +684,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -694,7 +738,7 @@ describe('UsersService.createUser', () => {
|
||||
expect(
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -707,16 +751,16 @@ describe('UsersService.createUser', () => {
|
||||
// 追加されたユーザーが正しくDBに登録されていることを確認
|
||||
const user = await getUserFromExternalId(source, externalId);
|
||||
expect(user).not.toBeNull();
|
||||
expect(user.account_id).toEqual(accountId);
|
||||
expect(user.role).toEqual(role);
|
||||
expect(user.author_id).toBeNull();
|
||||
expect(user.email_verified).toEqual(false);
|
||||
expect(user.auto_renew).toEqual(autoRenew);
|
||||
expect(user.license_alert).toEqual(licenseAlert);
|
||||
expect(user.notification).toEqual(notification);
|
||||
expect(user.encryption).toEqual(false);
|
||||
expect(user.encryption_password).toBeNull();
|
||||
expect(user.prompt).toEqual(false);
|
||||
expect(user?.account_id).toEqual(accountId);
|
||||
expect(user?.role).toEqual(role);
|
||||
expect(user?.author_id).toBeNull();
|
||||
expect(user?.email_verified).toEqual(false);
|
||||
expect(user?.auto_renew).toEqual(autoRenew);
|
||||
expect(user?.license_alert).toEqual(licenseAlert);
|
||||
expect(user?.notification).toEqual(notification);
|
||||
expect(user?.encryption).toEqual(false);
|
||||
expect(user?.encryption_password).toBeNull();
|
||||
expect(user?.prompt).toEqual(false);
|
||||
|
||||
// 他にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
@ -724,7 +768,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
@ -785,7 +831,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -809,7 +855,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
@ -870,7 +918,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -899,7 +947,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -949,7 +999,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -972,7 +1022,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('Azure AD B2C内でメールアドレスが重複している場合、エラーとなる。', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -1026,7 +1078,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -1049,7 +1101,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { account, admin } = await makeTestAccount(
|
||||
@ -1105,7 +1159,7 @@ describe('UsersService.createUser', () => {
|
||||
expect(
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email_1,
|
||||
@ -1146,7 +1200,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email_2,
|
||||
@ -1175,7 +1229,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
@ -1240,7 +1296,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -1272,7 +1328,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
|
||||
@ -1327,7 +1385,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -1357,7 +1415,9 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
|
||||
@ -1417,7 +1477,7 @@ describe('UsersService.createUser', () => {
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
adminExternalId,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -1446,7 +1506,7 @@ describe('UsersService.createUser', () => {
|
||||
});
|
||||
|
||||
describe('UsersService.getUsers', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1459,13 +1519,16 @@ describe('UsersService.getUsers', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId_author, id: authorUserId } =
|
||||
@ -1565,7 +1628,9 @@ describe('UsersService.getUsers', () => {
|
||||
|
||||
it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { id: user1, external_id: external_id1 } = await makeTestUser(
|
||||
@ -1679,7 +1744,9 @@ describe('UsersService.getUsers', () => {
|
||||
|
||||
it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
await makeTestUser(source, {
|
||||
@ -1702,7 +1769,9 @@ describe('UsersService.getUsers', () => {
|
||||
it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
adb2cParam.getUsers = new Error('ADB2C error');
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: externalId_author } = await makeTestUser(source, {
|
||||
@ -1742,11 +1811,7 @@ describe('UsersService.updateSortCriteria', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
await service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
|
||||
@ -1771,11 +1836,7 @@ describe('UsersService.updateSortCriteria', () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -1806,11 +1867,7 @@ describe('UsersService.updateSortCriteria', () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
@ -1838,13 +1895,10 @@ describe('UsersService.getSortCriteria', () => {
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
|
||||
expect(
|
||||
await service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' });
|
||||
expect(await service.getSortCriteria('external_id')).toEqual({
|
||||
direction: 'ASC',
|
||||
paramName: 'JOB_NUMBER',
|
||||
});
|
||||
});
|
||||
|
||||
it('ソート条件が存在せず、ソート条件を取得できない', async () => {
|
||||
@ -1869,13 +1923,7 @@ describe('UsersService.getSortCriteria', () => {
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -1907,13 +1955,7 @@ describe('UsersService.getSortCriteria', () => {
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
|
||||
new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -1923,7 +1965,7 @@ describe('UsersService.getSortCriteria', () => {
|
||||
});
|
||||
|
||||
describe('UsersService.updateUser', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -1936,12 +1978,15 @@ describe('UsersService.updateUser', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('ユーザー情報を更新できる(None)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -1985,19 +2030,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.NONE);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.NONE);
|
||||
expect(createdUser?.author_id).toBeNull();
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(false);
|
||||
expect(createdUser?.encryption_password).toBeNull();
|
||||
expect(createdUser?.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザー情報を更新できる(Typist)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2041,19 +2088,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser?.author_id).toBeNull();
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(false);
|
||||
expect(createdUser?.encryption_password).toBeNull();
|
||||
expect(createdUser?.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザー情報を更新できる(Author)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2097,19 +2146,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(true);
|
||||
expect(createdUser.encryption_password).toBe('new_password');
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser?.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(true);
|
||||
expect(createdUser?.encryption_password).toBe('new_password');
|
||||
expect(createdUser?.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('ユーザーのRoleを更新できる(None⇒Typist)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2153,19 +2204,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser.author_id).toBeNull();
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.TYPIST);
|
||||
expect(createdUser?.author_id).toBeNull();
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(false);
|
||||
expect(createdUser?.encryption_password).toBeNull();
|
||||
expect(createdUser?.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('ユーザーのRoleを更新できる(None⇒Author)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2209,19 +2262,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(false);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser?.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(false);
|
||||
expect(createdUser?.encryption_password).toBeNull();
|
||||
expect(createdUser?.prompt).toBe(false);
|
||||
});
|
||||
|
||||
it('None以外からRoleを変更した場合、エラーとなる(Typist⇒None)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2267,7 +2322,9 @@ describe('UsersService.updateUser', () => {
|
||||
});
|
||||
|
||||
it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持する(Encryptionがtrue)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2311,19 +2368,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(true);
|
||||
expect(createdUser.encryption_password).toBe('password');
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser?.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(true);
|
||||
expect(createdUser?.encryption_password).toBe('password');
|
||||
expect(createdUser?.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('Authorが暗号化なしで更新した場合、パスワードをNULLにする(Encryptionがfalse)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2367,19 +2426,21 @@ describe('UsersService.updateUser', () => {
|
||||
|
||||
const createdUser = await getUser(source, user1);
|
||||
|
||||
expect(createdUser.id).toBe(user1);
|
||||
expect(createdUser.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser.auto_renew).toBe(false);
|
||||
expect(createdUser.license_alert).toBe(false);
|
||||
expect(createdUser.notification).toBe(false);
|
||||
expect(createdUser.encryption).toBe(false);
|
||||
expect(createdUser.encryption_password).toBeNull();
|
||||
expect(createdUser.prompt).toBe(true);
|
||||
expect(createdUser?.id).toBe(user1);
|
||||
expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
|
||||
expect(createdUser?.author_id).toBe('AUTHOR_ID');
|
||||
expect(createdUser?.auto_renew).toBe(false);
|
||||
expect(createdUser?.license_alert).toBe(false);
|
||||
expect(createdUser?.notification).toBe(false);
|
||||
expect(createdUser?.encryption).toBe(false);
|
||||
expect(createdUser?.encryption_password).toBeNull();
|
||||
expect(createdUser?.prompt).toBe(true);
|
||||
});
|
||||
|
||||
it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなる(Encryptionがtrue)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2425,7 +2486,9 @@ describe('UsersService.updateUser', () => {
|
||||
});
|
||||
|
||||
it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
|
||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||
const { external_id: external_id } = await makeTestUser(source, {
|
||||
@ -2483,7 +2546,7 @@ describe('UsersService.updateUser', () => {
|
||||
});
|
||||
|
||||
describe('UsersService.updateAcceptedVersion', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -2496,12 +2559,15 @@ describe('UsersService.updateAcceptedVersion', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョンを更新できる(第五)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
@ -2511,11 +2577,13 @@ describe('UsersService.updateAcceptedVersion', () => {
|
||||
await service.updateAcceptedVersion(context, admin.external_id, 'v2.0');
|
||||
const user = await getUser(source, admin.id);
|
||||
|
||||
expect(user.accepted_eula_version).toBe('v2.0');
|
||||
expect(user?.accepted_eula_version).toBe('v2.0');
|
||||
});
|
||||
|
||||
it('同意済み利用規約バージョンを更新できる(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
@ -2530,27 +2598,14 @@ describe('UsersService.updateAcceptedVersion', () => {
|
||||
);
|
||||
const user = await getUser(source, admin.id);
|
||||
|
||||
expect(user.accepted_eula_version).toBe('v2.0');
|
||||
expect(user.accepted_dpa_version).toBe('v3.0');
|
||||
});
|
||||
|
||||
it('パラメータが不在のときエラーとなる(第五)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
const context = makeContext(uuidv4());
|
||||
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
await expect(
|
||||
service.updateAcceptedVersion(context, admin.external_id, undefined),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
|
||||
);
|
||||
expect(user?.accepted_eula_version).toBe('v2.0');
|
||||
expect(user?.accepted_dpa_version).toBe('v3.0');
|
||||
});
|
||||
|
||||
it('パラメータが不在のときエラーとなる(第一~第四)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
const { admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
|
||||
@ -48,9 +48,13 @@ import {
|
||||
LicenseExpiredError,
|
||||
LicenseUnavailableError,
|
||||
} from '../../repositories/licenses/errors/types';
|
||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
private readonly logger = new Logger(UsersService.name);
|
||||
private readonly mailFrom: string;
|
||||
private readonly appDomain: string;
|
||||
constructor(
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly licensesRepository: LicensesRepositoryService,
|
||||
@ -58,8 +62,10 @@ export class UsersService {
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly sendgridService: SendGridService,
|
||||
) {}
|
||||
private readonly logger = new Logger(UsersService.name);
|
||||
) {
|
||||
this.mailFrom = this.configService.getOrThrow<string>('MAIL_FROM');
|
||||
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms user
|
||||
@ -128,7 +134,7 @@ export class UsersService {
|
||||
*/
|
||||
async createUser(
|
||||
context: Context,
|
||||
accessToken: AccessToken,
|
||||
externalId: string,
|
||||
name: string,
|
||||
role: UserRoles,
|
||||
email: string,
|
||||
@ -145,9 +151,7 @@ export class UsersService {
|
||||
//DBよりアクセス者の所属するアカウントIDを取得する
|
||||
let adminUser: EntityUser;
|
||||
try {
|
||||
adminUser = await this.usersRepository.findUserByExternalId(
|
||||
accessToken.userId,
|
||||
);
|
||||
adminUser = await this.usersRepository.findUserByExternalId(externalId);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw new HttpException(
|
||||
@ -254,9 +258,6 @@ export class UsersService {
|
||||
|
||||
//Email送信用のコンテンツを作成する
|
||||
try {
|
||||
// メールの送信元を取得
|
||||
const from = this.configService.get<string>('MAIL_FROM') ?? '';
|
||||
|
||||
// メールの内容を構成
|
||||
const { subject, text, html } =
|
||||
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
|
||||
@ -269,7 +270,7 @@ export class UsersService {
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -343,6 +344,12 @@ export class UsersService {
|
||||
license_alert: licenseAlert,
|
||||
notification,
|
||||
role,
|
||||
accepted_dpa_version: null,
|
||||
accepted_eula_version: null,
|
||||
encryption: false,
|
||||
encryption_password: null,
|
||||
prompt: false,
|
||||
author_id: null,
|
||||
};
|
||||
case USER_ROLES.AUTHOR:
|
||||
return {
|
||||
@ -352,10 +359,12 @@ export class UsersService {
|
||||
license_alert: licenseAlert,
|
||||
notification,
|
||||
role,
|
||||
author_id: authorId,
|
||||
encryption,
|
||||
encryption_password: encryptionPassword,
|
||||
prompt,
|
||||
author_id: authorId ?? null,
|
||||
encryption: encryption ?? false,
|
||||
encryption_password: encryptionPassword ?? null,
|
||||
prompt: prompt ?? false,
|
||||
accepted_dpa_version: null,
|
||||
accepted_eula_version: null,
|
||||
};
|
||||
default:
|
||||
//不正なroleが指定された場合はログを出力してエラーを返す
|
||||
@ -405,19 +414,16 @@ export class UsersService {
|
||||
await this.adB2cService.changePassword(extarnalId, ramdomPassword);
|
||||
// ユーザを認証済みにする
|
||||
await this.usersRepository.updateUserVerified(userId);
|
||||
// メールの送信元を取得
|
||||
const from = this.configService.get<string>('MAIL_FROM') ?? '';
|
||||
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
|
||||
const subject = 'A temporary password has been issued.';
|
||||
const text = 'temporary password: ' + ramdomPassword;
|
||||
const domains = this.configService.get<string>('APP_DOMAIN');
|
||||
const html = `<p>OMDS TOP PAGE URL.<p><a href="${domains}">${domains}"</a><br>temporary password: ${ramdomPassword}`;
|
||||
const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}"</a><br>temporary password: ${ramdomPassword}`;
|
||||
|
||||
// メールを送信
|
||||
await this.sendgridService.sendMail(
|
||||
context,
|
||||
email,
|
||||
from,
|
||||
this.mailFrom,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
@ -464,17 +470,29 @@ export class UsersService {
|
||||
);
|
||||
|
||||
// DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出
|
||||
const users = dbUsers.map((x) => {
|
||||
const users = dbUsers.map((dbUser): User => {
|
||||
// ユーザーの所属グループ名を取得する
|
||||
const groupNames =
|
||||
x.userGroupMembers?.map((group) => group.userGroup?.name) ?? [];
|
||||
const userGroupMembers =
|
||||
dbUser.userGroupMembers !== null ? dbUser.userGroupMembers : [];
|
||||
|
||||
const adb2cUser = adb2cUsers.find((user) => user.id === x.external_id);
|
||||
//所属グループ名の配列にする
|
||||
const groupNames = userGroupMembers.flatMap((userGroupMember) =>
|
||||
userGroupMember.userGroup ? [userGroupMember.userGroup.name] : [],
|
||||
);
|
||||
|
||||
const adb2cUser = adb2cUsers.find(
|
||||
(user) => user.id === dbUser.external_id,
|
||||
);
|
||||
|
||||
// メールアドレスを取得する
|
||||
const mail = adb2cUser.identities.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
).issuerAssignedId;
|
||||
const mail = adb2cUser?.identities?.find(
|
||||
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
)?.issuerAssignedId;
|
||||
|
||||
//メールアドレスが取得できない場合はエラー
|
||||
if (!mail) {
|
||||
throw new Error('mail not found.');
|
||||
}
|
||||
|
||||
let status = USER_LICENSE_STATUS.NORMAL;
|
||||
|
||||
@ -483,21 +501,30 @@ export class UsersService {
|
||||
let expiration: string | undefined = undefined;
|
||||
let remaining: number | undefined = undefined;
|
||||
|
||||
if (x.license) {
|
||||
if (dbUser.license) {
|
||||
// 有効期限日付 YYYY/MM/DD
|
||||
const expiry_date = x.license.expiry_date;
|
||||
expiration = `${expiry_date.getFullYear()}/${
|
||||
expiry_date.getMonth() + 1
|
||||
}/${expiry_date.getDate()}`;
|
||||
const expiry_date = dbUser.license.expiry_date ?? undefined;
|
||||
expiration =
|
||||
expiry_date !== undefined
|
||||
? `${expiry_date.getFullYear()}/${
|
||||
expiry_date.getMonth() + 1
|
||||
}/${expiry_date.getDate()}`
|
||||
: undefined;
|
||||
|
||||
const currentDate = new DateWithZeroTime();
|
||||
// 有効期限までの日数
|
||||
remaining = Math.floor(
|
||||
(expiry_date.getTime() - currentDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
);
|
||||
if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) {
|
||||
status = x.auto_renew
|
||||
remaining =
|
||||
expiry_date !== undefined
|
||||
? Math.floor(
|
||||
(expiry_date.getTime() - currentDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: undefined;
|
||||
if (
|
||||
remaining !== undefined &&
|
||||
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
|
||||
) {
|
||||
status = dbUser.auto_renew
|
||||
? USER_LICENSE_STATUS.RENEW
|
||||
: USER_LICENSE_STATUS.ALERT;
|
||||
}
|
||||
@ -506,18 +533,18 @@ export class UsersService {
|
||||
}
|
||||
|
||||
return {
|
||||
id: x.id,
|
||||
id: dbUser.id,
|
||||
name: adb2cUser.displayName,
|
||||
role: x.role,
|
||||
authorId: x.author_id ?? undefined,
|
||||
role: dbUser.role,
|
||||
authorId: dbUser.author_id ?? undefined,
|
||||
typistGroupName: groupNames,
|
||||
email: mail,
|
||||
emailVerified: x.email_verified,
|
||||
autoRenew: x.auto_renew,
|
||||
licenseAlert: x.license_alert,
|
||||
notification: x.notification,
|
||||
encryption: x.encryption,
|
||||
prompt: x.prompt,
|
||||
emailVerified: dbUser.email_verified,
|
||||
autoRenew: dbUser.auto_renew,
|
||||
licenseAlert: dbUser.license_alert,
|
||||
notification: dbUser.notification,
|
||||
encryption: dbUser.encryption,
|
||||
prompt: dbUser.prompt,
|
||||
expiration: expiration,
|
||||
remaining: remaining,
|
||||
licenseStatus: status,
|
||||
@ -545,14 +572,13 @@ export class UsersService {
|
||||
async updateSortCriteria(
|
||||
paramName: TaskListSortableAttribute,
|
||||
direction: SortDirection,
|
||||
token: AccessToken,
|
||||
externalId: string,
|
||||
): Promise<void> {
|
||||
this.logger.log(`[IN] ${this.updateSortCriteria.name}`);
|
||||
let user: EntityUser;
|
||||
try {
|
||||
// ユーザー情報を取得
|
||||
const sub = token.userId;
|
||||
user = await this.usersRepository.findUserByExternalId(sub);
|
||||
user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
|
||||
@ -584,7 +610,7 @@ export class UsersService {
|
||||
* @param token
|
||||
* @returns sort criteria
|
||||
*/
|
||||
async getSortCriteria(token: AccessToken): Promise<{
|
||||
async getSortCriteria(externalId: string): Promise<{
|
||||
paramName: TaskListSortableAttribute;
|
||||
direction: SortDirection;
|
||||
}> {
|
||||
@ -592,8 +618,7 @@ export class UsersService {
|
||||
let user: EntityUser;
|
||||
try {
|
||||
// ユーザー情報を取得
|
||||
const sub = token.userId;
|
||||
user = await this.usersRepository.findUserByExternalId(sub);
|
||||
user = await this.usersRepository.findUserByExternalId(externalId);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
|
||||
@ -643,8 +668,8 @@ export class UsersService {
|
||||
|
||||
// TODO: PBI2105 本実装時に修正すること
|
||||
return {
|
||||
authorId: user.author_id,
|
||||
authorIdList: [user.author_id, 'XXX'],
|
||||
authorId: user.author_id ?? '',
|
||||
authorIdList: [user.author_id ?? '', 'XXX'],
|
||||
isEncrypted: true,
|
||||
encryptionPassword: 'abcd@123?dcba',
|
||||
audioFormat: 'DS2(QP)',
|
||||
@ -1004,6 +1029,11 @@ export class UsersService {
|
||||
makeErrorResponse('E010204'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case AccountNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010501'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case UpdateTermsVersionNotSetError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010001'),
|
||||
|
||||
@ -49,7 +49,7 @@ export const getWorkflow = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
id: number,
|
||||
): Promise<Workflow> => {
|
||||
): Promise<Workflow | null> => {
|
||||
return await datasource.getRepository(Workflow).findOne({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
|
||||
@ -66,7 +66,6 @@ export class WorkflowsController {
|
||||
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
|
||||
@Get()
|
||||
async getWorkflows(@Req() req: Request): Promise<GetWorkflowsResponse> {
|
||||
// TODO strictNullChecks対応
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
@ -123,7 +122,7 @@ export class WorkflowsController {
|
||||
@Body() body: CreateWorkflowsRequest,
|
||||
): Promise<CreateWorkflowsResponse> {
|
||||
const { authorId, worktypeId, templateId, typists } = body;
|
||||
// TODO strictNullChecks対応
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
@ -188,7 +187,7 @@ export class WorkflowsController {
|
||||
): Promise<UpdateWorkflowResponse> {
|
||||
const { authorId, worktypeId, templateId, typists } = body;
|
||||
const { workflowId } = param;
|
||||
// TODO strictNullChecks対応
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
@ -252,7 +251,7 @@ export class WorkflowsController {
|
||||
@Param() param: DeleteWorkflowRequestParam,
|
||||
): Promise<DeleteWorkflowResponse> {
|
||||
const { workflowId } = param;
|
||||
// TODO strictNullChecks対応
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
|
||||
@ -2062,7 +2062,7 @@ describe('updateWorkflow', () => {
|
||||
});
|
||||
|
||||
describe('deleteWorkflows', () => {
|
||||
let source: DataSource = null;
|
||||
let source: DataSource | null = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
@ -2074,12 +2074,15 @@ describe('deleteWorkflows', () => {
|
||||
return source.initialize();
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (!source) return;
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('アカウント内のWorkflowを削除できる', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorId } = await makeTestUser(source, {
|
||||
@ -2126,7 +2129,9 @@ describe('deleteWorkflows', () => {
|
||||
});
|
||||
|
||||
it('アカウント内のWorkflowを削除できる(複数ワークフローがある場合)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorId1 } = await makeTestUser(source, {
|
||||
@ -2177,7 +2182,9 @@ describe('deleteWorkflows', () => {
|
||||
});
|
||||
|
||||
it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorId } = await makeTestUser(source, {
|
||||
@ -2226,7 +2233,9 @@ describe('deleteWorkflows', () => {
|
||||
});
|
||||
|
||||
it('指定されたワークフローが存在しない場合、400エラーを返却する(ログインユーザーのアカウント外)', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorId } = await makeTestUser(source, {
|
||||
@ -2303,7 +2312,9 @@ describe('deleteWorkflows', () => {
|
||||
});
|
||||
|
||||
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
|
||||
if (!source) fail();
|
||||
const module = await makeTestingModule(source);
|
||||
if (!module) fail();
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, { tier: 5 });
|
||||
const { id: authorId } = await makeTestUser(source, {
|
||||
|
||||
@ -57,8 +57,8 @@ export class WorkflowsService {
|
||||
return workflowTypists;
|
||||
});
|
||||
// externalIdsからundefinedを除外
|
||||
const filteredExternalIds = externalIds.filter(
|
||||
(externalId): externalId is string => externalId !== undefined,
|
||||
const filteredExternalIds = externalIds.flatMap((externalId) =>
|
||||
externalId ? [externalId] : [],
|
||||
);
|
||||
// externalIdsから重複を除外
|
||||
const distinctedExternalIds = [...new Set(filteredExternalIds)];
|
||||
|
||||
@ -40,8 +40,8 @@ export class AdB2cService {
|
||||
// ADB2Cへの認証情報
|
||||
const credential = new ClientSecretCredential(
|
||||
this.configService.getOrThrow<string>('ADB2C_TENANT_ID'),
|
||||
this.configService.getOrThrow('ADB2C_CLIENT_ID'),
|
||||
this.configService.getOrThrow('ADB2C_CLIENT_SECRET'),
|
||||
this.configService.getOrThrow<string>('ADB2C_CLIENT_ID'),
|
||||
this.configService.getOrThrow<string>('ADB2C_CLIENT_SECRET'),
|
||||
);
|
||||
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
||||
scopes: ['https://graph.microsoft.com/.default'],
|
||||
@ -76,7 +76,7 @@ export class AdB2cService {
|
||||
},
|
||||
identities: [
|
||||
{
|
||||
signinType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
|
||||
signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
|
||||
issuer: `${this.tenantName}.onmicrosoft.com`,
|
||||
issuerAssignedId: email,
|
||||
},
|
||||
|
||||
@ -8,7 +8,13 @@ import { Context } from '../../common/log';
|
||||
@Injectable()
|
||||
export class SendGridService {
|
||||
private readonly logger = new Logger(SendGridService.name);
|
||||
private readonly emailConfirmLifetime: number;
|
||||
private readonly appDomain: string;
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
|
||||
this.emailConfirmLifetime = this.configService.getOrThrow<number>(
|
||||
'EMAIL_CONFIRM_LIFETIME',
|
||||
);
|
||||
const key = this.configService.getOrThrow<string>('SENDGRID_API_KEY');
|
||||
sendgrid.setApiKey(key);
|
||||
}
|
||||
@ -30,8 +36,6 @@ export class SendGridService {
|
||||
`[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`,
|
||||
);
|
||||
|
||||
const lifetime =
|
||||
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const token = sign<{ accountId: number; userId: number; email: string }>(
|
||||
{
|
||||
@ -39,10 +43,9 @@ export class SendGridService {
|
||||
userId,
|
||||
email,
|
||||
},
|
||||
lifetime,
|
||||
this.emailConfirmLifetime,
|
||||
privateKey,
|
||||
);
|
||||
const domains = this.configService.get<string>('APP_DOMAIN');
|
||||
const path = 'mail-confirm/';
|
||||
|
||||
this.logger.log(
|
||||
@ -50,8 +53,8 @@ export class SendGridService {
|
||||
);
|
||||
return {
|
||||
subject: 'Verify your new account',
|
||||
text: `The verification URL. ${domains}${path}?verify=${token}`,
|
||||
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`,
|
||||
text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
|
||||
html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}"</a>`,
|
||||
};
|
||||
}
|
||||
|
||||
@ -68,9 +71,6 @@ export class SendGridService {
|
||||
userId: number,
|
||||
email: string,
|
||||
): Promise<{ subject: string; text: string; html: string }> {
|
||||
const lifetime =
|
||||
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
|
||||
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
const token = sign<{ accountId: number; userId: number; email: string }>(
|
||||
@ -79,16 +79,15 @@ export class SendGridService {
|
||||
userId,
|
||||
email,
|
||||
},
|
||||
lifetime,
|
||||
this.emailConfirmLifetime,
|
||||
privateKey,
|
||||
);
|
||||
const domains = this.configService.get<string>('APP_DOMAIN');
|
||||
const path = 'mail-confirm/user/';
|
||||
|
||||
return {
|
||||
subject: 'Verify your new account',
|
||||
text: `The verification URL. ${domains}${path}?verify=${token}`,
|
||||
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`,
|
||||
text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
|
||||
html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}"</a>`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
import { User, UserArchive } from '../users/entity/user.entity';
|
||||
import { Account } from './entity/account.entity';
|
||||
import {
|
||||
CardLicense,
|
||||
License,
|
||||
LicenseAllocationHistory,
|
||||
LicenseAllocationHistoryArchive,
|
||||
@ -48,6 +49,14 @@ import {
|
||||
import { DateWithZeroTime } from '../../features/licenses/types/types';
|
||||
import { Worktype } from '../worktypes/entity/worktype.entity';
|
||||
import { WorktypeIdNotFoundError } from '../worktypes/errors/types';
|
||||
import { OptionItem } from '../worktypes/entity/option_item.entity';
|
||||
import { Task } from '../tasks/entity/task.entity';
|
||||
import { CheckoutPermission } from '../checkout_permissions/entity/checkout_permission.entity';
|
||||
import { AudioFile } from '../audio_files/entity/audio_file.entity';
|
||||
import { AudioOptionItem } from '../audio_option_items/entity/audio_option_item.entity';
|
||||
import { UserGroup } from '../user_groups/entity/user_group.entity';
|
||||
import { UserGroupMember } from '../user_groups/entity/user_group_member.entity';
|
||||
import { TemplateFile } from '../template_files/entity/template_file.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsRepositoryService {
|
||||
@ -117,13 +126,13 @@ export class AccountsRepositoryService {
|
||||
tier: number,
|
||||
adminExternalUserId: string,
|
||||
adminUserRole: string,
|
||||
adminUserAcceptedEulaVersion: string,
|
||||
adminUserAcceptedDpaVersion: string,
|
||||
adminUserAcceptedEulaVersion?: string,
|
||||
adminUserAcceptedDpaVersion?: string,
|
||||
): Promise<{ newAccount: Account; adminUser: User }> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const account = new Account();
|
||||
{
|
||||
account.parent_account_id = dealerAccountId;
|
||||
account.parent_account_id = dealerAccountId ?? null;
|
||||
account.company_name = companyName;
|
||||
account.country = country;
|
||||
account.tier = tier;
|
||||
@ -138,8 +147,8 @@ export class AccountsRepositoryService {
|
||||
user.account_id = persistedAccount.id;
|
||||
user.external_id = adminExternalUserId;
|
||||
user.role = adminUserRole;
|
||||
user.accepted_eula_version = adminUserAcceptedEulaVersion;
|
||||
user.accepted_dpa_version = adminUserAcceptedDpaVersion;
|
||||
user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
|
||||
user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
|
||||
}
|
||||
const usersRepo = entityManager.getRepository(User);
|
||||
const newUser = usersRepo.create(user);
|
||||
@ -490,6 +499,9 @@ export class AccountsRepositoryService {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
if (!ownAccount) {
|
||||
throw new AccountNotFoundError();
|
||||
}
|
||||
|
||||
// 自アカウントのライセンス注文状況を取得する
|
||||
const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus(
|
||||
@ -532,8 +544,8 @@ export class AccountsRepositoryService {
|
||||
);
|
||||
|
||||
// 第五の不足数を算出するためのライセンス数情報を取得する
|
||||
let expiringSoonLicense: number;
|
||||
let allocatableLicenseWithMargin: number;
|
||||
let expiringSoonLicense: number = 0;
|
||||
let allocatableLicenseWithMargin: number = 0;
|
||||
if (childAccount.tier === TIERS.TIER5) {
|
||||
expiringSoonLicense = await this.getExpiringSoonLicense(
|
||||
entityManager,
|
||||
@ -606,7 +618,7 @@ export class AccountsRepositoryService {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const accountRepository = entityManager.getRepository(Account);
|
||||
const maxTierDifference = TIERS.TIER5 - TIERS.TIER1;
|
||||
const parentAccountIds = [];
|
||||
const parentAccountIds: number[] = [];
|
||||
|
||||
let currentAccountId = targetAccountId;
|
||||
// システム的な最大の階層差異分、親を参照する
|
||||
@ -619,6 +631,9 @@ export class AccountsRepositoryService {
|
||||
if (!account) {
|
||||
break;
|
||||
}
|
||||
if (!account.parent_account_id) {
|
||||
throw new Error("Parent account doesn't exist.");
|
||||
}
|
||||
|
||||
parentAccountIds.push(account.parent_account_id);
|
||||
currentAccountId = account.parent_account_id;
|
||||
@ -740,11 +755,13 @@ export class AccountsRepositoryService {
|
||||
});
|
||||
|
||||
// ADB2Cから情報を取得するための外部ユーザIDを取得する(念のためプライマリ管理者IDが存在しない場合を考慮)
|
||||
const primaryUserIds = partnerAccounts.map((x) => {
|
||||
const primaryUserIds = partnerAccounts.flatMap((x) => {
|
||||
if (x.primary_admin_user_id) {
|
||||
return x.primary_admin_user_id;
|
||||
return [x.primary_admin_user_id];
|
||||
} else if (x.secondary_admin_user_id) {
|
||||
return x.secondary_admin_user_id;
|
||||
return [x.secondary_admin_user_id];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
@ -761,15 +778,18 @@ export class AccountsRepositoryService {
|
||||
user.id === account.primary_admin_user_id ||
|
||||
user.id === account.secondary_admin_user_id,
|
||||
);
|
||||
const primaryAccountExternalId = primaryUser
|
||||
? primaryUser.external_id
|
||||
: undefined;
|
||||
if (!primaryUser) {
|
||||
throw new AdminUserNotFoundError(
|
||||
`Primary admin user is not found. id: ${account.primary_admin_user_id}, account_id: ${account.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
name: account.company_name,
|
||||
tier: account.tier,
|
||||
accountId: account.id,
|
||||
country: account.country,
|
||||
primaryAccountExternalId: primaryAccountExternalId,
|
||||
primaryAccountExternalId: primaryUser.external_id,
|
||||
dealerManagement: account.delegation_permission,
|
||||
};
|
||||
});
|
||||
@ -790,7 +810,7 @@ export class AccountsRepositoryService {
|
||||
async getOneUpperTierAccount(
|
||||
accountId: number,
|
||||
tier: number,
|
||||
): Promise<Account | undefined> {
|
||||
): Promise<Account | null> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
return await accountRepo.findOne({
|
||||
@ -869,10 +889,10 @@ export class AccountsRepositoryService {
|
||||
await accountRepo.update(
|
||||
{ id: myAccountId },
|
||||
{
|
||||
parent_account_id: parentAccountId || null,
|
||||
parent_account_id: parentAccountId ?? null,
|
||||
delegation_permission: delegationPermission,
|
||||
primary_admin_user_id: primaryAdminUserId,
|
||||
secondary_admin_user_id: secondryAdminUserId || null,
|
||||
secondary_admin_user_id: secondryAdminUserId ?? null,
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -966,9 +986,107 @@ export class AccountsRepositoryService {
|
||||
.execute();
|
||||
|
||||
// アカウントを削除
|
||||
// アカウントを削除することで、外部キー制約がで紐づいている関連テーブルのデータも削除される
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
await accountRepo.delete({ id: accountId });
|
||||
|
||||
// ライセンス系(card_license_issue以外)のテーブルのレコードを削除する
|
||||
const orderRepo = entityManager.getRepository(LicenseOrder);
|
||||
await orderRepo.delete({
|
||||
from_account_id: accountId,
|
||||
});
|
||||
const licenseRepo = entityManager.getRepository(License);
|
||||
const targetLicenses = await licenseRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
const cardLicenseRepo = entityManager.getRepository(CardLicense);
|
||||
await cardLicenseRepo.delete({
|
||||
license_id: In(targetLicenses.map((license) => license.id)),
|
||||
});
|
||||
await licenseRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
const LicenseAllocationHistoryRepo = entityManager.getRepository(
|
||||
LicenseAllocationHistory,
|
||||
);
|
||||
await LicenseAllocationHistoryRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
// ワークタイプ系のテーブルのレコードを削除する
|
||||
const worktypeRepo = entityManager.getRepository(Worktype);
|
||||
const taggerWorktypes = await worktypeRepo.find({
|
||||
where: { account_id: accountId },
|
||||
});
|
||||
|
||||
const optionItemRepo = entityManager.getRepository(OptionItem);
|
||||
await optionItemRepo.delete({
|
||||
worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)),
|
||||
});
|
||||
await worktypeRepo.delete({ account_id: accountId });
|
||||
|
||||
// タスク系のテーブルのレコードを削除する
|
||||
const taskRepo = entityManager.getRepository(Task);
|
||||
const targetTasks = await taskRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
const checkoutPermissionRepo =
|
||||
entityManager.getRepository(CheckoutPermission);
|
||||
await checkoutPermissionRepo.delete({
|
||||
task_id: In(targetTasks.map((task) => task.id)),
|
||||
});
|
||||
await taskRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
// オーディオファイル系のテーブルのレコードを削除する
|
||||
const audioFileRepo = entityManager.getRepository(AudioFile);
|
||||
const targetaudioFiles = await audioFileRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
const audioOptionItemsRepo = entityManager.getRepository(AudioOptionItem);
|
||||
await audioOptionItemsRepo.delete({
|
||||
audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)),
|
||||
});
|
||||
await audioFileRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
// ユーザーグループ系のテーブルのレコードを削除する
|
||||
const userGroupRepo = entityManager.getRepository(UserGroup);
|
||||
const targetUserGroup = await userGroupRepo.find({
|
||||
where: {
|
||||
account_id: accountId,
|
||||
},
|
||||
});
|
||||
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
|
||||
await userGroupMemberRepo.delete({
|
||||
user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)),
|
||||
});
|
||||
await userGroupRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
// テンプレートファイルテーブルのレコードを削除する
|
||||
const templateFileRepo = entityManager.getRepository(TemplateFile);
|
||||
await templateFileRepo.delete({ account_id: accountId });
|
||||
|
||||
// ユーザテーブルのレコードを削除する
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
await userRepo.delete({
|
||||
account_id: accountId,
|
||||
});
|
||||
|
||||
// ソート条件のテーブルのレコードを削除する
|
||||
const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
|
||||
await sortCriteriaRepo.delete({
|
||||
user_id: In(users.map((user) => user.id)),
|
||||
});
|
||||
return users;
|
||||
});
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ export class Account {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
parent_account_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
parent_account_id: number | null;
|
||||
|
||||
@Column()
|
||||
tier: number;
|
||||
@ -34,30 +34,36 @@ export class Account {
|
||||
@Column({ default: false })
|
||||
verified: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
primary_admin_user_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
primary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
secondary_admin_user_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
secondary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
active_worktype_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
active_worktype_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by?: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by?: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@OneToMany(() => User, (user) => user.id)
|
||||
user?: User[];
|
||||
user: User[] | null;
|
||||
}
|
||||
|
||||
@ -32,12 +32,12 @@ export class AudioFile {
|
||||
priority: string;
|
||||
@Column()
|
||||
audio_format: string;
|
||||
@Column({ nullable: true })
|
||||
comment?: string;
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
comment: string | null;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
@Column()
|
||||
is_encrypted: boolean;
|
||||
@OneToOne(() => Task, (task) => task.file)
|
||||
task?: Task;
|
||||
task: Task | null;
|
||||
}
|
||||
|
||||
@ -19,5 +19,5 @@ export class AudioOptionItem {
|
||||
value: string;
|
||||
@ManyToOne(() => Task, (task) => task.audio_file_id)
|
||||
@JoinColumn({ name: 'audio_file_id' })
|
||||
task?: Task;
|
||||
task: Task | null;
|
||||
}
|
||||
|
||||
@ -18,21 +18,21 @@ export class CheckoutPermission {
|
||||
@Column({})
|
||||
task_id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
user_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
user_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
user_group_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
user_group_id: number | null;
|
||||
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user?: User;
|
||||
user: User | null;
|
||||
|
||||
@OneToOne(() => UserGroup, (group) => group.id)
|
||||
@JoinColumn({ name: 'user_group_id' })
|
||||
user_group?: UserGroup;
|
||||
user_group: UserGroup | null;
|
||||
|
||||
@ManyToOne(() => Task, (task) => task.id)
|
||||
@JoinColumn({ name: 'task_id' })
|
||||
task?: Task;
|
||||
task: Task | null;
|
||||
}
|
||||
|
||||
@ -25,11 +25,14 @@ export class LicenseOrder {
|
||||
@Column()
|
||||
to_account_id: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
ordered_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
issued_at?: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
issued_at: Date | null;
|
||||
|
||||
@Column()
|
||||
quantity: number;
|
||||
@ -37,19 +40,25 @@ export class LicenseOrder {
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
canceled_at?: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
canceled_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn()
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
@ -58,8 +67,8 @@ export class License {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
expiry_date: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
expiry_date: Date | null;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
@ -70,33 +79,41 @@ export class License {
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
allocated_user_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
allocated_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
order_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
order_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
delete_order_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
delete_order_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn()
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
updated_at: Date;
|
||||
|
||||
@OneToOne(() => User, (user) => user.license)
|
||||
@OneToOne(() => User, (user) => user.license, {
|
||||
createForeignKeyConstraints: false,
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: 'allocated_user_id' })
|
||||
user?: User;
|
||||
user: User | null;
|
||||
}
|
||||
|
||||
@Entity({ name: 'card_license_issue' })
|
||||
@ -107,16 +124,22 @@ export class CardLicenseIssue {
|
||||
@Column()
|
||||
issued_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn()
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
@ -131,19 +154,25 @@ export class CardLicense {
|
||||
@Column()
|
||||
card_license_key: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
activated_at: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
activated_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({})
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
@ -170,24 +199,32 @@ export class LicenseAllocationHistory {
|
||||
@Column()
|
||||
switch_from_type: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn()
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, (licenses) => licenses.id)
|
||||
@ManyToOne(() => License, (licenses) => licenses.id, {
|
||||
createForeignKeyConstraints: false,
|
||||
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license?: License;
|
||||
license: License | null;
|
||||
}
|
||||
|
||||
@Entity({ name: 'licenses_archive' })
|
||||
@ -195,8 +232,8 @@ export class LicenseArchive {
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true })
|
||||
expiry_date: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
expiry_date: Date | null;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
@ -207,31 +244,34 @@ export class LicenseArchive {
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
allocated_user_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
allocated_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
order_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
order_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
delete_order_id: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
delete_order_id: number | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@Column()
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@Column()
|
||||
updated_at: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
archived_at: Date;
|
||||
}
|
||||
|
||||
@ -258,21 +298,24 @@ export class LicenseAllocationHistoryArchive {
|
||||
@Column()
|
||||
switch_from_type: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
deleted_at: Date;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@Column()
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
|
||||
@Column()
|
||||
updated_at: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
})
|
||||
archived_at: Date;
|
||||
}
|
||||
|
||||
@ -444,7 +444,7 @@ export class LicensesRepositoryService {
|
||||
const allocatableLicenses = await queryBuilder.getMany();
|
||||
return allocatableLicenses.map((license) => ({
|
||||
licenseId: license.id,
|
||||
expiryDate: license.expiry_date,
|
||||
expiryDate: license.expiry_date ?? undefined,
|
||||
}));
|
||||
}
|
||||
/**
|
||||
@ -469,6 +469,13 @@ export class LicensesRepositoryService {
|
||||
},
|
||||
});
|
||||
|
||||
// ライセンスが存在しない場合はエラー
|
||||
if (!targetLicense) {
|
||||
throw new LicenseNotExistError(
|
||||
`License not exist. licenseId: ${newLicenseId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 期限切れの場合はエラー
|
||||
if (targetLicense.expiry_date) {
|
||||
const currentDay = new Date();
|
||||
@ -533,7 +540,7 @@ export class LicensesRepositoryService {
|
||||
});
|
||||
|
||||
let switchFromType = '';
|
||||
if (oldLicenseType) {
|
||||
if (oldLicenseType && oldLicenseType.license) {
|
||||
switch (oldLicenseType.license.type) {
|
||||
case LICENSE_TYPE.CARD:
|
||||
switchFromType = SWITCH_FROM_TYPE.CARD;
|
||||
|
||||
@ -20,33 +20,33 @@ export class Task {
|
||||
job_number: string;
|
||||
@Column()
|
||||
account_id: number;
|
||||
@Column({ nullable: true })
|
||||
is_job_number_enabled?: boolean;
|
||||
@Column({ nullable: true, type: 'tinyint' })
|
||||
is_job_number_enabled: boolean | null;
|
||||
@Column()
|
||||
audio_file_id: number;
|
||||
@Column()
|
||||
status: string;
|
||||
@Column({ nullable: true })
|
||||
typist_user_id?: number;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
typist_user_id: number | null;
|
||||
@Column()
|
||||
priority: string;
|
||||
@Column({ nullable: true })
|
||||
template_file_id?: number;
|
||||
@Column({ nullable: true })
|
||||
started_at?: Date;
|
||||
@Column({ nullable: true })
|
||||
finished_at?: Date;
|
||||
@Column({ nullable: true, type: 'unsigned big int' })
|
||||
template_file_id: number | null;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
started_at: Date | null;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
finished_at: Date | null;
|
||||
@Column({})
|
||||
created_at: Date;
|
||||
@OneToOne(() => AudioFile, (audiofile) => audiofile.task)
|
||||
@JoinColumn({ name: 'audio_file_id' })
|
||||
file?: AudioFile;
|
||||
file: AudioFile | null;
|
||||
@OneToMany(() => AudioOptionItem, (option) => option.task)
|
||||
option_items?: AudioOptionItem[];
|
||||
option_items: AudioOptionItem[] | null;
|
||||
@OneToOne(() => User, (user) => user.id)
|
||||
@JoinColumn({ name: 'typist_user_id' })
|
||||
typist_user?: User;
|
||||
typist_user: User | null;
|
||||
@ManyToOne(() => TemplateFile, (templateFile) => templateFile.id)
|
||||
@JoinColumn({ name: 'template_file_id' })
|
||||
template_file?: TemplateFile;
|
||||
template_file: TemplateFile | null;
|
||||
}
|
||||
|
||||
@ -757,7 +757,7 @@ export class TasksRepositoryService {
|
||||
*/
|
||||
async changeCheckoutPermission(
|
||||
audio_file_id: number,
|
||||
author_id: string,
|
||||
author_id: string | undefined,
|
||||
account_id: number,
|
||||
roles: Roles[],
|
||||
assignees: Assignee[],
|
||||
@ -844,8 +844,8 @@ export class TasksRepositoryService {
|
||||
(assignee) => {
|
||||
const checkoutPermission = new CheckoutPermission();
|
||||
checkoutPermission.task_id = taskRecord.id;
|
||||
checkoutPermission.user_id = assignee.typistUserId;
|
||||
checkoutPermission.user_group_id = assignee.typistGroupId;
|
||||
checkoutPermission.user_id = assignee.typistUserId ?? null;
|
||||
checkoutPermission.user_group_id = assignee.typistGroupId ?? null;
|
||||
return checkoutPermission;
|
||||
},
|
||||
);
|
||||
|
||||
@ -18,11 +18,11 @@ export class TemplateFile {
|
||||
url: string;
|
||||
@Column()
|
||||
file_name: string;
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
@CreateDateColumn()
|
||||
created_at: Date;
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
updated_by: string | null;
|
||||
@UpdateDateColumn()
|
||||
updated_at: Date;
|
||||
|
||||
@ -17,15 +17,21 @@ export class Term {
|
||||
@Column()
|
||||
version: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
created_by: string;
|
||||
@Column({ nullable: true, type: 'datetime' })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true })
|
||||
updated_by?: string;
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({ default: () => "datetime('now', 'localtime')" }) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: 'datetime',
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user