This commit is contained in:
oura.a 2023-10-19 16:33:11 +09:00
commit 215ccbee0c
112 changed files with 4398 additions and 1416 deletions

View File

@ -58,6 +58,7 @@ jobs:
npm run test npm run test
env: env:
JWT_PUBLIC_KEY: $(token-public-key) JWT_PUBLIC_KEY: $(token-public-key)
JWT_PRIVATE_KEY: $(token-private-key)
SENDGRID_API_KEY: $(sendgrid-api-key) SENDGRID_API_KEY: $(sendgrid-api-key)
NOTIFICATION_HUB_NAME: $(notification-hub-name) NOTIFICATION_HUB_NAME: $(notification-hub-name)
NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string) NOTIFICATION_HUB_CONNECT_STRING: $(notification-hub-connect-string)
@ -73,6 +74,12 @@ jobs:
ADB2C_TENANT_ID: $(adb2c-tenant-id) ADB2C_TENANT_ID: $(adb2c-tenant-id)
ADB2C_CLIENT_ID: $(adb2c-client-id) ADB2C_CLIENT_ID: $(adb2c-client-id)
ADB2C_CLIENT_SECRET: $(adb2c-client-secret) 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 - task: Docker@0
displayName: build displayName: build
inputs: inputs:

View File

@ -1,5 +1,6 @@
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import TopPage from "pages/TopPage"; import TopPage from "pages/TopPage";
import AuthPage from "pages/AuthPage";
import LoginPage from "pages/LoginPage"; import LoginPage from "pages/LoginPage";
import SamplePage from "pages/SamplePage"; import SamplePage from "pages/SamplePage";
import { AuthErrorPage } from "pages/ErrorPage"; import { AuthErrorPage } from "pages/ErrorPage";
@ -20,18 +21,21 @@ import WorkflowPage from "pages/WorkflowPage";
import TypistGroupSettingPage from "pages/TypistGroupSettingPage"; import TypistGroupSettingPage from "pages/TypistGroupSettingPage";
import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage"; import WorktypeIdSettingPage from "pages/WorkTypeIdSettingPage";
import AccountPage from "pages/AccountPage"; import AccountPage from "pages/AccountPage";
import AcceptToUsePage from "pages/TermsPage";
import { TemplateFilePage } from "pages/TemplateFilePage"; import { TemplateFilePage } from "pages/TemplateFilePage";
import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess"; import { AccountDeleteSuccess } from "pages/AccountPage/accountDeleteSuccess";
const AppRouter: React.FC = () => ( const AppRouter: React.FC = () => (
<Routes> <Routes>
<Route path="/" element={<TopPage />} /> <Route path="/" element={<TopPage />} />
<Route path="/auth" element={<AuthPage />} />
<Route path="/login" element={<LoginPage />} /> <Route path="/login" element={<LoginPage />} />
<Route path="/authError" element={<AuthErrorPage />} /> <Route path="/authError" element={<AuthErrorPage />} />
<Route <Route
path="/signup" path="/signup"
element={<SignupPage completeTo="/signup/complete" />} element={<SignupPage completeTo="/signup/complete" />}
/> />
<Route path="/terms" element={<AcceptToUsePage />} />
<Route path="/signup/complete" element={<SignupCompletePage />} /> <Route path="/signup/complete" element={<SignupCompletePage />} />
<Route path="/mail-confirm/" element={<VerifyPage />} /> <Route path="/mail-confirm/" element={<VerifyPage />} />
<Route path="/mail-confirm/user" element={<UserVerifyPage />} /> <Route path="/mail-confirm/user" element={<UserVerifyPage />} />

View File

@ -127,7 +127,7 @@ export interface AllocatableLicenseInfo {
* @type {string} * @type {string}
* @memberof AllocatableLicenseInfo * @memberof AllocatableLicenseInfo
*/ */
'expiryDate': string; 'expiryDate'?: string;
} }
/** /**
* *
@ -2561,6 +2561,44 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions, 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 * @summary
@ -3340,6 +3378,17 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccountAndData(deleteAccountRequest, options); const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAccountAndData(deleteAccountRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); 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 * @summary
@ -3616,6 +3665,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise<object> { deleteAccountAndData(deleteAccountRequest: DeleteAccountRequest, options?: any): AxiosPromise<object> {
return localVarFp.deleteAccountAndData(deleteAccountRequest, options).then((request) => request(axios, basePath)); 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 * @summary
@ -3888,6 +3947,18 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).deleteAccountAndData(deleteAccountRequest, options).then((request) => request(this.axios, this.basePath)); 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 * @summary

View File

@ -18,6 +18,7 @@ import worktype from "features/workflow/worktype/worktypeSlice";
import account from "features/account/accountSlice"; import account from "features/account/accountSlice";
import template from "features/workflow/template/templateSlice"; import template from "features/workflow/template/templateSlice";
import workflow from "features/workflow/workflowSlice"; import workflow from "features/workflow/workflowSlice";
import terms from "features/terms/termsSlice";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
@ -40,6 +41,7 @@ export const store = configureStore({
account, account,
template, template,
workflow, workflow,
terms,
}, },
}); });

View File

@ -32,6 +32,7 @@ export const errorCodes = [
"E010206", // DBのTierが想定外の値エラー "E010206", // DBのTierが想定外の値エラー
"E010207", // ユーザーのRole変更不可エラー "E010207", // ユーザーのRole変更不可エラー
"E010208", // ユーザーの暗号化パスワード不足エラー "E010208", // ユーザーの暗号化パスワード不足エラー
"E010209", // ユーザーの同意済み利用規約バージョンが最新でないエラー
"E010301", // メールアドレス登録済みエラー "E010301", // メールアドレス登録済みエラー
"E010302", // authorId重複エラー "E010302", // authorId重複エラー
"E010401", // PONumber重複エラー "E010401", // PONumber重複エラー
@ -55,6 +56,7 @@ export const errorCodes = [
"E011001", // ワークタイプ重複エラー "E011001", // ワークタイプ重複エラー
"E011002", // ワークタイプ登録上限超過エラー "E011002", // ワークタイプ登録上限超過エラー
"E011003", // ワークタイプ不在エラー "E011003", // ワークタイプ不在エラー
"E011004", // ワークタイプ使用中エラー
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー "E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
"E013002", // ワークフロー不在エラー "E013002", // ワークフロー不在エラー
] as const; ] as const;

View File

@ -81,3 +81,21 @@ const isErrorResponse = (error: unknown): error is ErrorResponse => {
const isErrorCode = (errorCode: string): errorCode is ErrorCodeType => const isErrorCode = (errorCode: string): errorCode is ErrorCodeType =>
errorCodes.includes(errorCode as 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;
};

View File

@ -5,7 +5,7 @@ export const msalConfig: Configuration = {
clientId: import.meta.env.VITE_B2C_CLIENTID, clientId: import.meta.env.VITE_B2C_CLIENTID,
authority: import.meta.env.VITE_B2C_AUTHORITY, authority: import.meta.env.VITE_B2C_AUTHORITY,
knownAuthorities: [import.meta.env.VITE_B2C_KNOWNAUTHORITIES], knownAuthorities: [import.meta.env.VITE_B2C_KNOWNAUTHORITIES],
redirectUri: `${globalThis.location.origin}/login`, redirectUri: `${globalThis.location.origin}/auth`,
navigateToLoginRequestUrl: false, navigateToLoginRequestUrl: false,
}, },
cache: { cache: {

View File

@ -62,3 +62,16 @@ export const isIdToken = (arg: any): arg is IdToken => {
return true; 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;
};

View File

@ -1,17 +1,26 @@
import { createSlice } from "@reduxjs/toolkit"; import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { LoginState } from "./state"; import { LoginState } from "./state";
import { loginAsync } from "./operations"; import { loginAsync } from "./operations";
const initialState: LoginState = { const initialState: LoginState = {
apps: { apps: {
LoginApiCallStatus: "none", LoginApiCallStatus: "none",
localStorageKeyforIdToken: null,
}, },
}; };
export const loginSlice = createSlice({ export const loginSlice = createSlice({
name: "login", name: "login",
initialState, initialState,
reducers: {}, reducers: {
changeLocalStorageKeyforIdToken: (
state,
action: PayloadAction<{ localStorageKeyforIdToken: string }>
) => {
const { localStorageKeyforIdToken } = action.payload;
state.apps.localStorageKeyforIdToken = localStorageKeyforIdToken;
},
},
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(loginAsync.pending, (state) => { builder.addCase(loginAsync.pending, (state) => {
state.apps.LoginApiCallStatus = "pending"; state.apps.LoginApiCallStatus = "pending";
@ -25,4 +34,5 @@ export const loginSlice = createSlice({
}, },
}); });
export const { changeLocalStorageKeyforIdToken } = loginSlice.actions;
export default loginSlice.reducer; export default loginSlice.reducer;

View File

@ -3,6 +3,7 @@ import type { RootState } from "app/store";
import { setToken } from "features/auth/authSlice"; import { setToken } from "features/auth/authSlice";
import { AuthApi } from "../../api/api"; import { AuthApi } from "../../api/api";
import { Configuration } from "../../api/configuration"; import { Configuration } from "../../api/configuration";
import { ErrorObject, createErrorObject } from "../../common/errors";
export const loginAsync = createAsyncThunk< export const loginAsync = createAsyncThunk<
{ {
@ -14,7 +15,7 @@ export const loginAsync = createAsyncThunk<
{ {
// rejectした時の返却値の型 // rejectした時の返却値の型
rejectValue: { rejectValue: {
/* Empty Object */ error: ErrorObject;
}; };
} }
>("login/loginAsync", async (args, thunkApi) => { >("login/loginAsync", async (args, thunkApi) => {
@ -41,6 +42,8 @@ export const loginAsync = createAsyncThunk<
return {}; return {};
} catch (e) { } catch (e) {
return thunkApi.rejectWithValue({}); // e ⇒ errorObjectに変換"
const error = createErrorObject(e);
return thunkApi.rejectWithValue({ error });
} }
}); });

View File

@ -4,3 +4,7 @@ export const selectLoginApiCallStatus = (
state: RootState state: RootState
): "fulfilled" | "rejected" | "none" | "pending" => ): "fulfilled" | "rejected" | "none" | "pending" =>
state.login.apps.LoginApiCallStatus; state.login.apps.LoginApiCallStatus;
export const selectLocalStorageKeyforIdToken = (
state: RootState
): string | null => state.login.apps.localStorageKeyforIdToken;

View File

@ -4,4 +4,5 @@ export interface LoginState {
export interface Apps { export interface Apps {
LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending"; LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending";
localStorageKeyforIdToken: string | null;
} }

View File

@ -3,10 +3,12 @@ import type { RootState } from "app/store";
import { ErrorObject, createErrorObject } from "common/errors"; import { ErrorObject, createErrorObject } from "common/errors";
import { getTranslationID } from "translation"; import { getTranslationID } from "translation";
import { closeSnackbar, openSnackbar } from "features/ui/uiSlice"; import { closeSnackbar, openSnackbar } from "features/ui/uiSlice";
import { TERMS_DOCUMENT_TYPE } from "features/terms/constants";
import { import {
AccountsApi, AccountsApi,
CreateAccountRequest, CreateAccountRequest,
GetDealersResponse, GetDealersResponse,
TermsApi,
} from "../../api/api"; } from "../../api/api";
import { Configuration } from "../../api/configuration"; import { Configuration } from "../../api/configuration";
@ -93,3 +95,42 @@ export const getDealersAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error }); 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 });
}
});

View File

@ -72,3 +72,6 @@ export const selectSelectedDealer = (state: RootState) => {
const { dealer } = state.signup.apps; const { dealer } = state.signup.apps;
return dealers.find((x: Dealer) => x.id === dealer); return dealers.find((x: Dealer) => x.id === dealer);
}; };
export const selectEulaVersion = (state: RootState) =>
state.signup.domain.eulaVersion;

View File

@ -1,6 +1,10 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { SignupState } from "./state"; import { SignupState } from "./state";
import { getDealersAsync, signupAsync } from "./operations"; import {
getDealersAsync,
getLatestEulaVersionAsync,
signupAsync,
} from "./operations";
const initialState: SignupState = { const initialState: SignupState = {
apps: { apps: {
@ -15,6 +19,7 @@ const initialState: SignupState = {
}, },
domain: { domain: {
dealers: [], dealers: [],
eulaVersion: "",
}, },
}; };
@ -74,6 +79,15 @@ export const signupSlice = createSlice({
builder.addCase(getDealersAsync.rejected, () => { 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 { export const {

View File

@ -18,4 +18,5 @@ export interface Apps {
export interface Domain { export interface Domain {
dealers: Dealer[]; dealers: Dealer[];
eulaVersion: string;
} }

View File

@ -0,0 +1,8 @@
/**
*
* @const {string[]}
*/
export const TERMS_DOCUMENT_TYPE = {
DPA: "DPA",
EULA: "EULA",
} as const;

View File

@ -0,0 +1,4 @@
export * from "./termsSlice";
export * from "./state";
export * from "./operations";
export * from "./selectors";

View 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 });
}
});

View 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;

View 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;
}

View 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;

View File

@ -87,7 +87,7 @@ export const userSlice = createSlice({
action: PayloadAction<{ authorId: string | undefined }> action: PayloadAction<{ authorId: string | undefined }>
) => { ) => {
const { authorId } = action.payload; const { authorId } = action.payload;
state.apps.addUser.authorId = authorId; state.apps.addUser.authorId = authorId?.toUpperCase();
}, },
changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => { changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => {
const { autoRenew } = action.payload; const { autoRenew } = action.payload;
@ -144,7 +144,7 @@ export const userSlice = createSlice({
state.apps.updateUser.name = user.name; state.apps.updateUser.name = user.name;
state.apps.updateUser.email = user.email; state.apps.updateUser.email = user.email;
state.apps.updateUser.role = user.role as RoleType; 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.encryption = user.encryption;
state.apps.updateUser.encryptionPassword = undefined; state.apps.updateUser.encryptionPassword = undefined;
state.apps.updateUser.prompt = user.prompt; state.apps.updateUser.prompt = user.prompt;
@ -156,7 +156,7 @@ export const userSlice = createSlice({
state.apps.selectedUser.name = user.name; state.apps.selectedUser.name = user.name;
state.apps.selectedUser.email = user.email; state.apps.selectedUser.email = user.email;
state.apps.selectedUser.role = user.role as RoleType; 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.encryption = user.encryption;
state.apps.selectedUser.encryptionPassword = undefined; state.apps.selectedUser.encryptionPassword = undefined;
state.apps.selectedUser.prompt = user.prompt; state.apps.selectedUser.prompt = user.prompt;
@ -175,7 +175,7 @@ export const userSlice = createSlice({
action: PayloadAction<{ authorId: string }> action: PayloadAction<{ authorId: string }>
) => { ) => {
const { authorId } = action.payload; const { authorId } = action.payload;
state.apps.updateUser.authorId = authorId; state.apps.updateUser.authorId = authorId.toUpperCase();
}, },
changeUpdateEncryption: ( changeUpdateEncryption: (
state, state,
@ -243,7 +243,8 @@ export const userSlice = createSlice({
state.apps.licenseAllocateUser.id = selectedUser.id; state.apps.licenseAllocateUser.id = selectedUser.id;
state.apps.licenseAllocateUser.name = selectedUser.name; state.apps.licenseAllocateUser.name = selectedUser.name;
state.apps.licenseAllocateUser.email = selectedUser.email; 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.licenseStatus = selectedUser.licenseStatus;
state.apps.licenseAllocateUser.expiration = selectedUser.expiration; state.apps.licenseAllocateUser.expiration = selectedUser.expiration;
state.apps.licenseAllocateUser.remaining = selectedUser.remaining; state.apps.licenseAllocateUser.remaining = selectedUser.remaining;

View File

@ -342,3 +342,75 @@ export const updateActiveWorktypeAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error }); 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 });
}
});

View 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;

View File

@ -1,30 +1,39 @@
import { useMsal } from "@azure/msal-react"; import { useMsal } from "@azure/msal-react";
import { AuthError } from "@azure/msal-browser";
import { AppDispatch } from "app/store"; import { AppDispatch } from "app/store";
import { isIdToken } from "common/token"; 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 Footer from "components/footer";
import Header from "components/header"; 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 { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { isErrorObject } from "common/errors";
const LoginPage: React.FC = (): JSX.Element => { const LoginPage: React.FC = (): JSX.Element => {
const { instance } = useMsal(); const { instance } = useMsal();
const dispatch: AppDispatch = useDispatch(); const dispatch: AppDispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [, i18n] = useTranslation(); const [, i18n] = useTranslation();
const status = useSelector(selectLoginApiCallStatus); const localStorageKeyforIdToken = useSelector(
selectLocalStorageKeyforIdToken
);
const login = useCallback( const tokenSet = useCallback(
async (idToken: string) => { async (idToken: string) => {
// ログイン処理呼び出し // ログイン処理呼び出し
const { meta } = await dispatch(loginAsync({ idToken })); const { meta, payload } = await dispatch(loginAsync({ idToken }));
// ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する // ログイン失敗した場合、B2Cをログアウトしてからエラーページに遷移する
if (meta.requestStatus === "rejected") { if (meta.requestStatus === "rejected") {
if (isErrorObject(payload)) {
// 未同意の規約がある場合は利用規約同意画面に遷移する
if (payload.error.code === "E010209") {
navigate("/terms");
return;
}
}
instance.logoutRedirect({ instance.logoutRedirect({
postLogoutRedirectUri: "/AuthError", postLogoutRedirectUri: "/AuthError",
}); });
@ -48,53 +57,26 @@ const LoginPage: React.FC = (): JSX.Element => {
[dispatch, i18n.language, instance, navigate] [dispatch, i18n.language, instance, navigate]
); );
// TODO 将来的にトークンの取得処理をoperations.ts側に移動させたい。useEffect内で非同期処理を行いたくない。
useEffect(() => { useEffect(() => {
if (status !== "none") { // AADB2Cのログイン画面とLoginPageを経由していない場合はトップページに遷移する
// ログイン処理で、何回か本画面が描画される契機があるが、認証処理は一度だけ実施すればよいため認証処理実行済みであれば何もしない if (!localStorageKeyforIdToken) {
navigate("/");
return; return;
} }
(async () => { (async () => {
try { // IDトークンの取得
const loginResult = await instance.handleRedirectPromise(); const idTokenString = localStorage.getItem(localStorageKeyforIdToken);
if (idTokenString) {
// eslint-disable-next-line const idTokenObject = JSON.parse(idTokenString);
console.log({ loginResult }); // TODO:loading画面から遷移できない事象の調査用ログ。事象解消後削除eslint-disable含めてする。 if (isIdToken(idTokenObject)) {
await tokenSet(idTokenObject.secret);
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("/");
}
} }
} }
})(); })();
}, [instance, login, navigate, status]); // 画面描画後のみ実行するため引数を設定しない
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<> <>

View File

@ -14,6 +14,7 @@ import {
selectEmail, selectEmail,
selectPassword, selectPassword,
selectSelectedDealer, selectSelectedDealer,
selectEulaVersion,
} from "../../features/signup/selectors"; } from "../../features/signup/selectors";
import { signupAsync } from "../../features/signup/operations"; import { signupAsync } from "../../features/signup/operations";
@ -27,6 +28,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
const adminMail = useSelector(selectEmail); const adminMail = useSelector(selectEmail);
const adminPassword = useSelector(selectPassword); const adminPassword = useSelector(selectPassword);
const dealer = useSelector(selectSelectedDealer); const dealer = useSelector(selectSelectedDealer);
const acceptedEulaVersion = useSelector(selectEulaVersion);
const onSubmit = useCallback(() => { const onSubmit = useCallback(() => {
dispatch( dispatch(
@ -37,7 +39,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
adminName, adminName,
adminMail, adminMail,
adminPassword, adminPassword,
acceptedEulaVersion: "", acceptedEulaVersion,
acceptedDpaVersion: "", acceptedDpaVersion: "",
token: "", token: "",
}) })
@ -50,6 +52,7 @@ const SignupConfirm: React.FC = (): JSX.Element => {
adminName, adminName,
adminMail, adminMail,
adminPassword, adminPassword,
acceptedEulaVersion,
]); ]);
return ( return (

View File

@ -24,7 +24,10 @@ import { useDispatch, useSelector } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { getTranslationID } from "translation"; import { getTranslationID } from "translation";
import styles from "styles/app.module.scss"; 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 { LANGUAGE_LIST } from "features/top/constants";
import { openSnackbar } from "features/ui"; import { openSnackbar } from "features/ui";
import { COUNTRY_LIST } from "./constants"; import { COUNTRY_LIST } from "./constants";
@ -84,6 +87,7 @@ const SignupInput: React.FC = (): JSX.Element => {
// 入力画面の初期化時の処理 // 入力画面の初期化時の処理
useEffect(() => { useEffect(() => {
dispatch(getDealersAsync()); dispatch(getDealersAsync());
dispatch(getLatestEulaVersionAsync());
}, [dispatch]); }, [dispatch]);
useEffect(() => { useEffect(() => {
@ -264,10 +268,9 @@ const SignupInput: React.FC = (): JSX.Element => {
/> />
{isPushCreateButton && hasErrorEmptyAdminName && ( {isPushCreateButton && hasErrorEmptyAdminName && (
<span className={styles.formError}> <span className={styles.formError}>
{" "} {` ${t(
{t(
getTranslationID("signupPage.message.inputEmptyError") getTranslationID("signupPage.message.inputEmptyError")
)} )}`}
</span> </span>
)} )}
</dd> </dd>
@ -369,8 +372,9 @@ const SignupInput: React.FC = (): JSX.Element => {
}} }}
> >
{t(getTranslationID("signupPage.label.termsLink"))} {t(getTranslationID("signupPage.label.termsLink"))}
</a>{" "} </a>
{t(getTranslationID("signupPage.label.termsLinkFor"))} <br /> {` ${t(getTranslationID("signupPage.label.termsLinkFor"))} `}
<br />
<label htmlFor="check-box"> <label htmlFor="check-box">
<input <input
id="check-box" id="check-box"

View 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;

View File

@ -200,7 +200,11 @@ export const UserAddPopup: React.FC<UserAddPopupProps> = (props) => {
className={styles.formInput} className={styles.formInput}
value={addUser.authorId ?? undefined} value={addUser.authorId ?? undefined}
onChange={(e) => { onChange={(e) => {
dispatch(changeAuthorId({ authorId: e.target.value })); dispatch(
changeAuthorId({
authorId: e.target.value.toUpperCase(),
})
);
}} }}
/> />
{isPushCreateButton && hasErrorEmptyAuthorId && ( {isPushCreateButton && hasErrorEmptyAuthorId && (

View File

@ -184,7 +184,9 @@ export const UserUpdatePopup: React.FC<UserUpdatePopupProps> = (props) => {
className={styles.formInput} className={styles.formInput}
onChange={(e) => { onChange={(e) => {
dispatch( dispatch(
changeUpdateAuthorId({ authorId: e.target.value }) changeUpdateAuthorId({
authorId: e.target.value.toUpperCase(),
})
); );
}} }}
/> />

View File

@ -1,7 +1,7 @@
import { UpdateTokenTimer } from "components/auth/updateTokenTimer"; import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
import Footer from "components/footer"; import Footer from "components/footer";
import Header from "components/header"; import Header from "components/header";
import React, { useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { getTranslationID } from "translation"; import { getTranslationID } from "translation";
import styles from "styles/app.module.scss"; import styles from "styles/app.module.scss";
import undo from "assets/images/undo.svg"; import undo from "assets/images/undo.svg";
@ -18,6 +18,7 @@ import {
selectIsLoading, selectIsLoading,
selectWorktypes, selectWorktypes,
selectActiveWorktypeId, selectActiveWorktypeId,
deleteWorktypeAsync,
} from "features/workflow/worktype"; } from "features/workflow/worktype";
import { AppDispatch } from "app/store"; import { AppDispatch } from "app/store";
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup"; import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
@ -86,6 +87,23 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedActiveWorktypeId]); }, [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 ( return (
<> <>
<AddWorktypeIdPopup <AddWorktypeIdPopup
@ -253,9 +271,10 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
</a> </a>
</li> </li>
<li> <li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a <a
className={`${styles.menuLink} ${styles.isActive}`} className={`${styles.menuLink} ${styles.isActive}`}
// onClick={} onClick={() => onDeleteWoktype(worktype.id)}
> >
{t(getTranslationID("common.label.delete"))} {t(getTranslationID("common.label.delete"))}
</a> </a>

View File

@ -2266,8 +2266,7 @@ tr.isSelected .menuInTable li a.isDisable {
} }
.formChange ul.chooseMember li input + label:hover, .formChange ul.chooseMember li input + label:hover,
.formChange ul.holdMember li input + label:hover { .formChange ul.holdMember li input + label:hover {
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
center;
background-size: 1.3rem; background-size: 1.3rem;
} }
.formChange ul.chooseMember li input:checked + label, .formChange ul.chooseMember li input:checked + label,
@ -2278,8 +2277,8 @@ tr.isSelected .menuInTable li a.isDisable {
} }
.formChange ul.chooseMember li input:checked + label:hover, .formChange ul.chooseMember li input:checked + label:hover,
.formChange ul.holdMember li input:checked + label:hover { .formChange ul.holdMember li input:checked + label:hover {
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
right center; center;
background-size: 1.3rem; background-size: 1.3rem;
} }
.formChange > p { .formChange > p {
@ -2432,8 +2431,7 @@ tr.isSelected .menuInTable li a.isDisable {
} }
.formChange ul.chooseMember li input + label:hover, .formChange ul.chooseMember li input + label:hover,
.formChange ul.holdMember li input + label:hover { .formChange ul.holdMember li input + label:hover {
background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center;
center;
background-size: 1.3rem; background-size: 1.3rem;
} }
.formChange ul.chooseMember li input:checked + label, .formChange ul.chooseMember li input:checked + label,
@ -2444,8 +2442,8 @@ tr.isSelected .menuInTable li a.isDisable {
} }
.formChange ul.chooseMember li input:checked + label:hover, .formChange ul.chooseMember li input:checked + label:hover,
.formChange ul.holdMember li input:checked + label:hover { .formChange ul.holdMember li input:checked + label:hover {
background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right
right center; center;
background-size: 1.3rem; background-size: 1.3rem;
} }
.formChange > p { .formChange > p {

View File

@ -429,7 +429,8 @@
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" "updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
"worktypeInUseError": "(de)このWorktype IDはルーティングルールで使用されているため削除できません。"
} }
}, },
"templateFilePage": { "templateFilePage": {
@ -499,13 +500,14 @@
"backToTopPageLink": "(de)Back to TOP Page" "backToTopPageLink": "(de)Back to TOP Page"
} }
}, },
"AgreeToUsePage": { "termsPage": {
"label": { "label": {
"title": "(de)Terms of Use has updated. Please confirm again.", "title": "(de)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(de)Click here to read the terms of use.", "linkOfEula": "(de)Click here to read the terms of use.",
"linkOfDpa": "(de)Click here to read the terms of use.", "linkOfDpa": "(de)Click here to read the terms of use.",
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.", "checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
"forOdds": "(de)for ODDS." "forOdds": "(de)for ODDS.",
"button": "(de)Continue"
} }
} }
} }

View File

@ -429,7 +429,8 @@
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" "updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
"worktypeInUseError": "このWorktype IDはルーティングルールで使用されているため削除できません。"
} }
}, },
"templateFilePage": { "templateFilePage": {
@ -499,13 +500,14 @@
"backToTopPageLink": "Back to TOP Page" "backToTopPageLink": "Back to TOP Page"
} }
}, },
"AgreeToUsePage": { "termsPage": {
"label": { "label": {
"title": "Terms of Use has updated. Please confirm again.", "title": "Terms of Use has updated. Please confirm again.",
"linkOfEula": "Click here to read the terms of use.", "linkOfEula": "Click here to read the terms of use.",
"linkOfDpa": "Click here to read the terms of use.", "linkOfDpa": "Click here to read the terms of use.",
"checkBoxForConsent": "Yes, I agree to the terms of use.", "checkBoxForConsent": "Yes, I agree to the terms of use.",
"forOdds": "for ODDS." "forOdds": "for ODDS.",
"button": "Continue"
} }
} }
} }

View File

@ -429,7 +429,8 @@
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" "updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
"worktypeInUseError": "(es)このWorktype IDはルーティングルールで使用されているため削除できません。"
} }
}, },
"templateFilePage": { "templateFilePage": {
@ -499,13 +500,14 @@
"backToTopPageLink": "(es)Back to TOP Page" "backToTopPageLink": "(es)Back to TOP Page"
} }
}, },
"AgreeToUsePage": { "termsPage": {
"label": { "label": {
"title": "(es)Terms of Use has updated. Please confirm again.", "title": "(es)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(es)Click here to read the terms of use.", "linkOfEula": "(es)Click here to read the terms of use.",
"linkOfDpa": "(es)Click here to read the terms of use.", "linkOfDpa": "(es)Click here to read the terms of use.",
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.", "checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
"forOdds": "(es)for ODDS." "forOdds": "(es)for ODDS.",
"button": "(es)Continue"
} }
} }
} }

View File

@ -429,7 +429,8 @@
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。",
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください",
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください",
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください" "updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください",
"worktypeInUseError": "(fr)このWorktype IDはルーティングルールで使用されているため削除できません。"
} }
}, },
"templateFilePage": { "templateFilePage": {
@ -499,13 +500,14 @@
"backToTopPageLink": "(fr)Back to TOP Page" "backToTopPageLink": "(fr)Back to TOP Page"
} }
}, },
"AgreeToUsePage": { "termsPage": {
"label": { "label": {
"title": "(fr)Terms of Use has updated. Please confirm again.", "title": "(fr)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(fr)Click here to read the terms of use.", "linkOfEula": "(fr)Click here to read the terms of use.",
"linkOfDpa": "(fr)Click here to read the terms of use.", "linkOfDpa": "(fr)Click here to read the terms of use.",
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.", "checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
"forOdds": "(fr)for ODDS." "forOdds": "(fr)for ODDS.",
"button": "(fr)Continue"
} }
} }
} }

View File

@ -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;

View File

@ -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;

View File

@ -990,6 +990,59 @@
"security": [{ "bearer": [] }] "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": { "/accounts/worktypes/{id}/option-items": {
"get": { "get": {
"operationId": "getOptionItems", "operationId": "getOptionItems",
@ -3706,6 +3759,7 @@
"required": ["worktypeId"] "required": ["worktypeId"]
}, },
"UpdateWorktypeResponse": { "type": "object", "properties": {} }, "UpdateWorktypeResponse": { "type": "object", "properties": {} },
"DeleteWorktypeResponse": { "type": "object", "properties": {} },
"GetWorktypeOptionItem": { "GetWorktypeOptionItem": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -4411,7 +4465,7 @@
"licenseId": { "type": "number" }, "licenseId": { "type": "number" },
"expiryDate": { "format": "date-time", "type": "string" } "expiryDate": { "format": "date-time", "type": "string" }
}, },
"required": ["licenseId", "expiryDate"] "required": ["licenseId"]
}, },
"GetAllocatableLicensesResponse": { "GetAllocatableLicensesResponse": {
"type": "object", "type": "object",

View File

@ -57,6 +57,7 @@ export const ErrorCodes = [
'E011001', // ワークタイプ重複エラー 'E011001', // ワークタイプ重複エラー
'E011002', // ワークタイプ登録上限超過エラー 'E011002', // ワークタイプ登録上限超過エラー
'E011003', // ワークタイプ不在エラー 'E011003', // ワークタイプ不在エラー
'E011004', // ワークタイプ使用中エラー
'E012001', // テンプレートファイル不在エラー 'E012001', // テンプレートファイル不在エラー
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー 'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
'E013002', // ワークフロー不在エラー 'E013002', // ワークフロー不在エラー

View File

@ -46,6 +46,7 @@ export const errors: Errors = {
E011001: 'This WorkTypeID already used Error', E011001: 'This WorkTypeID already used Error',
E011002: 'WorkTypeID create limit exceeded Error', E011002: 'WorkTypeID create limit exceeded Error',
E011003: 'WorkTypeID not found Error', E011003: 'WorkTypeID not found Error',
E011004: 'WorkTypeID is in use Error',
E012001: 'Template file not found Error', E012001: 'Template file not found Error',
E013001: 'AuthorId and WorktypeId pair already exists Error', E013001: 'AuthorId and WorktypeId pair already exists Error',
E013002: 'Workflow not found Error', E013002: 'Workflow not found Error',

View File

@ -92,13 +92,15 @@ export class RoleGuard implements CanActivate {
* @returns true/false * @returns true/false
*/ */
checkRole(role: string): boolean { checkRole(role: string): boolean {
const { roles } = this.settings; const settings = this.settings;
if (!settings || !settings.roles) {
return true;
}
const userRoles = role.split(' '); const userRoles = role.split(' ');
// Role毎にAccessTokenの権限チェックを行う // Role毎にAccessTokenの権限チェックを行う
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < settings.roles.length; i++) {
const role = roles[i]; const role = settings.roles[i];
let isValid = false; let isValid = false;
if (Array.isArray(role)) { if (Array.isArray(role)) {
isValid = role.every((x) => userRoles.includes(x)); isValid = role.every((x) => userRoles.includes(x));
@ -172,9 +174,12 @@ export class RoleGuard implements CanActivate {
* @returns true/false * @returns true/false
*/ */
checkTier(tier: number): boolean { checkTier(tier: number): boolean {
const { tiers } = this.settings; const settings = this.settings;
if (!settings || !settings.tiers) {
return true;
}
// 宣言された階層中にパラメータの内容が含まれていればtrue // 宣言された階層中にパラメータの内容が含まれていればtrue
return tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]); return settings.tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
} }
} }

View File

@ -132,7 +132,7 @@ export const getPrivateKey = (configService: ConfigService): string => {
return ( return (
// 開発環境用に改行コードを置換する // 開発環境用に改行コードを置換する
// 本番環境では\\nが含まれないため、置換が行われない想定 // 本番環境では\\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 ( return (
// 開発環境用に改行コードを置換する // 開発環境用に改行コードを置換する
// 本番環境では\\nが含まれないため、置換が行われない想定 // 本番環境では\\nが含まれないため、置換が行われない想定
configService.get<string>('JWT_PUBLIC_KEY')?.replace(/\\n/g, '\n') ?? '' configService.getOrThrow<string>('JWT_PUBLIC_KEY').replace(/\\n/g, '\n')
); );
}; };

View File

@ -15,10 +15,9 @@ export const makePassword = (): string => {
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる // autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
let valid = false; let valid = false;
let autoGeneratedPassword: string; let autoGeneratedPassword: string = '';
while (!valid) { while (!valid) {
autoGeneratedPassword = '';
// パスワードをランダムに決定 // パスワードをランダムに決定
while (autoGeneratedPassword.length < passLength) { while (autoGeneratedPassword.length < passLength) {
// 上で決定したcharsの中からランダムに1文字ずつ追加 // 上で決定したcharsの中からランダムに1文字ずつ追加

View File

@ -3,6 +3,7 @@ import { DataSource } from 'typeorm';
import { User, UserArchive } from '../../repositories/users/entity/user.entity'; import { User, UserArchive } from '../../repositories/users/entity/user.entity';
import { Account } from '../../repositories/accounts/entity/account.entity'; import { Account } from '../../repositories/accounts/entity/account.entity';
import { ADMIN_ROLES, USER_ROLES } from '../../constants'; import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { License } from '../../repositories/licenses/entity/license.entity';
type InitialTestDBState = { type InitialTestDBState = {
tier1Accounts: { account: Account; users: User[] }[]; tier1Accounts: { account: Account; users: User[] }[];
@ -57,11 +58,11 @@ export const makeHierarchicalAccounts = async (
} }
// 第2階層を作成 // 第2階層を作成
{ {
const { account: tier1 } = state.tier1Accounts.slice().shift(); const tier1 = state.tier1Accounts.slice().shift();
{ {
const { account, admin } = await makeTestAccount(datasource, { const { account, admin } = await makeTestAccount(datasource, {
tier: 2, tier: 2,
parent_account_id: tier1.id, parent_account_id: tier1?.account.id,
company_name: 'OMDS_US', company_name: 'OMDS_US',
}); });
state.tier2Accounts.push({ state.tier2Accounts.push({
@ -72,7 +73,7 @@ export const makeHierarchicalAccounts = async (
{ {
const { account, admin } = await makeTestAccount(datasource, { const { account, admin } = await makeTestAccount(datasource, {
tier: 2, tier: 2,
parent_account_id: tier1.id, parent_account_id: tier1?.account.id,
company_name: 'OMDS_EU', company_name: 'OMDS_EU',
}); });
state.tier2Accounts.push({ state.tier2Accounts.push({
@ -201,7 +202,7 @@ export const makeTestAccount = async (
} }
// Accountの管理者を設定する // Accountの管理者を設定する
let secondaryAdminUserId = null; let secondaryAdminUserId: number | null = null;
if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) { if (isPrimaryAdminNotExist && !isSecondaryAdminNotExist) {
secondaryAdminUserId = userId; secondaryAdminUserId = userId;
} }
@ -224,6 +225,9 @@ export const makeTestAccount = async (
id: userId, id: userId,
}, },
}); });
if (!account || !admin) {
throw new Error('Unexpected null');
}
return { return {
account: account, account: account,
@ -263,7 +267,9 @@ export const makeTestSimpleAccount = async (
id: result.id, id: result.id,
}, },
}); });
if (!account) {
throw new Error('Unexpected null');
}
return account; return account;
}; };
@ -299,11 +305,15 @@ export const makeTestUser = async (
}); });
const result = identifiers.pop() as User; const result = identifiers.pop() as User;
return await datasource.getRepository(User).findOne({ const user = await datasource.getRepository(User).findOne({
where: { where: {
id: result.id, id: result.id,
}, },
}); });
if (!user) {
throw new Error('Unexpected null');
}
return user;
}; };
/** /**
@ -312,7 +322,10 @@ export const makeTestUser = async (
* @param id ID * @param id ID
* @returns * @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({ return await dataSource.getRepository(Account).findOne({
where: { id: id }, where: { id: id },
}); });
@ -353,7 +366,7 @@ export const getUserFromExternalId = async (
export const getUser = async ( export const getUser = async (
datasource: DataSource, datasource: DataSource,
id: number, id: number,
): Promise<User> => { ): Promise<User | null> => {
const user = await datasource.getRepository(User).findOne({ const user = await datasource.getRepository(User).findOne({
where: { where: {
id: id, id: id,
@ -381,3 +394,14 @@ export const getUserArchive = async (
): Promise<UserArchive[]> => { ): Promise<UserArchive[]> => {
return await dataSource.getRepository(UserArchive).find(); 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;
};

View File

@ -244,7 +244,7 @@ export const OPTION_ITEM_VALUE_TYPE = {
* @const {string[]} * @const {string[]}
*/ */
export const ADB2C_SIGN_IN_TYPE = { export const ADB2C_SIGN_IN_TYPE = {
EAMILADDRESS: 'emailAddress', EMAILADDRESS: 'emailAddress',
} as const; } as const;
/** /**

View File

@ -66,6 +66,8 @@ import {
GetAuthorsResponse, GetAuthorsResponse,
GetAccountInfoMinimalAccessRequest, GetAccountInfoMinimalAccessRequest,
GetAccountInfoMinimalAccessResponse, GetAccountInfoMinimalAccessResponse,
DeleteWorktypeRequestParam,
DeleteWorktypeResponse,
} from './types/types'; } from './types/types';
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants'; import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
import { AuthGuard } from '../../common/guards/auth/authguards'; import { AuthGuard } from '../../common/guards/auth/authguards';
@ -198,14 +200,26 @@ export class AccountsController {
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get('me') @Get('me')
async getMyAccount(@Req() req: Request): Promise<GetMyAccountResponse> { async getMyAccount(@Req() req: Request): Promise<GetMyAccountResponse> {
// アクセストークン取得 const accessToken = retrieveAuthorizationToken(req) as string;
const accessToken = retrieveAuthorizationToken(req); if (!accessToken) {
const payload = jwt.decode(accessToken, { json: true }) as AccessToken; throw new HttpException(
const context = makeContext(payload.userId); 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取得処理 //アカウントID取得処理
const accountInfo = await this.accountService.getAccountInfo( const accountInfo = await this.accountService.getAccountInfo(
context, context,
payload.userId, userId,
); );
return accountInfo; return accountInfo;
} }
@ -235,8 +249,21 @@ export class AccountsController {
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get('authors') @Get('authors')
async getAuthors(@Req() req: Request): Promise<GetAuthorsResponse> { async getAuthors(@Req() req: Request): Promise<GetAuthorsResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
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); const context = makeContext(userId);
const authors = await this.accountService.getAuthors(context, userId); const authors = await this.accountService.getAuthors(context, userId);
@ -268,10 +295,23 @@ export class AccountsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('typists') @Get('typists')
async getTypists(@Req() req: Request): Promise<GetTypistsResponse> { async getTypists(@Req() req: Request): Promise<GetTypistsResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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 typists = await this.accountService.getTypists(payload.userId); const typists = await this.accountService.getTypists(userId);
return { typists }; return { typists };
} }
@ -300,12 +340,23 @@ export class AccountsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('typist-groups') @Get('typist-groups')
async getTypistGroups(@Req() req: Request): Promise<GetTypistGroupsResponse> { async getTypistGroups(@Req() req: Request): Promise<GetTypistGroupsResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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 typistGroups = await this.accountService.getTypistGroups( const typistGroups = await this.accountService.getTypistGroups(userId);
payload.userId,
);
return { typistGroups }; return { typistGroups };
} }
@ -346,8 +397,22 @@ export class AccountsController {
const { typistGroupId } = param; 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); const context = makeContext(userId);
@ -395,8 +460,22 @@ export class AccountsController {
): Promise<CreateTypistGroupResponse> { ): Promise<CreateTypistGroupResponse> {
const { typistGroupName, typistIds } = body; 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); const context = makeContext(userId);
await this.accountService.createTypistGroup( await this.accountService.createTypistGroup(
context, context,
@ -445,8 +524,22 @@ export class AccountsController {
const { typistGroupId } = param; 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); const context = makeContext(userId);
@ -496,10 +589,24 @@ export class AccountsController {
@Body() body: CreatePartnerAccountRequest, @Body() body: CreatePartnerAccountRequest,
): Promise<CreatePartnerAccountResponse> { ): Promise<CreatePartnerAccountResponse> {
const { companyName, country, email, adminName } = body; 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( await this.accountService.createPartnerAccount(
context, context,
@ -507,8 +614,8 @@ export class AccountsController {
country, country,
email, email,
adminName, adminName,
payload.userId, userId,
payload.tier, tier,
); );
return {}; return {};
@ -624,15 +731,28 @@ export class AccountsController {
): Promise<IssueLicenseResponse> { ): Promise<IssueLicenseResponse> {
const { orderedAccountId, poNumber } = body; const { orderedAccountId, poNumber } = body;
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const accessToken = jwt.decode(token, { 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, tier } = decodedAccessToken as AccessToken;
const context = makeContext(accessToken.userId); const context = makeContext(userId);
await this.accountService.issueLicense( await this.accountService.issueLicense(
context, context,
orderedAccountId, orderedAccountId,
accessToken.userId, userId,
accessToken.tier, tier,
poNumber, poNumber,
); );
return {}; return {};
@ -692,14 +812,27 @@ export class AccountsController {
@Req() req: Request, @Req() req: Request,
@Body() body: CancelIssueRequest, @Body() body: CancelIssueRequest,
): Promise<CancelIssueResponse> { ): Promise<CancelIssueResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = jwt.decode(token, { 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(payload.userId); const context = makeContext(userId);
await this.accountService.cancelIssue( await this.accountService.cancelIssue(
context, context,
payload.userId, userId,
body.poNumber, body.poNumber,
body.orderedAccountId, body.orderedAccountId,
); );
@ -727,8 +860,21 @@ export class AccountsController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
async getWorktypes(@Req() req: Request): Promise<GetWorktypesResponse> { async getWorktypes(@Req() req: Request): Promise<GetWorktypesResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const { userId } = jwt.decode(token, { 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); const context = makeContext(userId);
const worktypes = await this.accountService.getWorktypes(context, userId); const worktypes = await this.accountService.getWorktypes(context, userId);
@ -766,8 +912,22 @@ export class AccountsController {
@Body() body: CreateWorktypesRequest, @Body() body: CreateWorktypesRequest,
): Promise<CreateWorktypeResponse> { ): Promise<CreateWorktypeResponse> {
const { worktypeId, description } = body; 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); const context = makeContext(userId);
await this.accountService.createWorktype( await this.accountService.createWorktype(
@ -812,8 +972,22 @@ export class AccountsController {
): Promise<UpdateWorktypeResponse> { ): Promise<UpdateWorktypeResponse> {
const { worktypeId, description } = body; const { worktypeId, description } = body;
const { id } = param; 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); const context = makeContext(userId);
@ -828,6 +1002,59 @@ export class AccountsController {
return {}; 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') @Get('/worktypes/:id/option-items')
@ApiResponse({ @ApiResponse({
status: HttpStatus.OK, status: HttpStatus.OK,
@ -858,8 +1085,22 @@ export class AccountsController {
@Param() param: GetOptionItemsRequestParam, @Param() param: GetOptionItemsRequestParam,
): Promise<GetOptionItemsResponse> { ): Promise<GetOptionItemsResponse> {
const { id } = param; 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); const context = makeContext(userId);
@ -904,8 +1145,22 @@ export class AccountsController {
): Promise<UpdateOptionItemsResponse> { ): Promise<UpdateOptionItemsResponse> {
const { optionItems } = body; const { optionItems } = body;
const { id } = param; 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); const context = makeContext(userId);
@ -949,8 +1204,22 @@ export class AccountsController {
@Body() body: PostActiveWorktypeRequest, @Body() body: PostActiveWorktypeRequest,
): Promise<PostActiveWorktypeResponse> { ): Promise<PostActiveWorktypeResponse> {
const { id } = body; 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); const context = makeContext(userId);
@ -993,8 +1262,22 @@ export class AccountsController {
@Query() query: GetPartnersRequest, @Query() query: GetPartnersRequest,
): Promise<GetPartnersResponse> { ): Promise<GetPartnersResponse> {
const { limit, offset } = query; 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 context = makeContext(userId);
const response = await this.accountService.getPartners( const response = await this.accountService.getPartners(
@ -1046,8 +1329,22 @@ export class AccountsController {
primaryAdminUserId, primaryAdminUserId,
secondryAdminUserId, secondryAdminUserId,
} = body; } = 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); const context = makeContext(userId);
await this.accountService.updateAccountInfo( await this.accountService.updateAccountInfo(
@ -1060,7 +1357,7 @@ export class AccountsController {
secondryAdminUserId, secondryAdminUserId,
); );
return; return {};
} }
@Post('/delete') @Post('/delete')
@ -1092,12 +1389,26 @@ export class AccountsController {
@Body() body: DeleteAccountRequest, @Body() body: DeleteAccountRequest,
): Promise<DeleteAccountResponse> { ): Promise<DeleteAccountResponse> {
const { accountId } = body; 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); const context = makeContext(userId);
await this.accountService.deleteAccountAndData(context, userId, accountId); await this.accountService.deleteAccountAndData(context, userId, accountId);
return; return {};
} }
@Post('/minimal-access') @Post('/minimal-access')

View File

@ -33,6 +33,7 @@ import {
GetPartnersResponse, GetPartnersResponse,
PostWorktypeOptionItem, PostWorktypeOptionItem,
Author, Author,
Partner,
} from './types/types'; } from './types/types';
import { import {
DateWithZeroTime, DateWithZeroTime,
@ -65,12 +66,15 @@ import {
import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service'; import { WorktypesRepositoryService } from '../../repositories/worktypes/worktypes.repository.service';
import { import {
WorktypeIdAlreadyExistsError, WorktypeIdAlreadyExistsError,
WorktypeIdInUseError,
WorktypeIdMaxCountError, WorktypeIdMaxCountError,
WorktypeIdNotFoundError, WorktypeIdNotFoundError,
} from '../../repositories/worktypes/errors/types'; } from '../../repositories/worktypes/errors/types';
@Injectable() @Injectable()
export class AccountsService { export class AccountsService {
private readonly mailFrom =
this.configService.getOrThrow<string>('MAIL_FROM');
constructor( constructor(
private readonly accountRepository: AccountsRepositoryService, private readonly accountRepository: AccountsRepositoryService,
private readonly licensesRepository: LicensesRepositoryService, private readonly licensesRepository: LicensesRepositoryService,
@ -256,9 +260,6 @@ export class AccountsService {
} }
try { try {
// メールの送信元を取得
const from = this.configService.get<string>('MAIL_FROM') ?? '';
// メールの内容を構成 // メールの内容を構成
const { subject, text, html } = const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirm( await this.sendgridService.createMailContentFromEmailConfirm(
@ -272,7 +273,7 @@ export class AccountsService {
await this.sendgridService.sendMail( await this.sendgridService.sendMail(
context, context,
email, email,
from, this.mailFrom,
subject, subject,
text, text,
html, html,
@ -393,7 +394,7 @@ export class AccountsService {
userInfo.account_id, userInfo.account_id,
); );
let parentInfo: Account; let parentInfo: Account | undefined;
if (accountInfo.parent_account_id) { if (accountInfo.parent_account_id) {
parentInfo = await this.accountRepository.findAccountById( parentInfo = await this.accountRepository.findAccountById(
accountInfo.parent_account_id, accountInfo.parent_account_id,
@ -480,14 +481,20 @@ export class AccountsService {
const { account_id } = await this.usersRepository.findUserByExternalId( const { account_id } = await this.usersRepository.findUserByExternalId(
externalId, externalId,
); );
const userGroup = await this.userGroupsRepository.getTypistGroup( const { name, userGroupMembers } =
account_id, await this.userGroupsRepository.getTypistGroup(
typistGroupId, account_id,
); typistGroupId,
);
if (!userGroupMembers) {
throw new TypistGroupNotExistError(
`Typist Group is not exist. typistGroupId: ${typistGroupId}`,
);
}
return { return {
typistGroupName: userGroup.name, typistGroupName: name,
typistIds: userGroup.userGroupMembers.map((x) => x.user_id), typistIds: userGroupMembers.map((x) => x.user_id),
}; };
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
@ -540,6 +547,11 @@ export class AccountsService {
const typists = typistUsers.map((x) => { const typists = typistUsers.map((x) => {
const user = adb2cUsers.find((adb2c) => adb2c.id === x.external_id); 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 { return {
id: x.id, id: x.id,
name: user.displayName, name: user.displayName,
@ -585,6 +597,11 @@ export class AccountsService {
); );
const authors = authorUsers.map((x) => { 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 { return {
id: x.id, id: x.id,
authorId: x.author_id, authorId: x.author_id,
@ -700,8 +717,8 @@ export class AccountsService {
creatorAccountTier + 1, creatorAccountTier + 1,
externalUser.sub, externalUser.sub,
USER_ROLES.NONE, USER_ROLES.NONE,
null, undefined,
null, undefined,
); );
account = newAccount; account = newAccount;
user = adminUser; user = adminUser;
@ -742,7 +759,6 @@ export class AccountsService {
} }
try { try {
const from = this.configService.get<string>('MAIL_FROM') || '';
const { subject, text, html } = const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
account.id, account.id,
@ -752,7 +768,7 @@ export class AccountsService {
await this.sendgridService.sendMail( await this.sendgridService.sendMail(
context, context,
email, email,
from, this.mailFrom,
subject, subject,
text, text,
html, html,
@ -835,11 +851,19 @@ export class AccountsService {
// 各子アカウントのShortageを算出してreturn用の変数にマージする // 各子アカウントのShortageを算出してreturn用の変数にマージする
const childrenPartnerLicenses: PartnerLicenseInfo[] = []; const childrenPartnerLicenses: PartnerLicenseInfo[] = [];
for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) { for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) {
let childShortage; const { allocatableLicenseWithMargin, expiringSoonLicense } =
childPartnerLicenseFromRepository;
let childShortage: number = 0;
if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) { if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) {
childShortage = if (
childPartnerLicenseFromRepository.allocatableLicenseWithMargin - allocatableLicenseWithMargin === undefined ||
childPartnerLicenseFromRepository.expiringSoonLicense; expiringSoonLicense === undefined
) {
throw new Error(
`Tier5 account has no allocatableLicenseWithMargin or expiringSoonLicense. accountId: ${accountId}`,
);
}
childShortage = allocatableLicenseWithMargin - expiringSoonLicense;
} else { } else {
childShortage = childShortage =
childPartnerLicenseFromRepository.stockLicense - childPartnerLicenseFromRepository.stockLicense -
@ -906,13 +930,13 @@ export class AccountsService {
licenseOrder.issued_at !== null licenseOrder.issued_at !== null
? new Date(licenseOrder.issued_at) ? new Date(licenseOrder.issued_at)
.toISOString() .toISOString()
.substr(0, 10) .substring(0, 10)
.replace(/-/g, '/') .replace(/-/g, '/')
: null, : undefined,
numberOfOrder: licenseOrder.quantity, numberOfOrder: licenseOrder.quantity,
orderDate: new Date(licenseOrder.ordered_at) orderDate: new Date(licenseOrder.ordered_at)
.toISOString() .toISOString()
.substr(0, 10) .substring(0, 10)
.replace(/-/g, '/'), .replace(/-/g, '/'),
poNumber: licenseOrder.po_number, poNumber: licenseOrder.po_number,
status: licenseOrder.status, 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 * @param context
@ -1558,7 +1657,7 @@ export class AccountsService {
async updateActiveWorktype( async updateActiveWorktype(
context: Context, context: Context,
externalId: string, externalId: string,
id: number, id: number | undefined,
): Promise<void> { ): Promise<void> {
this.logger.log( this.logger.log(
`[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` + `[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` +
@ -1625,34 +1724,40 @@ export class AccountsService {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(externalId);
const partners = await this.accountRepository.getPartners( const partnersRecords = await this.accountRepository.getPartners(
accountId, accountId,
limit, limit,
offset, offset,
); );
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する // DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
let externalIds = partners.partnersInfo.map( let externalIds = partnersRecords.partnersInfo.map(
(x) => x.primaryAccountExternalId, (x) => x.primaryAccountExternalId,
); );
externalIds = externalIds.filter((item) => item !== undefined); externalIds = externalIds.filter((item) => item !== undefined);
const adb2cUsers = await this.adB2cService.getUsers(context, externalIds); const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
// DBから取得した情報とADB2Cから取得した情報をマージ // DBから取得した情報とADB2Cから取得した情報をマージ
const response = partners.partnersInfo.map((db) => { const partners = partnersRecords.partnersInfo.map((db): Partner => {
const adb2cUser = adb2cUsers.find( const adb2cUser = adb2cUsers.find(
(adb2c) => db.primaryAccountExternalId === adb2c.id, (adb2c) => db.primaryAccountExternalId === adb2c.id,
); );
if (!adb2cUser) {
let primaryAdmin = undefined; throw new Error(
let mail = undefined; `adb2c user not found. externalId: ${db.primaryAccountExternalId}`,
if (adb2cUser) { );
primaryAdmin = adb2cUser.displayName;
mail = adb2cUser.identities.find(
(identity) =>
identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS,
).issuerAssignedId;
} }
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 { return {
name: db.name, name: db.name,
tier: db.tier, tier: db.tier,
@ -1665,17 +1770,15 @@ export class AccountsService {
}); });
return { return {
total: partners.total, total: partnersRecords.total,
partners: response, partners: partners,
}; };
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
if (e instanceof Error) { throw new HttpException(
throw new HttpException( makeErrorResponse('E009999'),
makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR,
HttpStatus.INTERNAL_SERVER_ERROR, );
);
}
} finally { } finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`); this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`);
} }

View File

@ -30,7 +30,7 @@ export type LicensesRepositoryMockValue = {
orderHistories: LicenseOrder[]; orderHistories: LicenseOrder[];
} }
| Error; | Error;
issueLicense: undefined | Error; issueLicense: void | Error;
}; };
export type UsersRepositoryMockValue = { export type UsersRepositoryMockValue = {
findUserById: User | Error; findUserById: User | Error;
@ -61,10 +61,12 @@ export type ConfigMockValue = {
get: string | Error; get: string | Error;
}; };
export type AccountsRepositoryMockValue = { export type AccountsRepositoryMockValue = {
getLicenseSummaryInfo: { getLicenseSummaryInfo:
licenseSummary: LicenseSummaryInfo; | {
isStorageAvailable: boolean; licenseSummary: LicenseSummaryInfo;
}; isStorageAvailable: boolean;
}
| Error;
createAccount: { newAccount: Account; adminUser: User } | Error; createAccount: { newAccount: Account; adminUser: User } | Error;
}; };
@ -181,18 +183,7 @@ export const makeLicensesRepositoryMock = (
issueLicense: issueLicense:
issueLicense instanceof Error issueLicense instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(issueLicense) ? jest.fn<Promise<void>, []>().mockRejectedValue(issueLicense)
: jest : jest.fn<Promise<void>, []>().mockResolvedValue(issueLicense),
.fn<
Promise<{
context: Context;
orderedAccountId: number;
myAccountId: number;
tier: number;
poNumber: string;
}>,
[]
>()
.mockResolvedValue(issueLicense),
}; };
}; };
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
@ -355,7 +346,7 @@ export const makeDefaultAccountsRepositoryMockValue =
user.created_by = 'test'; user.created_by = 'test';
user.created_at = new Date(); user.created_at = new Date();
user.updated_by = null; user.updated_by = null;
user.updated_at = null; user.updated_at = new Date();
return { return {
getLicenseSummaryInfo: { getLicenseSummaryInfo: {
licenseSummary: licenseSummaryInfo, licenseSummary: licenseSummaryInfo,
@ -385,7 +376,7 @@ export const makeDefaultUsersRepositoryMockValue =
user.created_by = 'test'; user.created_by = 'test';
user.created_at = new Date(); user.created_at = new Date();
user.updated_by = null; user.updated_by = null;
user.updated_at = null; user.updated_at = new Date();
const typists: User[] = []; const typists: User[] = [];
typists.push( typists.push(
@ -434,7 +425,7 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user.created_by = 'test'; user.created_by = 'test';
user.created_at = new Date(); user.created_at = new Date();
user.updated_by = null; user.updated_by = null;
user.updated_at = null; user.updated_at = new Date();
return { return {
getUserGroups: [ getUserGroups: [
@ -444,6 +435,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
name: 'GroupA', name: 'GroupA',
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
userGroupMembers: null,
}, },
{ {
id: 2, id: 2,
@ -451,6 +446,10 @@ export const makeDefaultUserGroupsRepositoryMockValue =
name: 'GroupB', name: 'GroupB',
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
userGroupMembers: null,
}, },
], ],
}; };

View File

@ -23,14 +23,14 @@ export const getSortCriteria = async (dataSource: DataSource) => {
export const createLicense = async ( export const createLicense = async (
datasource: DataSource, datasource: DataSource,
licenseId: number, licenseId: number,
expiry_date: Date, expiry_date: Date | null,
accountId: number, accountId: number,
type: string, type: string,
status: string, status: string,
allocated_user_id: number, allocated_user_id: number | null,
order_id: number, order_id: number | null,
deleted_at: Date, deleted_at: Date | null,
delete_order_id: number, delete_order_id: number | null,
): Promise<void> => { ): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({ const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId, id: licenseId,
@ -54,7 +54,7 @@ export const createLicense = async (
export const createLicenseSetExpiryDateAndStatus = async ( export const createLicenseSetExpiryDateAndStatus = async (
datasource: DataSource, datasource: DataSource,
accountId: number, accountId: number,
expiryDate: Date, expiryDate: Date | null,
status: string, status: string,
): Promise<void> => { ): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({ const { identifiers } = await datasource.getRepository(License).insert({
@ -171,19 +171,21 @@ export const createOptionItems = async (
datasource: DataSource, datasource: DataSource,
worktypeId: number, worktypeId: number,
): Promise<OptionItem[]> => { ): Promise<OptionItem[]> => {
const optionItems = []; const optionItems: OptionItem[] = [];
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
optionItems.push({ const optionItem = new OptionItem();
worktype_id: worktypeId, {
item_label: '', optionItem.worktype_id = worktypeId;
default_value_type: OPTION_ITEM_VALUE_TYPE.DEFAULT, optionItem.item_label = '';
initial_value: '', optionItem.default_value_type = OPTION_ITEM_VALUE_TYPE.DEFAULT;
created_by: 'test_runner', optionItem.initial_value = '';
created_at: new Date(), optionItem.created_by = 'test_runner';
updated_by: 'updater', optionItem.created_at = new Date();
updated_at: new Date(), optionItem.updated_by = 'updater';
}); optionItem.updated_at = new Date();
}
optionItems.push(optionItem);
} }
await datasource.getRepository(OptionItem).insert(optionItems); await datasource.getRepository(OptionItem).insert(optionItems);

View File

@ -327,8 +327,8 @@ export class GetOrderHistoriesRequest {
export class LicenseOrder { export class LicenseOrder {
@ApiProperty({ description: '注文日付' }) @ApiProperty({ description: '注文日付' })
orderDate: string; orderDate: string;
@ApiProperty({ description: '発行日付' }) @ApiProperty({ description: '発行日付', required: false })
issueDate: string; issueDate?: string;
@ApiProperty({ description: '注文数' }) @ApiProperty({ description: '注文数' })
numberOfOrder: number; numberOfOrder: number;
@ApiProperty({ description: 'POナンバー' }) @ApiProperty({ description: 'POナンバー' })
@ -497,6 +497,16 @@ export class UpdateWorktypeRequestParam {
id: number; id: number;
} }
export class DeleteWorktypeRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(0)
id: number;
}
export class DeleteWorktypeResponse {}
export class PostActiveWorktypeRequest { export class PostActiveWorktypeRequest {
@ApiProperty({ @ApiProperty({
required: false, required: false,

View File

@ -5,12 +5,19 @@ import {
makeAdB2cServiceMock, makeAdB2cServiceMock,
makeDefaultAdB2cMockValue, makeDefaultAdB2cMockValue,
} from './test/auth.service.mock'; } from './test/auth.service.mock';
import { ConfigModule } from '@nestjs/config';
describe('AuthController', () => { describe('AuthController', () => {
let controller: AuthController; let controller: AuthController;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env.local', '.env'],
isGlobal: true,
}),
],
controllers: [AuthController], controllers: [AuthController],
providers: [AuthService], providers: [AuthService],
}) })

View File

@ -3,6 +3,7 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { import {
makeAuthServiceMock, makeAuthServiceMock,
makeDefaultAdB2cMockValue, makeDefaultAdB2cMockValue,
makeDefaultConfigValue,
makeDefaultGetPublicKeyFromJwk, makeDefaultGetPublicKeyFromJwk,
} from './test/auth.service.mock'; } from './test/auth.service.mock';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
@ -16,7 +17,8 @@ import { v4 as uuidv4 } from 'uuid';
describe('AuthService', () => { describe('AuthService', () => {
it('IDトークンの検証とペイロードの取得に成功する', async () => { it('IDトークンの検証とペイロードの取得に成功する', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token = const token =
@ -27,7 +29,8 @@ describe('AuthService', () => {
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => { it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = 'invalid.id.token'; const token = 'invalid.id.token';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual( await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
@ -37,7 +40,8 @@ describe('AuthService', () => {
it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => { it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token = const token =
@ -50,7 +54,8 @@ describe('AuthService', () => {
it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => { it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token = const token =
@ -63,7 +68,8 @@ describe('AuthService', () => {
it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => { it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
@ -74,7 +80,8 @@ describe('AuthService', () => {
it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => { it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const service = await makeAuthServiceMock(adb2cParam); const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替 //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk; service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token = const token =
@ -87,8 +94,9 @@ describe('AuthService', () => {
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。メタデータ', async () => { it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。メタデータ', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getMetaData = new Error('failed get metadata'); adb2cParam.getMetaData = new Error('failed get metadata');
const service = await makeAuthServiceMock(adb2cParam); const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
@ -101,8 +109,9 @@ describe('AuthService', () => {
}); });
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。キーセット', async () => { it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。キーセット', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getSignKeySets = new Error('failed get keyset'); adb2cParam.getSignKeySets = new Error('failed get keyset');
const service = await makeAuthServiceMock(adb2cParam); const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
@ -116,10 +125,11 @@ describe('AuthService', () => {
it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => { it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getSignKeySets = [ adb2cParam.getSignKeySets = [
{ kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' }, { kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
]; ];
const service = await makeAuthServiceMock(adb2cParam); const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
@ -133,7 +143,7 @@ describe('AuthService', () => {
}); });
describe('checkIsAcceptedLatestVersion', () => { describe('checkIsAcceptedLatestVersion', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -146,11 +156,14 @@ describe('checkIsAcceptedLatestVersion', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => { it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService); const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
@ -171,7 +184,9 @@ describe('checkIsAcceptedLatestVersion', () => {
}); });
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第一~第四)', async () => { it('同意済み利用規約バージョンが最新のときにチェックが通ること(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService); const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
@ -192,7 +207,9 @@ describe('checkIsAcceptedLatestVersion', () => {
}); });
it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => { it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService); const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
@ -213,7 +230,9 @@ describe('checkIsAcceptedLatestVersion', () => {
}); });
it('同意済み利用規約EULAバージョンが最新でないときにチェックが通らないこと第一第四', async () => { it('同意済み利用規約EULAバージョンが最新でないときにチェックが通らないこと第一第四', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService); const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
@ -234,7 +253,9 @@ describe('checkIsAcceptedLatestVersion', () => {
}); });
it('同意済み利用規約バージョンDPAが最新でないときにチェックが通らないこと第一第四', async () => { it('同意済み利用規約バージョンDPAが最新でないときにチェックが通らないこと第一第四', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService); const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,

View File

@ -25,6 +25,13 @@ import { Context } from '../../common/log';
@Injectable() @Injectable()
export class AuthService { 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( constructor(
private readonly adB2cService: AdB2cService, private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
@ -68,10 +75,7 @@ export class AuthService {
this.logger.log( this.logger.log(
`[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`, `[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; let user: User;
// ユーザー情報とユーザーが属しているアカウント情報を取得 // ユーザー情報とユーザーが属しているアカウント情報を取得
try { 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); const privateKey = getPrivateKey(this.configService);
// ユーザーのロールを設定 // ユーザーのロールを設定
@ -165,7 +172,6 @@ export class AuthService {
this.logger.log( this.logger.log(
`[IN] [${context.trackingId}] ${this.generateAccessToken.name}`, `[IN] [${context.trackingId}] ${this.generateAccessToken.name}`,
); );
const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB');
const privateKey = getPrivateKey(this.configService); const privateKey = getPrivateKey(this.configService);
const pubkey = getPublicKey(this.configService); const pubkey = getPublicKey(this.configService);
@ -188,7 +194,7 @@ export class AuthService {
tier: token.tier, tier: token.tier,
userId: token.userId, userId: token.userId,
}, },
lifetime, this.accessTokenlifetime,
privateKey, privateKey,
); );
@ -205,11 +211,14 @@ export class AuthService {
async getVerifiedIdToken(token: string): Promise<IDToken> { async getVerifiedIdToken(token: string): Promise<IDToken> {
this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`); this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`);
let kid = ''; let kid: string | undefined = '';
try { try {
// JWTトークンのヘッダを見るため一度デコードする // JWTトークンのヘッダを見るため一度デコードする
const decodedToken = jwt.decode(token, { complete: true }); 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) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
throw new HttpException( throw new HttpException(
@ -343,7 +352,7 @@ export class AuthService {
): Promise<boolean> { ): Promise<boolean> {
this.logger.log( this.logger.log(
`[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` + `[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` +
`idToken: ${idToken}, };`, `idToken.sub: ${idToken.sub}, };`,
); );
try { try {

View File

@ -9,9 +9,13 @@ export type AdB2cMockValue = {
getMetaData: B2cMetadata | Error; getMetaData: B2cMetadata | Error;
getSignKeySets: JwkSignKey[] | Error; getSignKeySets: JwkSignKey[] | Error;
}; };
export type ConfigMockValue = {
getOrThrow: number;
};
export const makeAuthServiceMock = async ( export const makeAuthServiceMock = async (
adB2cMockValue: AdB2cMockValue, adB2cMockValue: AdB2cMockValue,
configMockValue: ConfigMockValue,
): Promise<AuthService> => { ): Promise<AuthService> => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [AuthService], providers: [AuthService],
@ -21,7 +25,7 @@ export const makeAuthServiceMock = async (
case AdB2cService: case AdB2cService:
return makeAdB2cServiceMock(adB2cMockValue); return makeAdB2cServiceMock(adB2cMockValue);
case ConfigService: case ConfigService:
return {}; return makeConfigMock(configMockValue);
case UsersRepositoryService: case UsersRepositoryService:
return {}; return {};
} }
@ -80,3 +84,16 @@ export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => {
'-----END PUBLIC KEY-----', '-----END PUBLIC KEY-----',
].join('\n'); ].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,
};
};

View File

@ -2,6 +2,7 @@ import {
Body, Body,
Controller, Controller,
Get, Get,
HttpException,
HttpStatus, HttpStatus,
Post, Post,
Query, Query,
@ -37,6 +38,7 @@ import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { retrieveAuthorizationToken } from '../../common/http/helper'; import { retrieveAuthorizationToken } from '../../common/http/helper';
import { Request } from 'express'; import { Request } from 'express';
import { makeContext } from '../../common/log'; import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('files') @ApiTags('files')
@Controller('files') @Controller('files')
@ -75,10 +77,23 @@ export class FilesController {
@Req() req: Request, @Req() req: Request,
@Body() body: AudioUploadFinishedRequest, @Body() body: AudioUploadFinishedRequest,
): Promise<AudioUploadFinishedResponse> { ): Promise<AudioUploadFinishedResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { 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(accessToken.userId); const context = makeContext(userId);
const { const {
url, url,
@ -99,7 +114,7 @@ export class FilesController {
const res = await this.filesService.uploadFinished( const res = await this.filesService.uploadFinished(
context, context,
accessToken.userId, userId,
url, url,
authorId, authorId,
fileName, fileName,
@ -149,10 +164,23 @@ export class FilesController {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@Query() _query: AudioUploadLocationRequest, @Query() _query: AudioUploadLocationRequest,
): Promise<AudioUploadLocationResponse> { ): Promise<AudioUploadLocationResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { 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(accessToken.userId); const context = makeContext(userId);
const url = await this.filesService.publishUploadSas(context, accessToken); const url = await this.filesService.publishUploadSas(context, accessToken);
return { url }; return { url };
@ -195,14 +223,27 @@ export class FilesController {
): Promise<AudioDownloadLocationResponse> { ): Promise<AudioDownloadLocationResponse> {
const { audioFileId } = body; const { audioFileId } = body;
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { 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(accessToken.userId); const context = makeContext(userId);
const url = await this.filesService.publishAudioFileDownloadSas( const url = await this.filesService.publishAudioFileDownloadSas(
context, context,
accessToken.userId, userId,
audioFileId, audioFileId,
); );
@ -246,14 +287,27 @@ export class FilesController {
): Promise<TemplateDownloadLocationResponse> { ): Promise<TemplateDownloadLocationResponse> {
const { audioFileId } = body; const { audioFileId } = body;
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const accessToken = jwt.decode(token, { 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(accessToken.userId); const context = makeContext(userId);
const url = await this.filesService.publishTemplateFileDownloadSas( const url = await this.filesService.publishTemplateFileDownloadSas(
context, context,
accessToken.userId, userId,
audioFileId, audioFileId,
); );
@ -287,8 +341,21 @@ export class FilesController {
async uploadTemplateLocation( async uploadTemplateLocation(
@Req() req: Request, @Req() req: Request,
): Promise<TemplateUploadLocationResponse> { ): Promise<TemplateUploadLocationResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(token, { 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); const context = makeContext(userId);
@ -333,8 +400,21 @@ export class FilesController {
@Body() body: TemplateUploadFinishedRequest, @Body() body: TemplateUploadFinishedRequest,
): Promise<TemplateUploadFinishedReqponse> { ): Promise<TemplateUploadFinishedReqponse> {
const { name, url } = body; const { name, url } = body;
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(token, { 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); const context = makeContext(userId);
await this.filesService.templateUploadFinished(context, userId, url, name); await this.filesService.templateUploadFinished(context, userId, url, name);

View File

@ -35,11 +35,10 @@ describe('音声ファイルアップロードURL取得', () => {
); );
expect( expect(
await service.publishUploadSas(makeContext('trackingId'), { await service.publishUploadSas(
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', makeContext('trackingId'),
role: 'Author', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
tier: 5, ),
}),
).toEqual('https://blob-storage?sas-token'); ).toEqual('https://blob-storage?sas-token');
}); });
@ -57,11 +56,10 @@ describe('音声ファイルアップロードURL取得', () => {
); );
expect( expect(
await service.publishUploadSas(makeContext('trackingId'), { await service.publishUploadSas(
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', makeContext('trackingId'),
role: 'Author', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
tier: 5, ),
}),
).toEqual('https://blob-storage?sas-token'); ).toEqual('https://blob-storage?sas-token');
}); });
@ -78,11 +76,10 @@ describe('音声ファイルアップロードURL取得', () => {
); );
await expect( await expect(
service.publishUploadSas(makeContext('trackingId'), { service.publishUploadSas(
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', makeContext('trackingId'),
role: 'Author', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
tier: 5, ),
}),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
); );
@ -102,11 +99,10 @@ describe('音声ファイルアップロードURL取得', () => {
blobParam.publishUploadSas = new Error('Azure service down'); blobParam.publishUploadSas = new Error('Azure service down');
await expect( await expect(
service.publishUploadSas(makeContext('trackingId'), { service.publishUploadSas(
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx', makeContext('trackingId'),
role: 'Author', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
tier: 5, ),
}),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
); );
@ -295,7 +291,7 @@ describe('タスク作成', () => {
}); });
describe('音声ファイルダウンロードURL取得', () => { describe('音声ファイルダウンロードURL取得', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -308,11 +304,13 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => { it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { const {
external_id: externalId, external_id: externalId,
@ -333,7 +331,7 @@ describe('音声ファイルダウンロードURL取得', () => {
'test.zip', 'test.zip',
'InProgress', 'InProgress',
undefined, undefined,
authorId, authorId ?? '',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -341,6 +339,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
expect( expect(
@ -353,6 +352,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => { it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, { const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -382,6 +382,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -396,6 +397,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -429,6 +431,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -443,6 +446,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => { it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, { const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -467,6 +471,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -481,6 +486,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
it('Taskが存在しない場合はエラーとなる', async () => { it('Taskが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -492,6 +498,7 @@ describe('音声ファイルダウンロードURL取得', () => {
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -506,6 +513,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { const {
external_id: externalId, external_id: externalId,
@ -526,7 +534,7 @@ describe('音声ファイルダウンロードURL取得', () => {
'test.zip', 'test.zip',
'InProgress', 'InProgress',
undefined, undefined,
authorId, authorId ?? '',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -534,6 +542,7 @@ describe('音声ファイルダウンロードURL取得', () => {
blobParam.fileExists = false; blobParam.fileExists = false;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -549,7 +558,7 @@ describe('音声ファイルダウンロードURL取得', () => {
}); });
describe('テンプレートファイルダウンロードURL取得', () => { describe('テンプレートファイルダウンロードURL取得', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -562,11 +571,13 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => { it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser( const { external_id: externalId, author_id: authorId } = await makeTestUser(
source, source,
@ -586,7 +597,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
'test.zip', 'test.zip',
'InProgress', 'InProgress',
undefined, undefined,
authorId, authorId ?? '',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -594,6 +605,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
expect( expect(
@ -606,6 +618,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => { it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, id: userId } = await makeTestUser(source, { const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -629,6 +642,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -643,6 +657,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -672,6 +687,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -686,6 +702,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => { it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -710,6 +727,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = true; blobParam.fileExists = true;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -724,6 +742,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
it('Taskが存在しない場合はエラーとなる', async () => { it('Taskが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -735,6 +754,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -749,6 +769,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId, author_id: authorId } = await makeTestUser( const { external_id: externalId, author_id: authorId } = await makeTestUser(
source, source,
@ -768,7 +789,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
'test.zip', 'test.zip',
'InProgress', 'InProgress',
undefined, undefined,
authorId, authorId ?? '',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -776,6 +797,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
blobParam.fileExists = false; blobParam.fileExists = false;
const module = await makeTestingModuleWithBlob(source, blobParam); const module = await makeTestingModuleWithBlob(source, blobParam);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
@ -791,7 +813,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}); });
describe('publishTemplateFileUploadSas', () => { describe('publishTemplateFileUploadSas', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -804,12 +826,15 @@ describe('publishTemplateFileUploadSas', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => { it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -832,7 +857,9 @@ describe('publishTemplateFileUploadSas', () => {
}); });
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => { it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
@ -858,7 +885,9 @@ describe('publishTemplateFileUploadSas', () => {
}); });
it('SASトークンの取得に失敗した場合はエラーとなる', async () => { it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
@ -887,7 +916,7 @@ describe('publishTemplateFileUploadSas', () => {
}); });
describe('templateUploadFinished', () => { describe('templateUploadFinished', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -900,12 +929,15 @@ describe('templateUploadFinished', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('アップロード完了後のテンプレートファイル情報をDBに保存できる新規追加', async () => { it('アップロード完了後のテンプレートファイル情報をDBに保存できる新規追加', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -937,7 +969,9 @@ describe('templateUploadFinished', () => {
}); });
it('アップロード完了後のテンプレートファイル情報をDBに保存できる更新', async () => { it('アップロード完了後のテンプレートファイル情報をDBに保存できる更新', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
@ -975,7 +1009,9 @@ describe('templateUploadFinished', () => {
}); });
it('DBへの保存に失敗した場合はエラーとなる', async () => { it('DBへの保存に失敗した場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });

View File

@ -24,6 +24,7 @@ import {
} from '../../repositories/tasks/errors/types'; } from '../../repositories/tasks/errors/types';
import { Context } from '../../common/log'; import { Context } from '../../common/log';
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service'; import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@Injectable() @Injectable()
export class FilesService { export class FilesService {
@ -206,7 +207,7 @@ export class FilesService {
*/ */
async publishUploadSas( async publishUploadSas(
context: Context, context: Context,
token: AccessToken, externalId: string,
): Promise<string> { ): Promise<string> {
this.logger.log( this.logger.log(
`[IN] [${context.trackingId}] ${this.publishUploadSas.name}`, `[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
@ -216,10 +217,11 @@ export class FilesService {
let accountId: number; let accountId: number;
let country: string; let country: string;
try { try {
const user = await this.usersRepository.findUserByExternalId( const user = await this.usersRepository.findUserByExternalId(externalId);
token.userId, if (!user.account) {
); throw new AccountNotFoundError('account not found.');
accountId = user.account.id; }
accountId = user.account_id;
country = user.account.country; country = user.account.country;
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
@ -291,14 +293,17 @@ export class FilesService {
let userId: number; let userId: number;
let country: string; let country: string;
let isTypist: boolean; let isTypist: boolean;
let authorId: string; let authorId: string | undefined;
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); const user = await this.usersRepository.findUserByExternalId(externalId);
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
accountId = user.account.id; accountId = user.account.id;
userId = user.id; userId = user.id;
country = user.account.country; country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST; isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id; authorId = user.author_id ?? undefined;
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
@ -321,7 +326,7 @@ export class FilesService {
accountId, accountId,
status, status,
); );
const file = task.file; const { file } = task;
// タスクに紐づく音声ファイルだけが消される場合がある。 // タスクに紐づく音声ファイルだけが消される場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う // その場合はダウンロード不可なので不在エラーとして扱う
@ -332,9 +337,9 @@ export class FilesService {
} }
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
if (!isTypist && task.file.author_id !== authorId) { if (!isTypist && file.author_id !== authorId) {
throw new AuthorUserNotMatchError( 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 userId: number;
let country: string; let country: string;
let isTypist: boolean; let isTypist: boolean;
let authorId: string; let authorId: string | undefined;
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); 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; userId = user.id;
country = user.account.country; country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST; isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id; authorId = user.author_id ?? undefined;
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
this.logger.log( this.logger.log(
@ -454,6 +462,15 @@ export class FilesService {
accountId, accountId,
status, 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; const template_file = task.template_file;
@ -466,9 +483,9 @@ export class FilesService {
} }
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー // ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー
if (!isTypist && task.file.author_id !== authorId) { if (!isTypist && file.author_id !== authorId) {
throw new AuthorUserNotMatchError( 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'), makeErrorResponse('E010603'),
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
case AudioFileNotFoundError:
case TemplateFileNotFoundError: case TemplateFileNotFoundError:
throw new HttpException( throw new HttpException(
makeErrorResponse('E010701'), makeErrorResponse('E010701'),
@ -552,15 +570,18 @@ export class FilesService {
`[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`, `[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`,
); );
try { try {
const { const { account } = await this.usersRepository.findUserByExternalId(
account: { id: accountId, country }, externalId,
} = await this.usersRepository.findUserByExternalId(externalId); );
if (!account) {
throw new AccountNotFoundError('account not found.');
}
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認 // 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
const isContainerExists = await this.blobStorageService.containerExists( const isContainerExists = await this.blobStorageService.containerExists(
context, context,
accountId, account.id,
country, account.country,
); );
if (!isContainerExists) { if (!isContainerExists) {
throw new Error('container not found.'); throw new Error('container not found.');
@ -569,8 +590,8 @@ export class FilesService {
// SASトークン発行 // SASトークン発行
const url = await this.blobStorageService.publishTemplateUploadSas( const url = await this.blobStorageService.publishTemplateUploadSas(
context, context,
accountId, account.id,
country, account.country,
); );
return url; return url;

View File

@ -134,12 +134,15 @@ export const makeDefaultUsersRepositoryMockValue =
created_by: 'test', created_by: 'test',
created_at: new Date(), created_at: new Date(),
updated_by: null, updated_by: null,
updated_at: null, updated_at: new Date(),
auto_renew: true, auto_renew: true,
license_alert: true, license_alert: true,
notification: true, notification: true,
encryption: false, encryption: false,
prompt: false, prompt: false,
encryption_password: null,
license: null,
userGroupMembers: null,
account: { account: {
id: 2, id: 2,
parent_account_id: 2, parent_account_id: 2,
@ -154,7 +157,10 @@ export const makeDefaultUsersRepositoryMockValue =
created_by: '', created_by: '',
created_at: new Date(), created_at: new Date(),
updated_by: '', 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', status: 'Uploaded',
priority: '01', priority: '01',
created_at: new Date(), 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: { getTasksFromAccountId: {
tasks: [], tasks: [],

View File

@ -98,7 +98,7 @@ export const createTask = async (
export const makeTestingModuleWithBlob = async ( export const makeTestingModuleWithBlob = async (
datasource: DataSource, datasource: DataSource,
blobStorageService: BlobstorageServiceMockValue, blobStorageService: BlobstorageServiceMockValue,
): Promise<TestingModule> => { ): Promise<TestingModule | undefined> => {
try { try {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [

View File

@ -2,6 +2,7 @@ import {
Body, Body,
Controller, Controller,
Get, Get,
HttpException,
HttpStatus, HttpStatus,
Post, Post,
Req, Req,
@ -34,6 +35,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, TIERS } from '../../constants'; import { ADMIN_ROLES, TIERS } from '../../constants';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { makeContext } from '../../common/log'; import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('licenses') @ApiTags('licenses')
@Controller('licenses') @Controller('licenses')
@ -73,12 +75,25 @@ export class LicensesController {
@Req() req: Request, @Req() req: Request,
@Body() body: CreateOrdersRequest, @Body() body: CreateOrdersRequest,
): Promise<CreateOrdersResponse> { ): Promise<CreateOrdersResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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;
// ライセンス注文処理 // ライセンス注文処理
await this.licensesService.licenseOrders( await this.licensesService.licenseOrders(
payload, userId,
body.poNumber, body.poNumber,
body.orderCount, body.orderCount,
); );
@ -111,11 +126,24 @@ export class LicensesController {
@Req() req: Request, @Req() req: Request,
@Body() body: IssueCardLicensesRequest, @Body() body: IssueCardLicensesRequest,
): Promise<IssueCardLicensesResponse> { ): Promise<IssueCardLicensesResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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 cardLicenseKeys = await this.licensesService.issueCardLicenseKeys( const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
payload.userId, userId,
body.createCount, body.createCount,
); );
@ -154,11 +182,24 @@ export class LicensesController {
@Req() req: Request, @Req() req: Request,
@Body() body: ActivateCardLicensesRequest, @Body() body: ActivateCardLicensesRequest,
): Promise<ActivateCardLicensesResponse> { ): Promise<ActivateCardLicensesResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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;
await this.licensesService.activateCardLicenseKey( await this.licensesService.activateCardLicenseKey(
payload.userId, userId,
body.cardLicenseKey, body.cardLicenseKey,
); );
@ -194,16 +235,26 @@ export class LicensesController {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
@Req() req: Request, @Req() req: Request,
): Promise<GetAllocatableLicensesResponse> { ): Promise<GetAllocatableLicensesResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = jwt.decode(token, { 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(payload.userId); const context = makeContext(userId);
const allocatableLicenses = const allocatableLicenses =
await this.licensesService.getAllocatableLicenses( await this.licensesService.getAllocatableLicenses(context, userId);
context,
payload.userId,
);
return allocatableLicenses; return allocatableLicenses;
} }
@ -245,16 +296,25 @@ export class LicensesController {
@Req() req: Request, @Req() req: Request,
@Body() body: CancelOrderRequest, @Body() body: CancelOrderRequest,
): Promise<CancelOrderResponse> { ): Promise<CancelOrderResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = jwt.decode(token, { 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(payload.userId); const context = makeContext(userId);
await this.licensesService.cancelOrder( await this.licensesService.cancelOrder(context, userId, body.poNumber);
context,
payload.userId,
body.poNumber,
);
return {}; return {};
} }
} }

View File

@ -56,11 +56,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new CreateOrdersRequest(); const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.orderCount = 1000; body.orderCount = 1000;
body.poNumber = '1'; body.poNumber = '1';
expect( expect(
await service.licenseOrders(token, body.poNumber, body.orderCount), await service.licenseOrders(userId, body.poNumber, body.orderCount),
).toEqual(undefined); ).toEqual(undefined);
}); });
it('ユーザID取得できなかった場合、エラーとなる', async () => { it('ユーザID取得できなかった場合、エラーとなる', async () => {
@ -78,11 +78,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new CreateOrdersRequest(); const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '', role: '', tier: 5 }; const userId = '';
body.orderCount = 1000; body.orderCount = 1000;
body.poNumber = '1'; body.poNumber = '1';
await expect( await expect(
service.licenseOrders(token, body.poNumber, body.orderCount), service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -105,11 +105,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new CreateOrdersRequest(); const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.orderCount = 1000; body.orderCount = 1000;
body.poNumber = '1'; body.poNumber = '1';
await expect( await expect(
service.licenseOrders(token, body.poNumber, body.orderCount), service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -130,11 +130,11 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new CreateOrdersRequest(); const body = new CreateOrdersRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.orderCount = 1000; body.orderCount = 1000;
body.poNumber = '1'; body.poNumber = '1';
await expect( await expect(
service.licenseOrders(token, body.poNumber, body.orderCount), service.licenseOrders(userId, body.poNumber, body.orderCount),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E010401'), makeErrorResponse('E010401'),
@ -154,7 +154,7 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new IssueCardLicensesRequest(); const body = new IssueCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.createCount = 10; body.createCount = 10;
const issueCardLicensesResponse: IssueCardLicensesResponse = { const issueCardLicensesResponse: IssueCardLicensesResponse = {
cardLicenseKeys: [ cardLicenseKeys: [
@ -171,7 +171,7 @@ describe('LicensesService', () => {
], ],
}; };
expect( expect(
await service.issueCardLicenseKeys(token.userId, body.createCount), await service.issueCardLicenseKeys(userId, body.createCount),
).toEqual(issueCardLicensesResponse); ).toEqual(issueCardLicensesResponse);
}); });
it('カードライセンス発行に失敗した場合、エラーになる', async () => { it('カードライセンス発行に失敗した場合、エラーになる', async () => {
@ -187,10 +187,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new IssueCardLicensesRequest(); const body = new IssueCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.createCount = 1000; body.createCount = 1000;
await expect( await expect(
service.issueCardLicenseKeys(token.userId, body.createCount), service.issueCardLicenseKeys(userId, body.createCount),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -210,10 +210,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new ActivateCardLicensesRequest(); const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
expect( expect(
await service.activateCardLicenseKey(token.userId, body.cardLicenseKey), await service.activateCardLicenseKey(userId, body.cardLicenseKey),
).toEqual(undefined); ).toEqual(undefined);
}); });
it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => { it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => {
@ -229,10 +229,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new ActivateCardLicensesRequest(); const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect( await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey), service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -254,10 +254,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new ActivateCardLicensesRequest(); const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect( await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey), service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
); );
@ -276,10 +276,10 @@ describe('LicensesService', () => {
accountsRepositoryMockValue, accountsRepositoryMockValue,
); );
const body = new ActivateCardLicensesRequest(); const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 }; const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY'; body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect( await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey), service.activateCardLicenseKey(userId, body.cardLicenseKey),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
); );
@ -287,7 +287,7 @@ describe('LicensesService', () => {
}); });
describe('DBテスト', () => { describe('DBテスト', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -300,12 +300,15 @@ describe('DBテスト', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => { it('カードライセンス発行が完了する(発行数が合っているか確認)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
@ -323,7 +326,9 @@ describe('DBテスト', () => {
}); });
it('カードライセンス取り込みが完了する', async () => { it('カードライセンス取り込みが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, { const { external_id: externalId } = await makeTestUser(source, {
@ -362,13 +367,15 @@ describe('DBテスト', () => {
); );
const dbSelectResultFromLicense = await selectLicense(source, license_id); const dbSelectResultFromLicense = await selectLicense(source, license_id);
expect( expect(
dbSelectResultFromCardLicense.cardLicense.activated_at, dbSelectResultFromCardLicense?.cardLicense?.activated_at,
).toBeDefined(); ).toBeDefined();
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId); expect(dbSelectResultFromLicense?.license?.account_id).toEqual(accountId);
}); });
it('取込可能なライセンスのみが取得できる', async () => { it('取込可能なライセンスのみが取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const now = new Date(); const now = new Date();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
@ -513,7 +520,7 @@ describe('DBテスト', () => {
}); });
describe('ライセンス割り当て', () => { describe('ライセンス割り当て', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -526,12 +533,15 @@ describe('ライセンス割り当て', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => { it('未割当のライセンスに対して、ライセンス割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -567,11 +577,11 @@ describe('ライセンス割り当て', () => {
await service.allocateLicense(makeContext('trackingId'), userId, 1); await service.allocateLicense(makeContext('trackingId'), userId, 1);
const resultLicense = await selectLicense(source, 1); const resultLicense = await selectLicense(source, 1);
expect(resultLicense.license.allocated_user_id).toBe(userId); expect(resultLicense.license?.allocated_user_id).toBe(userId);
expect(resultLicense.license.status).toBe( expect(resultLicense.license?.status).toBe(
LICENSE_ALLOCATED_STATUS.ALLOCATED, LICENSE_ALLOCATED_STATUS.ALLOCATED,
); );
expect(resultLicense.license.expiry_date.setMilliseconds(0)).toEqual( expect(resultLicense.license?.expiry_date?.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0), expiry_date.setMilliseconds(0),
); );
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
@ -579,22 +589,24 @@ describe('ライセンス割り当て', () => {
userId, userId,
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId, userId,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( expect(
true, licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
); ).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId, accountId,
); );
}); });
it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => { it('再割り当て可能なライセンスに対して、ライセンス割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -630,30 +642,32 @@ describe('ライセンス割り当て', () => {
await service.allocateLicense(makeContext('trackingId'), userId, 1); await service.allocateLicense(makeContext('trackingId'), userId, 1);
const result = await selectLicense(source, 1); const result = await selectLicense(source, 1);
expect(result.license.allocated_user_id).toBe(userId); expect(result.license?.allocated_user_id).toBe(userId);
expect(result.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); expect(result.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result.license.expiry_date).toEqual(date); expect(result.license?.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
source, source,
userId, userId,
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId, userId,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( expect(
true, licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
); ).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId, accountId,
); );
}); });
it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => { it('未割当のライセンスに対して、別のライセンスが割り当てられているユーザーの割り当てが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -705,32 +719,32 @@ describe('ライセンス割り当て', () => {
// もともと割り当てられていたライセンスの状態確認 // もともと割り当てられていたライセンスの状態確認
const result1 = await selectLicense(source, 1); const result1 = await selectLicense(source, 1);
expect(result1.license.allocated_user_id).toBe(null); expect(result1.license?.allocated_user_id).toBe(null);
expect(result1.license.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE); expect(result1.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE);
expect(result1.license.expiry_date).toEqual(date); expect(result1.license?.expiry_date).toEqual(date);
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
source, source,
userId, userId,
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId, userId,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( expect(
false, licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
); ).toBe(false);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId, accountId,
); );
// 新たに割り当てたライセンスの状態確認 // 新たに割り当てたライセンスの状態確認
const result2 = await selectLicense(source, 2); const result2 = await selectLicense(source, 2);
expect(result2.license.allocated_user_id).toBe(userId); expect(result2.license?.allocated_user_id).toBe(userId);
expect(result2.license.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED); expect(result2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.ALLOCATED);
expect(result2.license.expiry_date.setMilliseconds(0)).toEqual( expect(result2.license?.expiry_date?.setMilliseconds(0)).toEqual(
expiry_date.setMilliseconds(0), expiry_date.setMilliseconds(0),
); );
const newlicenseAllocationHistory = await selectLicenseAllocationHistory( const newlicenseAllocationHistory = await selectLicenseAllocationHistory(
@ -738,22 +752,24 @@ describe('ライセンス割り当て', () => {
userId, userId,
2, 2,
); );
expect(newlicenseAllocationHistory.licenseAllocationHistory.user_id).toBe( expect(newlicenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId, userId,
); );
expect( expect(
newlicenseAllocationHistory.licenseAllocationHistory.license_id, newlicenseAllocationHistory.licenseAllocationHistory?.license_id,
).toBe(2); ).toBe(2);
expect( expect(
newlicenseAllocationHistory.licenseAllocationHistory.is_allocated, newlicenseAllocationHistory.licenseAllocationHistory?.is_allocated,
).toBe(true); ).toBe(true);
expect( expect(
newlicenseAllocationHistory.licenseAllocationHistory.account_id, newlicenseAllocationHistory.licenseAllocationHistory?.account_id,
).toBe(accountId); ).toBe(accountId);
}); });
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がNORMALのとき', async () => { it('割り当て時にライセンス履歴テーブルへの登録が完了する元がNORMALのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -806,12 +822,14 @@ describe('ライセンス割り当て', () => {
2, 2,
); );
expect( expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type, licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('NONE'); ).toBe('NONE');
}); });
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がCARDのとき', async () => { it('割り当て時にライセンス履歴テーブルへの登録が完了する元がCARDのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -864,12 +882,14 @@ describe('ライセンス割り当て', () => {
2, 2,
); );
expect( expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type, licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('CARD'); ).toBe('CARD');
}); });
it('割り当て時にライセンス履歴テーブルへの登録が完了する元がTRIALのとき', async () => { it('割り当て時にライセンス履歴テーブルへの登録が完了する元がTRIALのとき', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -922,12 +942,14 @@ describe('ライセンス割り当て', () => {
2, 2,
); );
expect( expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type, licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('TRIAL'); ).toBe('TRIAL');
}); });
it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => { it('有効期限が切れているライセンスを割り当てようとした場合、エラーになる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -961,7 +983,9 @@ describe('ライセンス割り当て', () => {
}); });
it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => { it('割り当て不可なライセンスを割り当てようとした場合、エラーになる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -1013,7 +1037,7 @@ describe('ライセンス割り当て', () => {
}); });
describe('ライセンス割り当て解除', () => { describe('ライセンス割り当て解除', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -1026,12 +1050,15 @@ describe('ライセンス割り当て解除', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ライセンスの割り当て解除が完了する', async () => { it('ライセンスの割り当て解除が完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -1068,11 +1095,11 @@ describe('ライセンス割り当て解除', () => {
// 割り当て解除したライセンスの状態確認 // 割り当て解除したライセンスの状態確認
const deallocatedLicense = await selectLicense(source, 1); const deallocatedLicense = await selectLicense(source, 1);
expect(deallocatedLicense.license.allocated_user_id).toBe(null); expect(deallocatedLicense.license?.allocated_user_id).toBe(null);
expect(deallocatedLicense.license.status).toBe( expect(deallocatedLicense.license?.status).toBe(
LICENSE_ALLOCATED_STATUS.REUSABLE, LICENSE_ALLOCATED_STATUS.REUSABLE,
); );
expect(deallocatedLicense.license.expiry_date).toEqual(date); expect(deallocatedLicense.license?.expiry_date).toEqual(date);
// ライセンス履歴テーブルの状態確認 // ライセンス履歴テーブルの状態確認
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
@ -1080,25 +1107,27 @@ describe('ライセンス割り当て解除', () => {
userId, userId,
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
userId, userId,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.license_id).toBe(
1, 1,
); );
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe( expect(
false, licenseAllocationHistory.licenseAllocationHistory?.is_allocated,
); ).toBe(false);
expect(licenseAllocationHistory.licenseAllocationHistory.account_id).toBe( expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
accountId, accountId,
); );
expect( expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type, licenseAllocationHistory.licenseAllocationHistory?.switch_from_type,
).toBe('NONE'); ).toBe('NONE');
}); });
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => { it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
@ -1158,7 +1187,7 @@ describe('ライセンス割り当て解除', () => {
}); });
describe('ライセンス注文キャンセル', () => { describe('ライセンス注文キャンセル', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -1171,12 +1200,15 @@ describe('ライセンス注文キャンセル', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ライセンス注文のキャンセルが完了する', async () => { it('ライセンス注文のキャンセルが完了する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source, source,
); );
@ -1185,7 +1217,7 @@ describe('ライセンス注文キャンセル', () => {
source, source,
poNumber, poNumber,
tier2Accounts[0].account.id, tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id, tier2Accounts[0].account.parent_account_id ?? 0,
null, null,
10, 10,
'Issue Requesting', 'Issue Requesting',
@ -1195,7 +1227,7 @@ describe('ライセンス注文キャンセル', () => {
source, source,
poNumber, poNumber,
tier2Accounts[0].account.id, tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id, tier2Accounts[0].account.parent_account_id ?? 0,
null, null,
10, 10,
'Order Canceled', 'Order Canceled',
@ -1214,12 +1246,14 @@ describe('ライセンス注文キャンセル', () => {
tier2Accounts[0].account.id, tier2Accounts[0].account.id,
poNumber, poNumber,
); );
expect(orderRecord.orderLicense.canceled_at).toBeDefined(); expect(orderRecord.orderLicense?.canceled_at).toBeDefined();
expect(orderRecord.orderLicense.status).toBe('Order Canceled'); expect(orderRecord.orderLicense?.status).toBe('Order Canceled');
}); });
it('ライセンスが既に発行済みの場合、エラーとなる', async () => { it('ライセンスが既に発行済みの場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source, source,
); );
@ -1228,7 +1262,7 @@ describe('ライセンス注文キャンセル', () => {
source, source,
poNumber, poNumber,
tier2Accounts[0].account.id, tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id, tier2Accounts[0].account.parent_account_id ?? 0,
null, null,
10, 10,
'Issued', 'Issued',
@ -1247,7 +1281,9 @@ describe('ライセンス注文キャンセル', () => {
}); });
it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => { it('ライセンスが既にキャンセル済みの場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts( const { tier2Accounts: tier2Accounts } = await makeHierarchicalAccounts(
source, source,
@ -1257,7 +1293,7 @@ describe('ライセンス注文キャンセル', () => {
source, source,
poNumber, poNumber,
tier2Accounts[0].account.id, tier2Accounts[0].account.id,
tier2Accounts[0].account.parent_account_id, tier2Accounts[0].account.parent_account_id ?? 0,
null, null,
10, 10,
'Order Canceled', 'Order Canceled',

View File

@ -33,20 +33,20 @@ export class LicensesService {
* @param body * @param body
*/ */
async licenseOrders( async licenseOrders(
accessToken: AccessToken, externalId: string,
poNumber: string, poNumber: string,
orderCount: number, orderCount: number,
): Promise<void> { ): Promise<void> {
//アクセストークンからユーザーIDを取得する //アクセストークンからユーザーIDを取得する
this.logger.log(`[IN] ${this.licenseOrders.name}`); this.logger.log(`[IN] ${this.licenseOrders.name}`);
const userId = accessToken.userId;
let myAccountId: number; let myAccountId: number;
let parentAccountId: number; let parentAccountId: number | undefined;
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
try { try {
myAccountId = (await this.usersRepository.findUserByExternalId(userId)) myAccountId = (
.account_id; await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
switch (e.constructor) { switch (e.constructor) {
@ -65,9 +65,13 @@ export class LicensesService {
// 親アカウントIDを取得 // 親アカウントIDを取得
try { try {
parentAccountId = ( parentAccountId =
await this.accountsRepository.findAccountById(myAccountId) (await this.accountsRepository.findAccountById(myAccountId))
).parent_account_id; .parent_account_id ?? undefined;
// 親アカウントIDが取得できない場合はエラー
if (parentAccountId === undefined) {
throw new Error('parent account id is undefined');
}
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
switch (e.constructor) { switch (e.constructor) {

View File

@ -124,11 +124,11 @@ export const makeDefaultUsersRepositoryMockValue =
user1.notification = false; user1.notification = false;
user1.encryption = false; user1.encryption = false;
user1.prompt = false; user1.prompt = false;
user1.deleted_at = undefined; user1.deleted_at = null;
user1.created_by = 'test'; user1.created_by = 'test';
user1.created_at = new Date(); user1.created_at = new Date();
user1.updated_by = undefined; user1.updated_by = null;
user1.updated_at = undefined; user1.updated_at = new Date();
return { return {
findUserByExternalId: user1, findUserByExternalId: user1,

View File

@ -12,14 +12,14 @@ import {
export const createLicense = async ( export const createLicense = async (
datasource: DataSource, datasource: DataSource,
licenseId: number, licenseId: number,
expiry_date: Date, expiry_date: Date | null,
accountId: number, accountId: number,
type: string, type: string,
status: string, status: string,
allocated_user_id: number, allocated_user_id: number | null,
order_id: number, order_id: number | null,
deleted_at: Date, deleted_at: Date | null,
delete_order_id: number, delete_order_id: number | null,
): Promise<void> => { ): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({ const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId, id: licenseId,
@ -107,7 +107,7 @@ export const createOrder = async (
poNumber: string, poNumber: string,
fromId: number, fromId: number,
toId: number, toId: number,
issuedAt: Date, issuedAt: Date | null,
quantity: number, quantity: number,
status: string, status: string,
): Promise<void> => { ): Promise<void> => {
@ -138,7 +138,7 @@ export const selectCardLicensesCount = async (
export const selectCardLicense = async ( export const selectCardLicense = async (
datasource: DataSource, datasource: DataSource,
cardLicenseKey: string, cardLicenseKey: string,
): Promise<{ cardLicense: CardLicense }> => { ): Promise<{ cardLicense: CardLicense | null }> => {
const cardLicense = await datasource.getRepository(CardLicense).findOne({ const cardLicense = await datasource.getRepository(CardLicense).findOne({
where: { where: {
card_license_key: cardLicenseKey, card_license_key: cardLicenseKey,
@ -150,7 +150,7 @@ export const selectCardLicense = async (
export const selectLicense = async ( export const selectLicense = async (
datasource: DataSource, datasource: DataSource,
id: number, id: number,
): Promise<{ license: License }> => { ): Promise<{ license: License | null }> => {
const license = await datasource.getRepository(License).findOne({ const license = await datasource.getRepository(License).findOne({
where: { where: {
id: id, id: id,
@ -163,7 +163,7 @@ export const selectLicenseAllocationHistory = async (
datasource: DataSource, datasource: DataSource,
userId: number, userId: number,
licence_id: number, licence_id: number,
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory }> => { ): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
const licenseAllocationHistory = await datasource const licenseAllocationHistory = await datasource
.getRepository(LicenseAllocationHistory) .getRepository(LicenseAllocationHistory)
.findOne({ .findOne({
@ -182,7 +182,7 @@ export const selectOrderLicense = async (
datasource: DataSource, datasource: DataSource,
accountId: number, accountId: number,
poNumber: string, poNumber: string,
): Promise<{ orderLicense: LicenseOrder }> => { ): Promise<{ orderLicense: LicenseOrder | null }> => {
const orderLicense = await datasource.getRepository(LicenseOrder).findOne({ const orderLicense = await datasource.getRepository(LicenseOrder).findOne({
where: { where: {
from_account_id: accountId, from_account_id: accountId,

View File

@ -47,8 +47,8 @@ export class GetAllocatableLicensesRequest {}
export class AllocatableLicenseInfo { export class AllocatableLicenseInfo {
@ApiProperty() @ApiProperty()
licenseId: number; licenseId: number;
@ApiProperty() @ApiProperty({ required: false })
expiryDate: Date; expiryDate?: Date;
} }
export class GetAllocatableLicensesResponse { export class GetAllocatableLicensesResponse {
@ApiProperty({ type: [AllocatableLicenseInfo] }) @ApiProperty({ type: [AllocatableLicenseInfo] })

View File

@ -77,14 +77,14 @@ export const makeDefaultUsersRepositoryMockValue =
user.external_id = 'external_id'; user.external_id = 'external_id';
user.account_id = 123; user.account_id = 123;
user.role = 'none'; user.role = 'none';
user.author_id = undefined; user.author_id = null;
user.accepted_eula_version = '1.0'; user.accepted_eula_version = '1.0';
user.accepted_dpa_version = '1.0'; user.accepted_dpa_version = '1.0';
user.email_verified = true; user.email_verified = true;
user.auto_renew = false; user.auto_renew = false;
user.license_alert = false; user.license_alert = false;
user.notification = false; user.notification = false;
user.deleted_at = undefined; user.deleted_at = null;
user.created_by = 'test'; user.created_by = 'test';
user.created_at = new Date(); user.created_at = new Date();
user.updated_by = 'test'; user.updated_by = 'test';

View File

@ -3,6 +3,7 @@ import {
Controller, Controller,
Get, Get,
Headers, Headers,
HttpException,
HttpStatus, HttpStatus,
Param, Param,
ParseIntPipe, ParseIntPipe,
@ -32,6 +33,8 @@ import {
TasksResponse, TasksResponse,
} from './types/types'; } from './types/types';
import { import {
SortDirection,
TaskListSortableAttribute,
isSortDirection, isSortDirection,
isTaskListSortableAttribute, isTaskListSortableAttribute,
} from '../../common/types/sort'; } from '../../common/types/sort';
@ -43,6 +46,7 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, USER_ROLES } from '../../constants'; import { ADMIN_ROLES, USER_ROLES } from '../../constants';
import { Roles } from '../../common/types/role'; import { Roles } from '../../common/types/role';
import { makeContext } from '../../common/log'; import { makeContext } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('tasks') @ApiTags('tasks')
@Controller('tasks') @Controller('tasks')
@ -80,22 +84,38 @@ export class TasksController {
@Req() req, @Req() req,
@Query() body: TasksRequest, @Query() body: TasksRequest,
): Promise<TasksResponse> { ): Promise<TasksResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const decodedToken = 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, 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 { limit, offset, status } = body;
const paramName = isTaskListSortableAttribute(body.paramName) const paramName = isTaskListSortableAttribute(body.paramName ?? '')
? body.paramName ? (body.paramName as TaskListSortableAttribute)
: undefined; : undefined;
const direction = isSortDirection(body.direction) const direction = isSortDirection(body.direction ?? '')
? body.direction ? (body.direction as SortDirection)
: undefined; : undefined;
const { tasks, total } = await this.taskService.getTasks( const { tasks, total } = await this.taskService.getTasks(
context, context,
decodedToken, userId,
roles,
offset, offset,
limit, limit,
// statusが指定されていない場合は全てのステータスを取得する // statusが指定されていない場合は全てのステータスを取得する
@ -183,10 +203,22 @@ export class TasksController {
@Param() param: ChangeStatusRequest, @Param() param: ChangeStatusRequest,
): Promise<ChangeStatusResponse> { ): Promise<ChangeStatusResponse> {
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { role, userId } = jwt.decode(accessToken, { const accessToken = retrieveAuthorizationToken(req) as string;
json: true, if (!accessToken) {
}) as 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の文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
@ -241,10 +273,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> { ): Promise<ChangeStatusResponse> {
const { audioFileId } = params; const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(accessToken, { const accessToken = retrieveAuthorizationToken(req) as string;
json: true, if (!accessToken) {
}) as 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); const context = makeContext(userId);
@ -296,10 +340,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> { ): Promise<ChangeStatusResponse> {
const { audioFileId } = params; const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId, role } = jwt.decode(accessToken, { const accessToken = retrieveAuthorizationToken(req) as string;
json: true, if (!accessToken) {
}) as 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の文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
@ -353,10 +409,22 @@ export class TasksController {
): Promise<ChangeStatusResponse> { ): Promise<ChangeStatusResponse> {
const { audioFileId } = params; const { audioFileId } = params;
// AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない // AuthGuardでチェック済みなのでここでのアクセストークンチェックはしない
const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(accessToken, { const accessToken = retrieveAuthorizationToken(req) as string;
json: true, if (!accessToken) {
}) as 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 context = makeContext(userId);
@ -491,11 +559,22 @@ export class TasksController {
@Body() body: PostCheckoutPermissionRequest, @Body() body: PostCheckoutPermissionRequest,
): Promise<PostCheckoutPermissionResponse> { ): Promise<PostCheckoutPermissionResponse> {
const { assignees } = body; const { assignees } = body;
const accessToken = retrieveAuthorizationToken(req);
const { role, userId } = jwt.decode(accessToken, { const accessToken = retrieveAuthorizationToken(req) as string;
json: true, if (!accessToken) {
}) as 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の文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];

File diff suppressed because it is too large Load Diff

View File

@ -45,10 +45,10 @@ export class TasksService {
private readonly notificationhubService: NotificationhubService, private readonly notificationhubService: NotificationhubService,
) {} ) {}
// TODO [Task2244] 引数にAccessTokenがあるのは不適切なのでController側で分解したい
async getTasks( async getTasks(
context: Context, context: Context,
accessToken: AccessToken, userId: string,
roles: Roles[],
offset: number, offset: number,
limit: number, limit: number,
status?: string[], 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} };`, `[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 defaultParamName: TaskListSortableAttribute = 'JOB_NUMBER';
const defaultDirection: SortDirection = 'ASC'; const defaultDirection: SortDirection = 'ASC';
@ -95,6 +91,10 @@ export class TasksService {
return { tasks: tasks, total: result.count }; return { tasks: tasks, total: result.count };
} }
if (roles.includes(USER_ROLES.AUTHOR)) { if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
throw new Error('AuthorID not found');
}
const result = const result =
await this.taskRepository.getTasksFromAuthorIdAndAccountId( await this.taskRepository.getTasksFromAuthorIdAndAccountId(
author_id, author_id,
@ -179,6 +179,10 @@ export class TasksService {
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(externalId);
if (roles.includes(USER_ROLES.AUTHOR)) { if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
throw new Error('AuthorID not found');
}
await this.taskRepository.getTaskFromAudioFileId( await this.taskRepository.getTaskFromAudioFileId(
audioFileId, audioFileId,
account_id, account_id,
@ -407,30 +411,21 @@ export class TasksService {
permissions: CheckoutPermission[], permissions: CheckoutPermission[],
): Promise<AdB2cUser[]> { ): Promise<AdB2cUser[]> {
// 割り当て候補の外部IDを列挙 // 割り当て候補の外部IDを列挙
const assigneesExternalIds = permissions.map((x) => { const assigneesExternalIds = permissions.flatMap((permission) =>
if (x.user) { permission.user ? [permission.user.external_id] : [],
return x.user.external_id; );
}
});
// 割り当てられているタイピストの外部IDを列挙 // 割り当てられているタイピストの外部IDを列挙
const typistExternalIds = tasks.flatMap((x) => { const typistExternalIds = tasks.flatMap((task) =>
if (x.typist_user) { task.typist_user ? [task.typist_user.external_id] : [],
return x.typist_user.external_id; );
}
});
//重複をなくす //重複をなくす
const distinctedExternalIds = [ const distinctedExternalIds = [
...new Set(assigneesExternalIds.concat(typistExternalIds)), ...new Set(assigneesExternalIds.concat(typistExternalIds)),
]; ];
// undefinedがあった場合、取り除く
const filteredExternalIds: string[] = distinctedExternalIds.filter(
(x): x is string => x !== undefined,
);
// B2Cからユーザー名を取得する // 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 } = const { author_id, account_id } =
await this.usersRepository.findUserByExternalId(externalId); 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( await this.taskRepository.changeCheckoutPermission(
audioFileId, audioFileId,
author_id, author_id ?? undefined,
account_id, account_id,
role, role,
assignees, assignees,
@ -462,11 +461,16 @@ export class TasksService {
// すべての割り当て候補ユーザーを取得する // すべての割り当て候補ユーザーを取得する
const assigneesGroupIds = assignees const assigneesGroupIds = assignees
.filter((x) => x.typistGroupId) .filter((assignee) => assignee.typistGroupId)
.map((x) => x.typistGroupId); .flatMap((assignee) =>
assignee.typistGroupId ? [assignee.typistGroupId] : [],
);
const assigneesUserIds = assignees const assigneesUserIds = assignees
.filter((x) => x.typistUserId) .filter((assignee) => assignee.typistUserId)
.map((x) => x.typistUserId); .flatMap((assignee) =>
assignee.typistUserId ? [assignee.typistUserId] : [],
);
const groupMembers = const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(

View File

@ -263,6 +263,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1, user_id: 1,
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
}, },
{ {
id: 2, id: 2,
@ -270,6 +275,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 2, user_id: 2,
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
}, },
{ {
id: 3, id: 3,
@ -277,6 +287,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1, user_id: 1,
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
}, },
{ {
id: 4, id: 4,
@ -284,6 +299,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 1, user_id: 1,
created_by: 'test', created_by: 'test',
updated_by: 'test', updated_by: 'test',
created_at: new Date(),
deleted_at: null,
updated_at: null,
user: null,
userGroup: null,
}, },
{ {
id: 5, id: 5,
@ -291,6 +311,11 @@ export const makeDefaultUserGroupsRepositoryMockValue =
user_id: 3, user_id: 3,
created_by: 'test', created_by: 'test',
updated_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.auto_renew = false;
user1.license_alert = false; user1.license_alert = false;
user1.notification = false; user1.notification = false;
user1.deleted_at = undefined; user1.deleted_at = null;
user1.created_by = 'test'; user1.created_by = 'test';
user1.created_at = new Date(); user1.created_at = new Date();
user1.author_id = 'abcdef'; user1.author_id = 'abcdef';
@ -331,66 +356,82 @@ const defaultTasksRepositoryMockValue: {
status: 'Uploaded', status: 'Uploaded',
priority: '00', priority: '00',
created_at: new Date('2023-01-01T01:01:01.000Z'), 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: [ option_items: [
{ {
id: 1, id: 1,
audio_file_id: 1, audio_file_id: 1,
label: 'label01', label: 'label01',
value: 'value01', value: 'value01',
task: null,
}, },
{ {
id: 2, id: 2,
audio_file_id: 1, audio_file_id: 1,
label: 'label02', label: 'label02',
value: 'value02', value: 'value02',
task: null,
}, },
{ {
id: 3, id: 3,
audio_file_id: 1, audio_file_id: 1,
label: 'label03', label: 'label03',
value: 'value03', value: 'value03',
task: null,
}, },
{ {
id: 4, id: 4,
audio_file_id: 1, audio_file_id: 1,
label: 'label04', label: 'label04',
value: 'value04', value: 'value04',
task: null,
}, },
{ {
id: 5, id: 5,
audio_file_id: 1, audio_file_id: 1,
label: 'label05', label: 'label05',
value: 'value05', value: 'value05',
task: null,
}, },
{ {
id: 6, id: 6,
audio_file_id: 1, audio_file_id: 1,
label: 'label06', label: 'label06',
value: 'value06', value: 'value06',
task: null,
}, },
{ {
id: 7, id: 7,
audio_file_id: 1, audio_file_id: 1,
label: 'label07', label: 'label07',
value: 'value07', value: 'value07',
task: null,
}, },
{ {
id: 8, id: 8,
audio_file_id: 1, audio_file_id: 1,
label: 'label08', label: 'label08',
value: 'value08', value: 'value08',
task: null,
}, },
{ {
id: 9, id: 9,
audio_file_id: 1, audio_file_id: 1,
label: 'label09', label: 'label09',
value: 'value09', value: 'value09',
task: null,
}, },
{ {
id: 10, id: 10,
audio_file_id: 1, audio_file_id: 1,
label: 'label10', label: 'label10',
value: 'value10', value: 'value10',
task: null,
}, },
], ],
file: { file: {
@ -410,6 +451,8 @@ const defaultTasksRepositoryMockValue: {
audio_format: 'DS', audio_format: 'DS',
comment: 'comment', comment: 'comment',
is_encrypted: true, is_encrypted: true,
deleted_at: null,
task: null,
}, },
}, },
], ],
@ -435,7 +478,16 @@ const defaultTasksRepositoryMockValue: {
created_at: new Date(), created_at: new Date(),
updated_by: 'test', updated_by: 'test',
updated_at: new Date(), 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, count: 1,

View File

@ -39,10 +39,10 @@ import {
makeNotificationhubServiceMock, makeNotificationhubServiceMock,
} from './tasks.service.mock'; } from './tasks.service.mock';
export const makeTaskTestingModule = async ( export const makeTaskTestingModuleWithNotificaiton = async (
datasource: DataSource, datasource: DataSource,
notificationhubServiceMockValue: NotificationhubServiceMockValue, notificationhubServiceMockValue: NotificationhubServiceMockValue,
): Promise<TestingModule> => { ): Promise<TestingModule | undefined> => {
try { try {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [
@ -205,7 +205,7 @@ export const createUserGroup = async (
export const getTask = async ( export const getTask = async (
datasource: DataSource, datasource: DataSource,
task_id: number, task_id: number,
): Promise<Task> => { ): Promise<Task | null> => {
const task = await datasource.getRepository(Task).findOne({ const task = await datasource.getRepository(Task).findOne({
where: { where: {
id: task_id, id: task_id,

View File

@ -45,7 +45,7 @@ const createTask = (
const assignees = createAssignees(permissions, b2cUserInfo); const assignees = createAssignees(permissions, b2cUserInfo);
// RepositoryDTO => ControllerDTOに変換 // RepositoryDTO => ControllerDTOに変換
const typist: Typist = const typist: Typist | undefined =
typist_user != null typist_user != null
? convertUserToTypist(typist_user, b2cUserInfo) ? convertUserToTypist(typist_user, b2cUserInfo)
: undefined; : undefined;
@ -113,7 +113,10 @@ const convertUserToAssignee = (
): Assignee => { ): Assignee => {
const typistName = b2cUserInfo.find( const typistName = b2cUserInfo.find(
(x) => x.id === user.external_id, (x) => x.id === user.external_id,
).displayName; )?.displayName;
if (!typistName) {
throw new Error('typistName not found.');
}
return { return {
typistUserId: user.id, typistUserId: user.id,
typistName, typistName,
@ -135,7 +138,10 @@ const convertUserToTypist = (
): Typist => { ): Typist => {
const typistName = b2cUserInfo.find( const typistName = b2cUserInfo.find(
(x) => x.id === user.external_id, (x) => x.id === user.external_id,
).displayName; )?.displayName;
if (!typistName) {
throw new Error('typistName not found.');
}
return { return {
id: user.id, id: user.id,
name: typistName, name: typistName,

View File

@ -66,7 +66,7 @@ describe('getTemplates', () => {
expect(templates[1].id).toBe(template2.id); expect(templates[1].id).toBe(template2.id);
expect(templates[1].name).toBe(template2.file_name); expect(templates[1].name).toBe(template2.file_name);
} }
}, 6000000); });
it('テンプレートファイル一覧を取得できる0件', async () => { it('テンプレートファイル一覧を取得できる0件', async () => {
if (!source) fail(); if (!source) fail();

View File

@ -23,6 +23,9 @@ export const createTemplateFile = async (
id: template.id, id: template.id,
}, },
}); });
if (!templateFile) {
fail();
}
return templateFile; return templateFile;
}; };

View File

@ -8,7 +8,7 @@ import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
describe('利用規約取得', () => { describe('利用規約取得', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -21,12 +21,15 @@ describe('利用規約取得', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('最新の利用規約情報が取得できる', async () => { it('最新の利用規約情報が取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
await createTermInfo(source, 'EULA', 'v1.0'); await createTermInfo(source, 'EULA', 'v1.0');
@ -44,7 +47,9 @@ describe('利用規約取得', () => {
}); });
it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => { it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
const context = makeContext(uuidv4()); const context = makeContext(uuidv4());
await expect(service.getTermsInfo(context)).rejects.toEqual( await expect(service.getTermsInfo(context)).rejects.toEqual(
@ -56,7 +61,9 @@ describe('利用規約取得', () => {
}); });
it('利用規約情報(EULAのみ)が存在しない場合エラーとなる', async () => { it('利用規約情報(EULAのみ)が存在しない場合エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
await createTermInfo(source, 'DPA', 'v1.0'); await createTermInfo(source, 'DPA', 'v1.0');
const context = makeContext(uuidv4()); const context = makeContext(uuidv4());
@ -69,7 +76,9 @@ describe('利用規約取得', () => {
}); });
it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => { it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
await createTermInfo(source, 'EULA', 'v1.0'); await createTermInfo(source, 'EULA', 'v1.0');
const context = makeContext(uuidv4()); const context = makeContext(uuidv4());

View File

@ -64,7 +64,7 @@ export type ConfigMockValue = {
export const makeUsersServiceMock = async ( export const makeUsersServiceMock = async (
usersRepositoryMockValue: UsersRepositoryMockValue, usersRepositoryMockValue: UsersRepositoryMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue, licensesRepositoryMockValue: LicensesRepositoryMockValue | null,
adB2cMockValue: AdB2cMockValue, adB2cMockValue: AdB2cMockValue,
sendGridMockValue: SendGridMockValue, sendGridMockValue: SendGridMockValue,
configMockValue: ConfigMockValue, configMockValue: ConfigMockValue,
@ -368,7 +368,7 @@ export const makeDefaultUsersRepositoryMockValue =
user1.created_by = 'test'; user1.created_by = 'test';
user1.created_at = new Date(); user1.created_at = new Date();
user1.updated_by = null; user1.updated_by = null;
user1.updated_at = null; user1.updated_at = new Date();
const user2 = new User(); const user2 = new User();
user2.id = 3; user2.id = 3;
@ -388,7 +388,7 @@ export const makeDefaultUsersRepositoryMockValue =
user2.created_by = 'test'; user2.created_by = 'test';
user2.created_at = new Date(); user2.created_at = new Date();
user2.updated_by = null; user2.updated_by = null;
user2.updated_at = null; user2.updated_at = new Date();
return { return {
updateUserVerified: undefined, updateUserVerified: undefined,
@ -406,7 +406,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
displayName: 'test1', displayName: 'test1',
identities: [ identities: [
{ {
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: 'issuer', issuer: 'issuer',
issuerAssignedId: 'test1@mail.com', issuerAssignedId: 'test1@mail.com',
}, },
@ -417,7 +417,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
displayName: 'test2', displayName: 'test2',
identities: [ identities: [
{ {
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: 'issuer', issuer: 'issuer',
issuerAssignedId: 'test2@mail.com', issuerAssignedId: 'test2@mail.com',
}, },
@ -428,7 +428,7 @@ const AdB2cMockUsers: AdB2cUser[] = [
displayName: 'test3', displayName: 'test3',
identities: [ identities: [
{ {
signInType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: 'issuer', issuer: 'issuer',
issuerAssignedId: 'test3@mail.com', issuerAssignedId: 'test3@mail.com',
}, },

View File

@ -107,7 +107,7 @@ export const createLicense = async (
export const makeTestingModuleWithAdb2c = async ( export const makeTestingModuleWithAdb2c = async (
datasource: DataSource, datasource: DataSource,
adB2cMockValue: AdB2cMockValue, adB2cMockValue: AdB2cMockValue,
): Promise<TestingModule> => { ): Promise<TestingModule | undefined> => {
try { try {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [ imports: [

View File

@ -130,10 +130,23 @@ export class UsersController {
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get() @Get()
async getUsers(@Req() req: Request): Promise<GetUsersResponse> { async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const decodedToken = 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 users = await this.usersService.getUsers(decodedToken.userId); const users = await this.usersService.getUsers(userId);
return { users }; return { users };
} }
@ -179,15 +192,28 @@ export class UsersController {
prompt, prompt,
} = body; } = body;
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const payload = 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(payload.userId); const context = makeContext(userId);
//ユーザ作成処理 //ユーザ作成処理
await this.usersService.createUser( await this.usersService.createUser(
context, context,
payload, userId,
name, name,
role as UserRoles, role as UserRoles,
email, email,
@ -225,8 +251,21 @@ export class UsersController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('relations') @Get('relations')
async getRelations(@Req() req: Request): Promise<GetRelationsResponse> { async getRelations(@Req() req: Request): Promise<GetRelationsResponse> {
const token = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
const { userId } = jwt.decode(token, { 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); const context = makeContext(userId);
@ -265,8 +304,22 @@ export class UsersController {
@Req() req: Request, @Req() req: Request,
): Promise<PostSortCriteriaResponse> { ): Promise<PostSortCriteriaResponse> {
const { direction, paramName } = body; 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 ( if (
@ -278,11 +331,7 @@ export class UsersController {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
await this.usersService.updateSortCriteria( await this.usersService.updateSortCriteria(paramName, direction, userId);
paramName,
direction,
decodedToken,
);
return {}; return {};
} }
@ -313,11 +362,25 @@ export class UsersController {
@Req() req: Request, @Req() req: Request,
): Promise<GetSortCriteriaResponse> { ): Promise<GetSortCriteriaResponse> {
const {} = query; 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( const { direction, paramName } = await this.usersService.getSortCriteria(
decodedToken, userId,
); );
return { direction, paramName }; return { direction, paramName };
} }
@ -367,7 +430,20 @@ export class UsersController {
} = body; } = body;
const accessToken = retrieveAuthorizationToken(req); 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); const context = makeContext(userId);
@ -421,8 +497,21 @@ export class UsersController {
@Body() body: AllocateLicenseRequest, @Body() body: AllocateLicenseRequest,
@Req() req: Request, @Req() req: Request,
): Promise<AllocateLicenseResponse> { ): Promise<AllocateLicenseResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
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); const context = makeContext(userId);
await this.usersService.allocateLicense( await this.usersService.allocateLicense(
@ -467,8 +556,21 @@ export class UsersController {
@Body() body: DeallocateLicenseRequest, @Body() body: DeallocateLicenseRequest,
@Req() req: Request, @Req() req: Request,
): Promise<DeallocateLicenseResponse> { ): Promise<DeallocateLicenseResponse> {
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req) as string;
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); const context = makeContext(userId);

View File

@ -46,7 +46,7 @@ import {
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
describe('UsersService.confirmUser', () => { describe('UsersService.confirmUser', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -57,12 +57,15 @@ describe('UsersService.confirmUser', () => {
return source.initialize(); return source.initialize();
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになり、トライアルライセンスが100件作成される', async () => { it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになり、トライアルライセンスが100件作成される', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = (await makeTestAccount(source)).account; const { id: accountId } = (await makeTestAccount(source)).account;
const { id: userId } = await makeTestUser(source, { const { id: userId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -110,9 +113,10 @@ describe('UsersService.confirmUser', () => {
allocated_user_id: resultLicenses[0].allocated_user_id, allocated_user_id: resultLicenses[0].allocated_user_id,
order_id: resultLicenses[0].order_id, order_id: resultLicenses[0].order_id,
delete_order_id: resultLicenses[0].delete_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(resultLicenses.length).toBe(100);
expect(resultLicensesCheckParam).toEqual({ expect(resultLicensesCheckParam).toEqual({
id: 0, id: 0,
@ -123,11 +127,14 @@ describe('UsersService.confirmUser', () => {
allocated_user_id: null, allocated_user_id: null,
order_id: null, order_id: null,
delete_order_id: null, delete_order_id: null,
user: null,
}); });
}); }, 600000);
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => { it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const token = 'invalid.id.token'; const token = 'invalid.id.token';
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await expect(service.confirmUser(token)).rejects.toEqual( await expect(service.confirmUser(token)).rejects.toEqual(
@ -136,7 +143,9 @@ describe('UsersService.confirmUser', () => {
}); });
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => { it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = (await makeTestAccount(source)).account; const { id: accountId } = (await makeTestAccount(source)).account;
await makeTestUser(source, { await makeTestUser(source, {
account_id: accountId, account_id: accountId,
@ -168,7 +177,9 @@ describe('UsersService.confirmUser', () => {
); );
}); });
it('ユーザーが存在しない場合は、想定外のエラーとなる', async () => { it('ユーザーが存在しない場合は、想定外のエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
@ -201,6 +212,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true, notification: true,
encryption: false, encryption: false,
prompt: false, prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
}; };
const licensesRepositoryMockValue = null; const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
@ -246,6 +263,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true, notification: true,
encryption: false, encryption: false,
prompt: false, prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
}; };
const licensesRepositoryMockValue = null; const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
@ -287,6 +310,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true, notification: true,
encryption: false, encryption: false,
prompt: false, prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
}; };
const licensesRepositoryMockValue = null; const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
@ -332,6 +361,12 @@ describe('UsersService.confirmUserAndInitPassword', () => {
notification: true, notification: true,
encryption: false, encryption: false,
prompt: false, prompt: false,
account: null,
author_id: null,
deleted_at: null,
encryption_password: null,
license: null,
userGroupMembers: null,
}; };
const licensesRepositoryMockValue = null; const licensesRepositoryMockValue = null;
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
@ -362,7 +397,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
}); });
describe('UsersService.createUser', () => { describe('UsersService.createUser', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -375,12 +410,15 @@ describe('UsersService.createUser', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
@ -433,7 +471,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -446,16 +484,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認 // 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId); const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull(); expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId); expect(user?.account_id).toEqual(accountId);
expect(user.role).toEqual(role); expect(user?.role).toEqual(role);
expect(user.author_id).toEqual(null); expect(user?.author_id).toEqual(null);
expect(user.email_verified).toEqual(false); expect(user?.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew); expect(user?.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert); expect(user?.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification); expect(user?.notification).toEqual(notification);
expect(user.encryption).toEqual(false); expect(user?.encryption).toEqual(false);
expect(user.encryption_password).toEqual(null); expect(user?.encryption_password).toEqual(null);
expect(user.prompt).toEqual(false); expect(user?.prompt).toEqual(false);
// 他にユーザーが登録されていないことを確認 // 他にユーザーが登録されていないことを確認
const users = await getUsers(source); const users = await getUsers(source);
@ -463,7 +501,9 @@ describe('UsersService.createUser', () => {
}); });
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化あり)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -519,7 +559,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -536,16 +576,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認 // 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId); const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull(); expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId); expect(user?.account_id).toEqual(accountId);
expect(user.role).toEqual(role); expect(user?.role).toEqual(role);
expect(user.author_id).toEqual(authorId); expect(user?.author_id).toEqual(authorId);
expect(user.email_verified).toEqual(false); expect(user?.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew); expect(user?.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert); expect(user?.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification); expect(user?.notification).toEqual(notification);
expect(user.encryption).toEqual(encryption); expect(user?.encryption).toEqual(encryption);
expect(user.encryption_password).toEqual(encryptionPassword); expect(user?.encryption_password).toEqual(encryptionPassword);
expect(user.prompt).toEqual(prompt); expect(user?.prompt).toEqual(prompt);
// 他にユーザーが登録されていないことを確認 // 他にユーザーが登録されていないことを確認
const users = await getUsers(source); const users = await getUsers(source);
@ -553,7 +593,9 @@ describe('UsersService.createUser', () => {
}); });
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author; 暗号化無し)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -608,7 +650,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -625,16 +667,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認 // 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId); const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull(); expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId); expect(user?.account_id).toEqual(accountId);
expect(user.role).toEqual(role); expect(user?.role).toEqual(role);
expect(user.author_id).toEqual(authorId); expect(user?.author_id).toEqual(authorId);
expect(user.email_verified).toEqual(false); expect(user?.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew); expect(user?.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert); expect(user?.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification); expect(user?.notification).toEqual(notification);
expect(user.encryption).toEqual(encryption); expect(user?.encryption).toEqual(encryption);
expect(user.encryption_password).toBeNull(); expect(user?.encryption_password).toBeNull();
expect(user.prompt).toEqual(prompt); expect(user?.prompt).toEqual(prompt);
// 他にユーザーが登録されていないことを確認 // 他にユーザーが登録されていないことを確認
const users = await getUsers(source); const users = await getUsers(source);
@ -642,7 +684,9 @@ describe('UsersService.createUser', () => {
}); });
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -694,7 +738,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -707,16 +751,16 @@ describe('UsersService.createUser', () => {
// 追加されたユーザーが正しくDBに登録されていることを確認 // 追加されたユーザーが正しくDBに登録されていることを確認
const user = await getUserFromExternalId(source, externalId); const user = await getUserFromExternalId(source, externalId);
expect(user).not.toBeNull(); expect(user).not.toBeNull();
expect(user.account_id).toEqual(accountId); expect(user?.account_id).toEqual(accountId);
expect(user.role).toEqual(role); expect(user?.role).toEqual(role);
expect(user.author_id).toBeNull(); expect(user?.author_id).toBeNull();
expect(user.email_verified).toEqual(false); expect(user?.email_verified).toEqual(false);
expect(user.auto_renew).toEqual(autoRenew); expect(user?.auto_renew).toEqual(autoRenew);
expect(user.license_alert).toEqual(licenseAlert); expect(user?.license_alert).toEqual(licenseAlert);
expect(user.notification).toEqual(notification); expect(user?.notification).toEqual(notification);
expect(user.encryption).toEqual(false); expect(user?.encryption).toEqual(false);
expect(user.encryption_password).toBeNull(); expect(user?.encryption_password).toBeNull();
expect(user.prompt).toEqual(false); expect(user?.prompt).toEqual(false);
// 他にユーザーが登録されていないことを確認 // 他にユーザーが登録されていないことを確認
const users = await getUsers(source); const users = await getUsers(source);
@ -724,7 +768,9 @@ describe('UsersService.createUser', () => {
}); });
it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => { it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
@ -785,7 +831,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -809,7 +855,9 @@ describe('UsersService.createUser', () => {
}); });
it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => { it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
@ -870,7 +918,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -899,7 +947,9 @@ describe('UsersService.createUser', () => {
}); });
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => { it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -949,7 +999,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -972,7 +1022,9 @@ describe('UsersService.createUser', () => {
}); });
it('Azure AD B2C内でメールアドレスが重複している場合、エラーとなる。', async () => { it('Azure AD B2C内でメールアドレスが重複している場合、エラーとなる。', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -1026,7 +1078,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -1049,7 +1101,9 @@ describe('UsersService.createUser', () => {
}); });
it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => { it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account, admin } = await makeTestAccount(
@ -1105,7 +1159,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email_1, email_1,
@ -1146,7 +1200,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email_2, email_2,
@ -1175,7 +1229,9 @@ describe('UsersService.createUser', () => {
}); });
it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => { it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
@ -1240,7 +1296,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -1272,7 +1328,9 @@ describe('UsersService.createUser', () => {
}); });
it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => { it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
@ -1327,7 +1385,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -1357,7 +1415,9 @@ describe('UsersService.createUser', () => {
}); });
it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => { it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
@ -1417,7 +1477,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId'),
token, adminExternalId,
name, name,
role, role,
email, email,
@ -1446,7 +1506,7 @@ describe('UsersService.createUser', () => {
}); });
describe('UsersService.getUsers', () => { describe('UsersService.getUsers', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -1459,13 +1519,16 @@ describe('UsersService.getUsers', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => { it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam); const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId_author, id: authorUserId } = const { external_id: externalId_author, id: authorUserId } =
@ -1565,7 +1628,9 @@ describe('UsersService.getUsers', () => {
it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => { it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam); const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { id: user1, external_id: external_id1 } = await makeTestUser( const { id: user1, external_id: external_id1 } = await makeTestUser(
@ -1679,7 +1744,9 @@ describe('UsersService.getUsers', () => {
it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => { it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam); const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, { await makeTestUser(source, {
@ -1702,7 +1769,9 @@ describe('UsersService.getUsers', () => {
it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => { it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => {
const adb2cParam = makeDefaultAdB2cMockValue(); const adb2cParam = makeDefaultAdB2cMockValue();
adb2cParam.getUsers = new Error('ADB2C error'); adb2cParam.getUsers = new Error('ADB2C error');
if (!source) fail();
const module = await makeTestingModuleWithAdb2c(source, adb2cParam); const module = await makeTestingModuleWithAdb2c(source, adb2cParam);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId_author } = await makeTestUser(source, { const { external_id: externalId_author } = await makeTestUser(source, {
@ -1742,11 +1811,7 @@ describe('UsersService.updateSortCriteria', () => {
); );
expect( expect(
await service.updateSortCriteria('AUTHOR_ID', 'ASC', { await service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).toEqual(undefined); ).toEqual(undefined);
}); });
@ -1771,11 +1836,7 @@ describe('UsersService.updateSortCriteria', () => {
); );
await expect( await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', { service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -1806,11 +1867,7 @@ describe('UsersService.updateSortCriteria', () => {
); );
await expect( await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', { service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -1838,13 +1895,10 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
expect( expect(await service.getSortCriteria('external_id')).toEqual({
await service.getSortCriteria({ direction: 'ASC',
role: 'none admin', paramName: 'JOB_NUMBER',
userId: 'xxxxxxxxxxxx', });
tier: 5,
}),
).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' });
}); });
it('ソート条件が存在せず、ソート条件を取得できない', async () => { it('ソート条件が存在せず、ソート条件を取得できない', async () => {
@ -1869,13 +1923,7 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
await expect( await expect(service.getSortCriteria('external_id')).rejects.toEqual(
service.getSortCriteria({
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
@ -1907,13 +1955,7 @@ describe('UsersService.getSortCriteria', () => {
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
await expect( await expect(service.getSortCriteria('external_id')).rejects.toEqual(
service.getSortCriteria({
role: 'none admin',
userId: 'xxxxxxxxxxxx',
tier: 5,
}),
).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
@ -1923,7 +1965,7 @@ describe('UsersService.getSortCriteria', () => {
}); });
describe('UsersService.updateUser', () => { describe('UsersService.updateUser', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -1936,12 +1978,15 @@ describe('UsersService.updateUser', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('ユーザー情報を更新できるNone', async () => { it('ユーザー情報を更新できるNone', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -1985,19 +2030,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.NONE); expect(createdUser?.role).toBe(USER_ROLES.NONE);
expect(createdUser.author_id).toBeNull(); expect(createdUser?.author_id).toBeNull();
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(false); expect(createdUser?.encryption).toBe(false);
expect(createdUser.encryption_password).toBeNull(); expect(createdUser?.encryption_password).toBeNull();
expect(createdUser.prompt).toBe(false); expect(createdUser?.prompt).toBe(false);
}); });
it('ユーザー情報を更新できるTypist', async () => { it('ユーザー情報を更新できるTypist', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2041,19 +2088,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.TYPIST); expect(createdUser?.role).toBe(USER_ROLES.TYPIST);
expect(createdUser.author_id).toBeNull(); expect(createdUser?.author_id).toBeNull();
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(false); expect(createdUser?.encryption).toBe(false);
expect(createdUser.encryption_password).toBeNull(); expect(createdUser?.encryption_password).toBeNull();
expect(createdUser.prompt).toBe(false); expect(createdUser?.prompt).toBe(false);
}); });
it('ユーザー情報を更新できるAuthor', async () => { it('ユーザー情報を更新できるAuthor', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2097,19 +2146,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.AUTHOR); expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
expect(createdUser.author_id).toBe('AUTHOR_ID'); expect(createdUser?.author_id).toBe('AUTHOR_ID');
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(true); expect(createdUser?.encryption).toBe(true);
expect(createdUser.encryption_password).toBe('new_password'); expect(createdUser?.encryption_password).toBe('new_password');
expect(createdUser.prompt).toBe(true); expect(createdUser?.prompt).toBe(true);
}); });
it('ユーザーのRoleを更新できるNone⇒Typist', async () => { it('ユーザーのRoleを更新できるNone⇒Typist', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2153,19 +2204,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.TYPIST); expect(createdUser?.role).toBe(USER_ROLES.TYPIST);
expect(createdUser.author_id).toBeNull(); expect(createdUser?.author_id).toBeNull();
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(false); expect(createdUser?.encryption).toBe(false);
expect(createdUser.encryption_password).toBeNull(); expect(createdUser?.encryption_password).toBeNull();
expect(createdUser.prompt).toBe(false); expect(createdUser?.prompt).toBe(false);
}); });
it('ユーザーのRoleを更新できるNone⇒Author', async () => { it('ユーザーのRoleを更新できるNone⇒Author', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2209,19 +2262,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.AUTHOR); expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
expect(createdUser.author_id).toBe('AUTHOR_ID'); expect(createdUser?.author_id).toBe('AUTHOR_ID');
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(false); expect(createdUser?.encryption).toBe(false);
expect(createdUser.encryption_password).toBeNull(); expect(createdUser?.encryption_password).toBeNull();
expect(createdUser.prompt).toBe(false); expect(createdUser?.prompt).toBe(false);
}); });
it('None以外からRoleを変更した場合、エラーとなるTypist⇒None', async () => { it('None以外からRoleを変更した場合、エラーとなるTypist⇒None', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2267,7 +2322,9 @@ describe('UsersService.updateUser', () => {
}); });
it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持するEncryptionがtrue', async () => { it('Authorがパスワードundefinedで渡されたとき、元のパスワードを維持するEncryptionがtrue', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2311,19 +2368,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.AUTHOR); expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
expect(createdUser.author_id).toBe('AUTHOR_ID'); expect(createdUser?.author_id).toBe('AUTHOR_ID');
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(true); expect(createdUser?.encryption).toBe(true);
expect(createdUser.encryption_password).toBe('password'); expect(createdUser?.encryption_password).toBe('password');
expect(createdUser.prompt).toBe(true); expect(createdUser?.prompt).toBe(true);
}); });
it('Authorが暗号化なしで更新した場合、パスワードをNULLにするEncryptionがfalse', async () => { it('Authorが暗号化なしで更新した場合、パスワードをNULLにするEncryptionがfalse', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2367,19 +2426,21 @@ describe('UsersService.updateUser', () => {
const createdUser = await getUser(source, user1); const createdUser = await getUser(source, user1);
expect(createdUser.id).toBe(user1); expect(createdUser?.id).toBe(user1);
expect(createdUser.role).toBe(USER_ROLES.AUTHOR); expect(createdUser?.role).toBe(USER_ROLES.AUTHOR);
expect(createdUser.author_id).toBe('AUTHOR_ID'); expect(createdUser?.author_id).toBe('AUTHOR_ID');
expect(createdUser.auto_renew).toBe(false); expect(createdUser?.auto_renew).toBe(false);
expect(createdUser.license_alert).toBe(false); expect(createdUser?.license_alert).toBe(false);
expect(createdUser.notification).toBe(false); expect(createdUser?.notification).toBe(false);
expect(createdUser.encryption).toBe(false); expect(createdUser?.encryption).toBe(false);
expect(createdUser.encryption_password).toBeNull(); expect(createdUser?.encryption_password).toBeNull();
expect(createdUser.prompt).toBe(true); expect(createdUser?.prompt).toBe(true);
}); });
it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなるEncryptionがtrue', async () => { it('AuthorのDBにパスワードが設定されていない場合、パスワードundefinedでわたすとエラーとなるEncryptionがtrue', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2425,7 +2486,9 @@ describe('UsersService.updateUser', () => {
}); });
it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => { it('AuthorIdが既存のユーザーと重複した場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source); const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: external_id } = await makeTestUser(source, { const { external_id: external_id } = await makeTestUser(source, {
@ -2483,7 +2546,7 @@ describe('UsersService.updateUser', () => {
}); });
describe('UsersService.updateAcceptedVersion', () => { describe('UsersService.updateAcceptedVersion', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -2496,12 +2559,15 @@ describe('UsersService.updateAcceptedVersion', () => {
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('同意済み利用規約バージョンを更新できる(第五)', async () => { it('同意済み利用規約バージョンを更新できる(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
}); });
@ -2511,11 +2577,13 @@ describe('UsersService.updateAcceptedVersion', () => {
await service.updateAcceptedVersion(context, admin.external_id, 'v2.0'); await service.updateAcceptedVersion(context, admin.external_id, 'v2.0');
const user = await getUser(source, admin.id); const user = await getUser(source, admin.id);
expect(user.accepted_eula_version).toBe('v2.0'); expect(user?.accepted_eula_version).toBe('v2.0');
}); });
it('同意済み利用規約バージョンを更新できる(第一~第四)', async () => { it('同意済み利用規約バージョンを更新できる(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
@ -2530,27 +2598,14 @@ describe('UsersService.updateAcceptedVersion', () => {
); );
const user = await getUser(source, admin.id); const user = await getUser(source, admin.id);
expect(user.accepted_eula_version).toBe('v2.0'); expect(user?.accepted_eula_version).toBe('v2.0');
expect(user.accepted_dpa_version).toBe('v3.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),
);
}); });
it('パラメータが不在のときエラーとなる(第一~第四)', async () => { it('パラメータが不在のときエラーとなる(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });

View File

@ -48,9 +48,13 @@ import {
LicenseExpiredError, LicenseExpiredError,
LicenseUnavailableError, LicenseUnavailableError,
} from '../../repositories/licenses/errors/types'; } from '../../repositories/licenses/errors/types';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@Injectable() @Injectable()
export class UsersService { export class UsersService {
private readonly logger = new Logger(UsersService.name);
private readonly mailFrom: string;
private readonly appDomain: string;
constructor( constructor(
private readonly usersRepository: UsersRepositoryService, private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService, private readonly licensesRepository: LicensesRepositoryService,
@ -58,8 +62,10 @@ export class UsersService {
private readonly adB2cService: AdB2cService, private readonly adB2cService: AdB2cService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly sendgridService: SendGridService, 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 * Confirms user
@ -128,7 +134,7 @@ export class UsersService {
*/ */
async createUser( async createUser(
context: Context, context: Context,
accessToken: AccessToken, externalId: string,
name: string, name: string,
role: UserRoles, role: UserRoles,
email: string, email: string,
@ -145,9 +151,7 @@ export class UsersService {
//DBよりアクセス者の所属するアカウントIDを取得する //DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser; let adminUser: EntityUser;
try { try {
adminUser = await this.usersRepository.findUserByExternalId( adminUser = await this.usersRepository.findUserByExternalId(externalId);
accessToken.userId,
);
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
throw new HttpException( throw new HttpException(
@ -254,9 +258,6 @@ export class UsersService {
//Email送信用のコンテンツを作成する //Email送信用のコンテンツを作成する
try { try {
// メールの送信元を取得
const from = this.configService.get<string>('MAIL_FROM') ?? '';
// メールの内容を構成 // メールの内容を構成
const { subject, text, html } = const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
@ -269,7 +270,7 @@ export class UsersService {
await this.sendgridService.sendMail( await this.sendgridService.sendMail(
context, context,
email, email,
from, this.mailFrom,
subject, subject,
text, text,
html, html,
@ -343,6 +344,12 @@ export class UsersService {
license_alert: licenseAlert, license_alert: licenseAlert,
notification, notification,
role, role,
accepted_dpa_version: null,
accepted_eula_version: null,
encryption: false,
encryption_password: null,
prompt: false,
author_id: null,
}; };
case USER_ROLES.AUTHOR: case USER_ROLES.AUTHOR:
return { return {
@ -352,10 +359,12 @@ export class UsersService {
license_alert: licenseAlert, license_alert: licenseAlert,
notification, notification,
role, role,
author_id: authorId, author_id: authorId ?? null,
encryption, encryption: encryption ?? false,
encryption_password: encryptionPassword, encryption_password: encryptionPassword ?? null,
prompt, prompt: prompt ?? false,
accepted_dpa_version: null,
accepted_eula_version: null,
}; };
default: default:
//不正なroleが指定された場合はログを出力してエラーを返す //不正なroleが指定された場合はログを出力してエラーを返す
@ -405,19 +414,16 @@ export class UsersService {
await this.adB2cService.changePassword(extarnalId, ramdomPassword); await this.adB2cService.changePassword(extarnalId, ramdomPassword);
// ユーザを認証済みにする // ユーザを認証済みにする
await this.usersRepository.updateUserVerified(userId); await this.usersRepository.updateUserVerified(userId);
// メールの送信元を取得
const from = this.configService.get<string>('MAIL_FROM') ?? '';
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする // TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
const subject = 'A temporary password has been issued.'; const subject = 'A temporary password has been issued.';
const text = 'temporary password: ' + ramdomPassword; const text = 'temporary password: ' + ramdomPassword;
const domains = this.configService.get<string>('APP_DOMAIN'); const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}"</a><br>temporary password: ${ramdomPassword}`;
const html = `<p>OMDS TOP PAGE URL.<p><a href="${domains}">${domains}"</a><br>temporary password: ${ramdomPassword}`;
// メールを送信 // メールを送信
await this.sendgridService.sendMail( await this.sendgridService.sendMail(
context, context,
email, email,
from, this.mailFrom,
subject, subject,
text, text,
html, html,
@ -464,17 +470,29 @@ export class UsersService {
); );
// DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出 // DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出
const users = dbUsers.map((x) => { const users = dbUsers.map((dbUser): User => {
// ユーザーの所属グループ名を取得する // ユーザーの所属グループ名を取得する
const groupNames = const userGroupMembers =
x.userGroupMembers?.map((group) => group.userGroup?.name) ?? []; 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( const mail = adb2cUser?.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EAMILADDRESS, (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
).issuerAssignedId; )?.issuerAssignedId;
//メールアドレスが取得できない場合はエラー
if (!mail) {
throw new Error('mail not found.');
}
let status = USER_LICENSE_STATUS.NORMAL; let status = USER_LICENSE_STATUS.NORMAL;
@ -483,21 +501,30 @@ export class UsersService {
let expiration: string | undefined = undefined; let expiration: string | undefined = undefined;
let remaining: number | undefined = undefined; let remaining: number | undefined = undefined;
if (x.license) { if (dbUser.license) {
// 有効期限日付 YYYY/MM/DD // 有効期限日付 YYYY/MM/DD
const expiry_date = x.license.expiry_date; const expiry_date = dbUser.license.expiry_date ?? undefined;
expiration = `${expiry_date.getFullYear()}/${ expiration =
expiry_date.getMonth() + 1 expiry_date !== undefined
}/${expiry_date.getDate()}`; ? `${expiry_date.getFullYear()}/${
expiry_date.getMonth() + 1
}/${expiry_date.getDate()}`
: undefined;
const currentDate = new DateWithZeroTime(); const currentDate = new DateWithZeroTime();
// 有効期限までの日数 // 有効期限までの日数
remaining = Math.floor( remaining =
(expiry_date.getTime() - currentDate.getTime()) / expiry_date !== undefined
(1000 * 60 * 60 * 24), ? Math.floor(
); (expiry_date.getTime() - currentDate.getTime()) /
if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) { (1000 * 60 * 60 * 24),
status = x.auto_renew )
: undefined;
if (
remaining !== undefined &&
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
) {
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW ? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT; : USER_LICENSE_STATUS.ALERT;
} }
@ -506,18 +533,18 @@ export class UsersService {
} }
return { return {
id: x.id, id: dbUser.id,
name: adb2cUser.displayName, name: adb2cUser.displayName,
role: x.role, role: dbUser.role,
authorId: x.author_id ?? undefined, authorId: dbUser.author_id ?? undefined,
typistGroupName: groupNames, typistGroupName: groupNames,
email: mail, email: mail,
emailVerified: x.email_verified, emailVerified: dbUser.email_verified,
autoRenew: x.auto_renew, autoRenew: dbUser.auto_renew,
licenseAlert: x.license_alert, licenseAlert: dbUser.license_alert,
notification: x.notification, notification: dbUser.notification,
encryption: x.encryption, encryption: dbUser.encryption,
prompt: x.prompt, prompt: dbUser.prompt,
expiration: expiration, expiration: expiration,
remaining: remaining, remaining: remaining,
licenseStatus: status, licenseStatus: status,
@ -545,14 +572,13 @@ export class UsersService {
async updateSortCriteria( async updateSortCriteria(
paramName: TaskListSortableAttribute, paramName: TaskListSortableAttribute,
direction: SortDirection, direction: SortDirection,
token: AccessToken, externalId: string,
): Promise<void> { ): Promise<void> {
this.logger.log(`[IN] ${this.updateSortCriteria.name}`); this.logger.log(`[IN] ${this.updateSortCriteria.name}`);
let user: EntityUser; let user: EntityUser;
try { try {
// ユーザー情報を取得 // ユーザー情報を取得
const sub = token.userId; user = await this.usersRepository.findUserByExternalId(externalId);
user = await this.usersRepository.findUserByExternalId(sub);
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
@ -584,7 +610,7 @@ export class UsersService {
* @param token * @param token
* @returns sort criteria * @returns sort criteria
*/ */
async getSortCriteria(token: AccessToken): Promise<{ async getSortCriteria(externalId: string): Promise<{
paramName: TaskListSortableAttribute; paramName: TaskListSortableAttribute;
direction: SortDirection; direction: SortDirection;
}> { }> {
@ -592,8 +618,7 @@ export class UsersService {
let user: EntityUser; let user: EntityUser;
try { try {
// ユーザー情報を取得 // ユーザー情報を取得
const sub = token.userId; user = await this.usersRepository.findUserByExternalId(externalId);
user = await this.usersRepository.findUserByExternalId(sub);
} catch (e) { } catch (e) {
this.logger.error(`error=${e}`); this.logger.error(`error=${e}`);
@ -643,8 +668,8 @@ export class UsersService {
// TODO: PBI2105 本実装時に修正すること // TODO: PBI2105 本実装時に修正すること
return { return {
authorId: user.author_id, authorId: user.author_id ?? '',
authorIdList: [user.author_id, 'XXX'], authorIdList: [user.author_id ?? '', 'XXX'],
isEncrypted: true, isEncrypted: true,
encryptionPassword: 'abcd@123?dcba', encryptionPassword: 'abcd@123?dcba',
audioFormat: 'DS2(QP)', audioFormat: 'DS2(QP)',
@ -1004,6 +1029,11 @@ export class UsersService {
makeErrorResponse('E010204'), makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
case AccountNotFoundError:
throw new HttpException(
makeErrorResponse('E010501'),
HttpStatus.BAD_REQUEST,
);
case UpdateTermsVersionNotSetError: case UpdateTermsVersionNotSetError:
throw new HttpException( throw new HttpException(
makeErrorResponse('E010001'), makeErrorResponse('E010001'),

View File

@ -49,7 +49,7 @@ export const getWorkflow = async (
datasource: DataSource, datasource: DataSource,
accountId: number, accountId: number,
id: number, id: number,
): Promise<Workflow> => { ): Promise<Workflow | null> => {
return await datasource.getRepository(Workflow).findOne({ return await datasource.getRepository(Workflow).findOne({
where: { where: {
account_id: accountId, account_id: accountId,

View File

@ -66,7 +66,6 @@ export class WorkflowsController {
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@Get() @Get()
async getWorkflows(@Req() req: Request): Promise<GetWorkflowsResponse> { async getWorkflows(@Req() req: Request): Promise<GetWorkflowsResponse> {
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(
@ -123,7 +122,7 @@ export class WorkflowsController {
@Body() body: CreateWorkflowsRequest, @Body() body: CreateWorkflowsRequest,
): Promise<CreateWorkflowsResponse> { ): Promise<CreateWorkflowsResponse> {
const { authorId, worktypeId, templateId, typists } = body; const { authorId, worktypeId, templateId, typists } = body;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(
@ -188,7 +187,7 @@ export class WorkflowsController {
): Promise<UpdateWorkflowResponse> { ): Promise<UpdateWorkflowResponse> {
const { authorId, worktypeId, templateId, typists } = body; const { authorId, worktypeId, templateId, typists } = body;
const { workflowId } = param; const { workflowId } = param;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(
@ -252,7 +251,7 @@ export class WorkflowsController {
@Param() param: DeleteWorkflowRequestParam, @Param() param: DeleteWorkflowRequestParam,
): Promise<DeleteWorkflowResponse> { ): Promise<DeleteWorkflowResponse> {
const { workflowId } = param; const { workflowId } = param;
// TODO strictNullChecks対応
const accessToken = retrieveAuthorizationToken(req); const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(

View File

@ -2062,7 +2062,7 @@ describe('updateWorkflow', () => {
}); });
describe('deleteWorkflows', () => { describe('deleteWorkflows', () => {
let source: DataSource = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
type: 'sqlite', type: 'sqlite',
@ -2074,12 +2074,15 @@ describe('deleteWorkflows', () => {
return source.initialize(); return source.initialize();
}); });
afterEach(async () => { afterEach(async () => {
if (!source) return;
await source.destroy(); await source.destroy();
source = null; source = null;
}); });
it('アカウント内のWorkflowを削除できる', async () => { it('アカウント内のWorkflowを削除できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, { const { id: authorId } = await makeTestUser(source, {
@ -2126,7 +2129,9 @@ describe('deleteWorkflows', () => {
}); });
it('アカウント内のWorkflowを削除できる複数ワークフローがある場合', async () => { it('アカウント内のWorkflowを削除できる複数ワークフローがある場合', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId1 } = await makeTestUser(source, { const { id: authorId1 } = await makeTestUser(source, {
@ -2177,7 +2182,9 @@ describe('deleteWorkflows', () => {
}); });
it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => { it('指定されたワークフローが存在しない場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, { const { id: authorId } = await makeTestUser(source, {
@ -2226,7 +2233,9 @@ describe('deleteWorkflows', () => {
}); });
it('指定されたワークフローが存在しない場合、400エラーを返却するログインユーザーのアカウント外', async () => { it('指定されたワークフローが存在しない場合、400エラーを返却するログインユーザーのアカウント外', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, { const { id: authorId } = await makeTestUser(source, {
@ -2303,7 +2312,9 @@ describe('deleteWorkflows', () => {
}); });
it('DBアクセスに失敗した場合、500エラーを返却する', async () => { it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, { const { id: authorId } = await makeTestUser(source, {

View File

@ -57,8 +57,8 @@ export class WorkflowsService {
return workflowTypists; return workflowTypists;
}); });
// externalIdsからundefinedを除外 // externalIdsからundefinedを除外
const filteredExternalIds = externalIds.filter( const filteredExternalIds = externalIds.flatMap((externalId) =>
(externalId): externalId is string => externalId !== undefined, externalId ? [externalId] : [],
); );
// externalIdsから重複を除外 // externalIdsから重複を除外
const distinctedExternalIds = [...new Set(filteredExternalIds)]; const distinctedExternalIds = [...new Set(filteredExternalIds)];

View File

@ -40,8 +40,8 @@ export class AdB2cService {
// ADB2Cへの認証情報 // ADB2Cへの認証情報
const credential = new ClientSecretCredential( const credential = new ClientSecretCredential(
this.configService.getOrThrow<string>('ADB2C_TENANT_ID'), this.configService.getOrThrow<string>('ADB2C_TENANT_ID'),
this.configService.getOrThrow('ADB2C_CLIENT_ID'), this.configService.getOrThrow<string>('ADB2C_CLIENT_ID'),
this.configService.getOrThrow('ADB2C_CLIENT_SECRET'), this.configService.getOrThrow<string>('ADB2C_CLIENT_SECRET'),
); );
const authProvider = new TokenCredentialAuthenticationProvider(credential, { const authProvider = new TokenCredentialAuthenticationProvider(credential, {
scopes: ['https://graph.microsoft.com/.default'], scopes: ['https://graph.microsoft.com/.default'],
@ -76,7 +76,7 @@ export class AdB2cService {
}, },
identities: [ identities: [
{ {
signinType: ADB2C_SIGN_IN_TYPE.EAMILADDRESS, signinType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: `${this.tenantName}.onmicrosoft.com`, issuer: `${this.tenantName}.onmicrosoft.com`,
issuerAssignedId: email, issuerAssignedId: email,
}, },

View File

@ -8,7 +8,13 @@ import { Context } from '../../common/log';
@Injectable() @Injectable()
export class SendGridService { export class SendGridService {
private readonly logger = new Logger(SendGridService.name); private readonly logger = new Logger(SendGridService.name);
private readonly emailConfirmLifetime: number;
private readonly appDomain: string;
constructor(private readonly configService: ConfigService) { 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'); const key = this.configService.getOrThrow<string>('SENDGRID_API_KEY');
sendgrid.setApiKey(key); sendgrid.setApiKey(key);
} }
@ -30,8 +36,6 @@ export class SendGridService {
`[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`, `[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`,
); );
const lifetime =
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
const privateKey = getPrivateKey(this.configService); const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>( const token = sign<{ accountId: number; userId: number; email: string }>(
{ {
@ -39,10 +43,9 @@ export class SendGridService {
userId, userId,
email, email,
}, },
lifetime, this.emailConfirmLifetime,
privateKey, privateKey,
); );
const domains = this.configService.get<string>('APP_DOMAIN');
const path = 'mail-confirm/'; const path = 'mail-confirm/';
this.logger.log( this.logger.log(
@ -50,8 +53,8 @@ export class SendGridService {
); );
return { return {
subject: 'Verify your new account', subject: 'Verify your new account',
text: `The verification URL. ${domains}${path}?verify=${token}`, text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`, 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, userId: number,
email: string, email: string,
): Promise<{ subject: string; text: string; html: string }> { ): Promise<{ subject: string; text: string; html: string }> {
const lifetime =
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
const privateKey = getPrivateKey(this.configService); const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>( const token = sign<{ accountId: number; userId: number; email: string }>(
@ -79,16 +79,15 @@ export class SendGridService {
userId, userId,
email, email,
}, },
lifetime, this.emailConfirmLifetime,
privateKey, privateKey,
); );
const domains = this.configService.get<string>('APP_DOMAIN');
const path = 'mail-confirm/user/'; const path = 'mail-confirm/user/';
return { return {
subject: 'Verify your new account', subject: 'Verify your new account',
text: `The verification URL. ${domains}${path}?verify=${token}`, text: `The verification URL. ${this.appDomain}${path}?verify=${token}`,
html: `<p>The verification URL.<p><a href="${domains}${path}?verify=${token}">${domains}${path}?verify=${token}"</a>`, html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}"</a>`,
}; };
} }

View File

@ -13,6 +13,7 @@ import {
import { User, UserArchive } from '../users/entity/user.entity'; import { User, UserArchive } from '../users/entity/user.entity';
import { Account } from './entity/account.entity'; import { Account } from './entity/account.entity';
import { import {
CardLicense,
License, License,
LicenseAllocationHistory, LicenseAllocationHistory,
LicenseAllocationHistoryArchive, LicenseAllocationHistoryArchive,
@ -48,6 +49,14 @@ import {
import { DateWithZeroTime } from '../../features/licenses/types/types'; import { DateWithZeroTime } from '../../features/licenses/types/types';
import { Worktype } from '../worktypes/entity/worktype.entity'; import { Worktype } from '../worktypes/entity/worktype.entity';
import { WorktypeIdNotFoundError } from '../worktypes/errors/types'; 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() @Injectable()
export class AccountsRepositoryService { export class AccountsRepositoryService {
@ -117,13 +126,13 @@ export class AccountsRepositoryService {
tier: number, tier: number,
adminExternalUserId: string, adminExternalUserId: string,
adminUserRole: string, adminUserRole: string,
adminUserAcceptedEulaVersion: string, adminUserAcceptedEulaVersion?: string,
adminUserAcceptedDpaVersion: string, adminUserAcceptedDpaVersion?: string,
): Promise<{ newAccount: Account; adminUser: User }> { ): Promise<{ newAccount: Account; adminUser: User }> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const account = new Account(); const account = new Account();
{ {
account.parent_account_id = dealerAccountId; account.parent_account_id = dealerAccountId ?? null;
account.company_name = companyName; account.company_name = companyName;
account.country = country; account.country = country;
account.tier = tier; account.tier = tier;
@ -138,8 +147,8 @@ export class AccountsRepositoryService {
user.account_id = persistedAccount.id; user.account_id = persistedAccount.id;
user.external_id = adminExternalUserId; user.external_id = adminExternalUserId;
user.role = adminUserRole; user.role = adminUserRole;
user.accepted_eula_version = adminUserAcceptedEulaVersion; user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
user.accepted_dpa_version = adminUserAcceptedDpaVersion; user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
} }
const usersRepo = entityManager.getRepository(User); const usersRepo = entityManager.getRepository(User);
const newUser = usersRepo.create(user); const newUser = usersRepo.create(user);
@ -490,6 +499,9 @@ export class AccountsRepositoryService {
id: id, id: id,
}, },
}); });
if (!ownAccount) {
throw new AccountNotFoundError();
}
// 自アカウントのライセンス注文状況を取得する // 自アカウントのライセンス注文状況を取得する
const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus( const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus(
@ -532,8 +544,8 @@ export class AccountsRepositoryService {
); );
// 第五の不足数を算出するためのライセンス数情報を取得する // 第五の不足数を算出するためのライセンス数情報を取得する
let expiringSoonLicense: number; let expiringSoonLicense: number = 0;
let allocatableLicenseWithMargin: number; let allocatableLicenseWithMargin: number = 0;
if (childAccount.tier === TIERS.TIER5) { if (childAccount.tier === TIERS.TIER5) {
expiringSoonLicense = await this.getExpiringSoonLicense( expiringSoonLicense = await this.getExpiringSoonLicense(
entityManager, entityManager,
@ -606,7 +618,7 @@ export class AccountsRepositoryService {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const accountRepository = entityManager.getRepository(Account); const accountRepository = entityManager.getRepository(Account);
const maxTierDifference = TIERS.TIER5 - TIERS.TIER1; const maxTierDifference = TIERS.TIER5 - TIERS.TIER1;
const parentAccountIds = []; const parentAccountIds: number[] = [];
let currentAccountId = targetAccountId; let currentAccountId = targetAccountId;
// システム的な最大の階層差異分、親を参照する // システム的な最大の階層差異分、親を参照する
@ -619,6 +631,9 @@ export class AccountsRepositoryService {
if (!account) { if (!account) {
break; break;
} }
if (!account.parent_account_id) {
throw new Error("Parent account doesn't exist.");
}
parentAccountIds.push(account.parent_account_id); parentAccountIds.push(account.parent_account_id);
currentAccountId = account.parent_account_id; currentAccountId = account.parent_account_id;
@ -740,11 +755,13 @@ export class AccountsRepositoryService {
}); });
// ADB2Cから情報を取得するための外部ユーザIDを取得する念のためプライマリ管理者IDが存在しない場合を考慮 // ADB2Cから情報を取得するための外部ユーザIDを取得する念のためプライマリ管理者IDが存在しない場合を考慮
const primaryUserIds = partnerAccounts.map((x) => { const primaryUserIds = partnerAccounts.flatMap((x) => {
if (x.primary_admin_user_id) { 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) { } 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); const userRepo = entityManager.getRepository(User);
@ -761,15 +778,18 @@ export class AccountsRepositoryService {
user.id === account.primary_admin_user_id || user.id === account.primary_admin_user_id ||
user.id === account.secondary_admin_user_id, user.id === account.secondary_admin_user_id,
); );
const primaryAccountExternalId = primaryUser if (!primaryUser) {
? primaryUser.external_id throw new AdminUserNotFoundError(
: undefined; `Primary admin user is not found. id: ${account.primary_admin_user_id}, account_id: ${account.id}`,
);
}
return { return {
name: account.company_name, name: account.company_name,
tier: account.tier, tier: account.tier,
accountId: account.id, accountId: account.id,
country: account.country, country: account.country,
primaryAccountExternalId: primaryAccountExternalId, primaryAccountExternalId: primaryUser.external_id,
dealerManagement: account.delegation_permission, dealerManagement: account.delegation_permission,
}; };
}); });
@ -790,7 +810,7 @@ export class AccountsRepositoryService {
async getOneUpperTierAccount( async getOneUpperTierAccount(
accountId: number, accountId: number,
tier: number, tier: number,
): Promise<Account | undefined> { ): Promise<Account | null> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const accountRepo = entityManager.getRepository(Account); const accountRepo = entityManager.getRepository(Account);
return await accountRepo.findOne({ return await accountRepo.findOne({
@ -869,10 +889,10 @@ export class AccountsRepositoryService {
await accountRepo.update( await accountRepo.update(
{ id: myAccountId }, { id: myAccountId },
{ {
parent_account_id: parentAccountId || null, parent_account_id: parentAccountId ?? null,
delegation_permission: delegationPermission, delegation_permission: delegationPermission,
primary_admin_user_id: primaryAdminUserId, 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(); .execute();
// アカウントを削除 // アカウントを削除
// アカウントを削除することで、外部キー制約がで紐づいている関連テーブルのデータも削除される
const accountRepo = entityManager.getRepository(Account); const accountRepo = entityManager.getRepository(Account);
await accountRepo.delete({ id: accountId }); 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; return users;
}); });
} }

View File

@ -13,8 +13,8 @@ export class Account {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
parent_account_id?: number; parent_account_id: number | null;
@Column() @Column()
tier: number; tier: number;
@ -34,30 +34,36 @@ export class Account {
@Column({ default: false }) @Column({ default: false })
verified: boolean; verified: boolean;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
primary_admin_user_id?: number; primary_admin_user_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
secondary_admin_user_id?: number; secondary_admin_user_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
active_worktype_id?: number; active_worktype_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at?: Date; deleted_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by?: string; 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; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by?: string; 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; updated_at: Date;
@OneToMany(() => User, (user) => user.id) @OneToMany(() => User, (user) => user.id)
user?: User[]; user: User[] | null;
} }

View File

@ -32,12 +32,12 @@ export class AudioFile {
priority: string; priority: string;
@Column() @Column()
audio_format: string; audio_format: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'varchar' })
comment?: string; comment: string | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at?: Date; deleted_at: Date | null;
@Column() @Column()
is_encrypted: boolean; is_encrypted: boolean;
@OneToOne(() => Task, (task) => task.file) @OneToOne(() => Task, (task) => task.file)
task?: Task; task: Task | null;
} }

View File

@ -19,5 +19,5 @@ export class AudioOptionItem {
value: string; value: string;
@ManyToOne(() => Task, (task) => task.audio_file_id) @ManyToOne(() => Task, (task) => task.audio_file_id)
@JoinColumn({ name: 'audio_file_id' }) @JoinColumn({ name: 'audio_file_id' })
task?: Task; task: Task | null;
} }

View File

@ -18,21 +18,21 @@ export class CheckoutPermission {
@Column({}) @Column({})
task_id: number; task_id: number;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
user_id?: number; user_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
user_group_id?: number; user_group_id: number | null;
@OneToOne(() => User, (user) => user.id) @OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'user_id' }) @JoinColumn({ name: 'user_id' })
user?: User; user: User | null;
@OneToOne(() => UserGroup, (group) => group.id) @OneToOne(() => UserGroup, (group) => group.id)
@JoinColumn({ name: 'user_group_id' }) @JoinColumn({ name: 'user_group_id' })
user_group?: UserGroup; user_group: UserGroup | null;
@ManyToOne(() => Task, (task) => task.id) @ManyToOne(() => Task, (task) => task.id)
@JoinColumn({ name: 'task_id' }) @JoinColumn({ name: 'task_id' })
task?: Task; task: Task | null;
} }

View File

@ -25,11 +25,14 @@ export class LicenseOrder {
@Column() @Column()
to_account_id: number; to_account_id: number;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
ordered_at: Date; ordered_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
issued_at?: Date; issued_at: Date | null;
@Column() @Column()
quantity: number; quantity: number;
@ -37,19 +40,25 @@ export class LicenseOrder {
@Column() @Column()
status: string; status: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
canceled_at?: Date; canceled_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@UpdateDateColumn() @UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
updated_at: Date; updated_at: Date;
} }
@ -58,8 +67,8 @@ export class License {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
expiry_date: Date; expiry_date: Date | null;
@Column() @Column()
account_id: number; account_id: number;
@ -70,33 +79,41 @@ export class License {
@Column() @Column()
status: string; status: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
allocated_user_id: number; allocated_user_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
order_id: number; order_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at: Date; deleted_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
delete_order_id: number; delete_order_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@UpdateDateColumn() @UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
updated_at: Date; updated_at: Date;
@OneToOne(() => User, (user) => user.license) @OneToOne(() => User, (user) => user.license, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: 'allocated_user_id' }) @JoinColumn({ name: 'allocated_user_id' })
user?: User; user: User | null;
} }
@Entity({ name: 'card_license_issue' }) @Entity({ name: 'card_license_issue' })
@ -107,16 +124,22 @@ export class CardLicenseIssue {
@Column() @Column()
issued_at: Date; issued_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@UpdateDateColumn() @UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
updated_at: Date; updated_at: Date;
} }
@ -131,19 +154,25 @@ export class CardLicense {
@Column() @Column()
card_license_key: string; card_license_key: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
activated_at: Date; activated_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@UpdateDateColumn({}) @UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
updated_at: Date; updated_at: Date;
} }
@ -170,24 +199,32 @@ export class LicenseAllocationHistory {
@Column() @Column()
switch_from_type: string; switch_from_type: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at: Date; deleted_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@UpdateDateColumn() @UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
updated_at: Date; updated_at: Date;
@ManyToOne(() => License, (licenses) => licenses.id) @ManyToOne(() => License, (licenses) => licenses.id, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: 'license_id' }) @JoinColumn({ name: 'license_id' })
license?: License; license: License | null;
} }
@Entity({ name: 'licenses_archive' }) @Entity({ name: 'licenses_archive' })
@ -195,8 +232,8 @@ export class LicenseArchive {
@PrimaryColumn() @PrimaryColumn()
id: number; id: number;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
expiry_date: Date; expiry_date: Date | null;
@Column() @Column()
account_id: number; account_id: number;
@ -207,31 +244,34 @@ export class LicenseArchive {
@Column() @Column()
status: string; status: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
allocated_user_id: number; allocated_user_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
order_id: number; order_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at: Date; deleted_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
delete_order_id: number; delete_order_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@Column() @Column()
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@Column() @Column()
updated_at: Date; updated_at: Date;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
archived_at: Date; archived_at: Date;
} }
@ -258,21 +298,24 @@ export class LicenseAllocationHistoryArchive {
@Column() @Column()
switch_from_type: string; switch_from_type: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
deleted_at: Date; deleted_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; created_by: string | null;
@Column() @Column()
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string; updated_by: string | null;
@Column() @Column()
updated_at: Date; updated_at: Date;
@CreateDateColumn() @CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: 'datetime',
})
archived_at: Date; archived_at: Date;
} }

View File

@ -444,7 +444,7 @@ export class LicensesRepositoryService {
const allocatableLicenses = await queryBuilder.getMany(); const allocatableLicenses = await queryBuilder.getMany();
return allocatableLicenses.map((license) => ({ return allocatableLicenses.map((license) => ({
licenseId: license.id, 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) { if (targetLicense.expiry_date) {
const currentDay = new Date(); const currentDay = new Date();
@ -533,7 +540,7 @@ export class LicensesRepositoryService {
}); });
let switchFromType = ''; let switchFromType = '';
if (oldLicenseType) { if (oldLicenseType && oldLicenseType.license) {
switch (oldLicenseType.license.type) { switch (oldLicenseType.license.type) {
case LICENSE_TYPE.CARD: case LICENSE_TYPE.CARD:
switchFromType = SWITCH_FROM_TYPE.CARD; switchFromType = SWITCH_FROM_TYPE.CARD;

View File

@ -20,33 +20,33 @@ export class Task {
job_number: string; job_number: string;
@Column() @Column()
account_id: number; account_id: number;
@Column({ nullable: true }) @Column({ nullable: true, type: 'tinyint' })
is_job_number_enabled?: boolean; is_job_number_enabled: boolean | null;
@Column() @Column()
audio_file_id: number; audio_file_id: number;
@Column() @Column()
status: string; status: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
typist_user_id?: number; typist_user_id: number | null;
@Column() @Column()
priority: string; priority: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'unsigned big int' })
template_file_id?: number; template_file_id: number | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
started_at?: Date; started_at: Date | null;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
finished_at?: Date; finished_at: Date | null;
@Column({}) @Column({})
created_at: Date; created_at: Date;
@OneToOne(() => AudioFile, (audiofile) => audiofile.task) @OneToOne(() => AudioFile, (audiofile) => audiofile.task)
@JoinColumn({ name: 'audio_file_id' }) @JoinColumn({ name: 'audio_file_id' })
file?: AudioFile; file: AudioFile | null;
@OneToMany(() => AudioOptionItem, (option) => option.task) @OneToMany(() => AudioOptionItem, (option) => option.task)
option_items?: AudioOptionItem[]; option_items: AudioOptionItem[] | null;
@OneToOne(() => User, (user) => user.id) @OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'typist_user_id' }) @JoinColumn({ name: 'typist_user_id' })
typist_user?: User; typist_user: User | null;
@ManyToOne(() => TemplateFile, (templateFile) => templateFile.id) @ManyToOne(() => TemplateFile, (templateFile) => templateFile.id)
@JoinColumn({ name: 'template_file_id' }) @JoinColumn({ name: 'template_file_id' })
template_file?: TemplateFile; template_file: TemplateFile | null;
} }

View File

@ -757,7 +757,7 @@ export class TasksRepositoryService {
*/ */
async changeCheckoutPermission( async changeCheckoutPermission(
audio_file_id: number, audio_file_id: number,
author_id: string, author_id: string | undefined,
account_id: number, account_id: number,
roles: Roles[], roles: Roles[],
assignees: Assignee[], assignees: Assignee[],
@ -844,8 +844,8 @@ export class TasksRepositoryService {
(assignee) => { (assignee) => {
const checkoutPermission = new CheckoutPermission(); const checkoutPermission = new CheckoutPermission();
checkoutPermission.task_id = taskRecord.id; checkoutPermission.task_id = taskRecord.id;
checkoutPermission.user_id = assignee.typistUserId; checkoutPermission.user_id = assignee.typistUserId ?? null;
checkoutPermission.user_group_id = assignee.typistGroupId; checkoutPermission.user_group_id = assignee.typistGroupId ?? null;
return checkoutPermission; return checkoutPermission;
}, },
); );

View File

@ -18,11 +18,11 @@ export class TemplateFile {
url: string; url: string;
@Column() @Column()
file_name: string; file_name: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string | null; created_by: string | null;
@CreateDateColumn() @CreateDateColumn()
created_at: Date; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
updated_by: string | null; updated_by: string | null;
@UpdateDateColumn() @UpdateDateColumn()
updated_at: Date; updated_at: Date;

View File

@ -17,15 +17,21 @@ export class Term {
@Column() @Column()
version: string; version: string;
@Column({ nullable: true }) @Column({ nullable: true, type: 'datetime' })
created_by: string; 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; created_at: Date;
@Column({ nullable: true }) @Column({ nullable: true, type: 'varchar' })
updated_by?: string; 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; updated_at: Date;
} }

Some files were not shown because too many files have changed in this diff Show More