Merge branch 'develop' into main

This commit is contained in:
maruyama.t 2023-12-28 14:36:23 +09:00
commit 0213d193e8
133 changed files with 9713 additions and 2769 deletions

View File

@ -87,6 +87,7 @@ jobs:
REDIS_PORT: 0 REDIS_PORT: 0
REDIS_PASSWORD: xxxxxxxxxxxx REDIS_PASSWORD: xxxxxxxxxxxx
ADB2C_CACHE_TTL: 0 ADB2C_CACHE_TTL: 0
STAGE: local
- task: Docker@0 - task: Docker@0
displayName: build displayName: build
inputs: inputs:

View File

@ -27,8 +27,8 @@
"debug.javascript.usePreview": false, "debug.javascript.usePreview": false,
"editor.copyWithSyntaxHighlighting": false, "editor.copyWithSyntaxHighlighting": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": true "source.fixAll.stylelint": "explicit"
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,

View File

@ -8,8 +8,8 @@
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"build": "tsc && vite build --mode development", "build": "tsc && vite build --mode development",
"build:stg": "tsc && vite build --mode staging", "build:stg": "tsc && vite build --mode staging --sourcemap false",
"build:prod": "tsc && vite build --mode production", "build:prod": "tsc && vite build --mode production --sourcemap false",
"build:local": "tsc && vite build --mode development && sh localdeploy.sh", "build:local": "tsc && vite build --mode development && sh localdeploy.sh",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",

View File

@ -0,0 +1,15 @@
// UTCの日付に対してローカルのロケールタイムゾーンを考慮して表示形式と時刻補正を行った文字列を返却する
export const convertUtcToLocal = (
utcDateString: string,
formatOptions?: Intl.DateTimeFormatOptions
): string => {
if (Number.isNaN(Date.parse(utcDateString))) {
// 日付文字列が未定義または無効な場合は 変換を行わない
return utcDateString;
}
const utcDate = new Date(utcDateString);
return formatOptions
? utcDate.toLocaleString(undefined, formatOptions)
: utcDate.toLocaleString();
};

View File

@ -51,7 +51,7 @@ export const HEADER_MENUS: {
}, },
]; ];
export const HEADER_NAME = getTranslationID("common.label.headerName"); export const HEADER_NAME = "ODMS Cloud";
/** /**
* adminのみに表示するヘッダータブ * adminのみに表示するヘッダータブ

View File

@ -74,7 +74,7 @@ const LoginedHeader: React.FC<HeaderProps> = (props: HeaderProps) => {
<div className={styles.headerLogo}> <div className={styles.headerLogo}>
<img src={logo} alt="OM System" /> <img src={logo} alt="OM System" />
</div> </div>
<div className={styles.headerSub}>{t(HEADER_NAME)}</div> <div className={styles.headerSub}>{HEADER_NAME}</div>
<div className={styles.headerMenu}> <div className={styles.headerMenu}>
<ul> <ul>
{filterMenus.map((x) => ( {filterMenus.map((x) => (

View File

@ -19,7 +19,7 @@ const NotLoginHeader: React.FC<NotLoginHeaderProps> = (
<div className={`${styles.headerLogo}`}> <div className={`${styles.headerLogo}`}>
<img src={logo} alt="OM System" /> <img src={logo} alt="OM System" />
</div> </div>
<p className={`${styles.headerSub}`}>{t(HEADER_NAME)}</p> <p className={`${styles.headerSub}`}>{HEADER_NAME}</p>
</header> </header>
); );
}; };

View File

@ -1,9 +1,50 @@
import { RootState } from "app/store"; import { RootState } from "app/store";
import { convertUtcToLocal } from "common/convertUtcToLocal";
import { ceil, floor } from "lodash"; import { ceil, floor } from "lodash";
import { BACKUP_POPUP_LIST_SIZE } from "./constants"; import { BACKUP_POPUP_LIST_SIZE } from "./constants";
export const selectTasks = (state: RootState) => state.dictation.domain.tasks; // ミリ秒の数値をhh:mm:ss形式に変換する
const formatMillisecondsToHHMMSS = (milliseconds: number): string => {
const seconds = Math.floor(milliseconds / 1000);
if (seconds < 0) {
return "00:00:00";
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
// "0x"となるように0埋めする
const formattedHours = String(hours).padStart(2, "0");
const formattedMinutes = String(minutes).padStart(2, "0");
const formattedSeconds = String(remainingSeconds).padStart(2, "0");
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
};
export const selectTasks = (state: RootState) => {
const { tasks } = state.dictation.domain;
const tasksWithLocalDate = tasks.map((task) => ({
...task,
// UTCからローカルタイムゾーンに変換して詰めなおす
audioCreatedDate: convertUtcToLocal(task.audioCreatedDate),
audioFinishedDate: convertUtcToLocal(task.audioFinishedDate),
audioUploadedDate: convertUtcToLocal(task.audioUploadedDate),
transcriptionStartedDate:
task.transcriptionStartedDate &&
convertUtcToLocal(task.transcriptionStartedDate),
transcriptionFinishedDate:
task.transcriptionFinishedDate &&
convertUtcToLocal(task.transcriptionFinishedDate),
// ミリ秒からhh:mm:ss形式に変換して詰めなおす
audioDuration: formatMillisecondsToHHMMSS(parseInt(task.audioDuration, 10)),
}));
return tasksWithLocalDate;
};
export const selectTotal = (state: RootState) => state.dictation.domain.total; export const selectTotal = (state: RootState) => state.dictation.domain.total;
export const seletctLimit = (state: RootState) => state.dictation.domain.limit; export const seletctLimit = (state: RootState) => state.dictation.domain.limit;

View File

@ -1,8 +1,27 @@
import { RootState } from "app/store"; import { RootState } from "app/store";
import { ceil, floor } from "lodash"; import { ceil, floor } from "lodash";
import { convertUtcToLocal } from "common/convertUtcToLocal";
export const selectOrderHisory = (state: RootState) => export const selectOrderHisory = (state: RootState) => {
state.licenseOrderHistory.domain.orderHistories; const { orderHistories } = state.licenseOrderHistory.domain;
const dateOptions: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "numeric",
day: "numeric",
};
const orderHistoriesWithLocalDate = orderHistories.map((orderHistory) => ({
...orderHistory,
// UTCからローカルタイムゾーンに変換して詰めなおす
orderDate: convertUtcToLocal(orderHistory.orderDate, dateOptions),
issueDate:
orderHistory.issueDate &&
convertUtcToLocal(orderHistory.issueDate, dateOptions),
}));
return orderHistoriesWithLocalDate;
};
export const selectCompanyName = (state: RootState) => export const selectCompanyName = (state: RootState) =>
state.licenseOrderHistory.domain.companyName; state.licenseOrderHistory.domain.companyName;

View File

@ -8,8 +8,11 @@ export const selectInputValidationErrors = (state: RootState) => {
const hasErrorEmptyAdminName = state.partner.apps.addPartner.adminName === ""; const hasErrorEmptyAdminName = state.partner.apps.addPartner.adminName === "";
const hasErrorEmptyEmail = state.partner.apps.addPartner.email === ""; const hasErrorEmptyEmail = state.partner.apps.addPartner.email === "";
const emailPattern =
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
const hasErrorIncorrectEmail = const hasErrorIncorrectEmail =
(state.partner.apps.addPartner.email as string).match(/^[^@]+@[^@]+$/) (state.partner.apps.addPartner.email as string).match(emailPattern)
?.length !== 1; ?.length !== 1;
return { return {

View File

@ -16,8 +16,10 @@ export const selectInputValidationErrors = (state: RootState) => {
state.signup.apps.password state.signup.apps.password
); );
const emailPattern =
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
const hasErrorIncorrectEmail = const hasErrorIncorrectEmail =
(state.signup.apps.email as string).match(/^[^@]+@[^@]+$/)?.length !== 1; (state.signup.apps.email as string).match(emailPattern)?.length !== 1;
return { return {
hasErrorEmptyEmail, hasErrorEmptyEmail,

View File

@ -22,3 +22,6 @@ export const selectTermVersions = (state: RootState) => {
}; };
export const selectTier = (state: RootState) => state.terms.domain.tier; export const selectTier = (state: RootState) => state.terms.domain.tier;
export const selectIsLoading = (state: RootState) =>
state.terms.apps.isLoading === true;

View File

@ -39,16 +39,9 @@ export const termsSlice = createSlice({
builder.addCase(getAccountInfoMinimalAccessAsync.rejected, (state) => { builder.addCase(getAccountInfoMinimalAccessAsync.rejected, (state) => {
state.apps.isLoading = false; state.apps.isLoading = false;
}); });
builder.addCase(getTermsInfoAsync.pending, (state) => {
state.apps.isLoading = true;
});
builder.addCase(getTermsInfoAsync.fulfilled, (state, actions) => { builder.addCase(getTermsInfoAsync.fulfilled, (state, actions) => {
state.apps.isLoading = false;
state.domain.termsInfo = actions.payload.termsInfo; state.domain.termsInfo = actions.payload.termsInfo;
}); });
builder.addCase(getTermsInfoAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(updateAcceptedVersionAsync.pending, (state) => { builder.addCase(updateAcceptedVersionAsync.pending, (state) => {
state.apps.isLoading = true; state.apps.isLoading = true;
}); });

View File

@ -26,7 +26,10 @@ export const selectInputValidationErrors = (state: RootState) => {
role role
); );
const hasErrorIncorrectEmail = email.match(/^[^@]+@[^@]+$/)?.length !== 1; const emailPattern =
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
const hasErrorIncorrectEmail = email.match(emailPattern)?.length !== 1;
const hasErrorIncorrectEncryptionPassword = const hasErrorIncorrectEncryptionPassword =
checkErrorIncorrectEncryptionPassword(encryptionPassword, role, encryption); checkErrorIncorrectEncryptionPassword(encryptionPassword, role, encryption);

View File

@ -38,7 +38,7 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
</p> </p>
<dl className={`${styles.formList} ${styles.property} ${styles.hasbg}`}> <dl className={`${styles.formList} ${styles.property} ${styles.hasbg}`}>
<dt className={styles.formTitle}> <dt className={styles.formTitle}>
{t(getTranslationID("dictationPage.label.general"))} {t(getTranslationID("filePropertyPopup.label.general"))}
</dt> </dt>
<dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt> <dt>{t(getTranslationID("dictationPage.label.fileName"))}</dt>
<dd>{selectedFileTask?.fileName.replace(".zip", "") ?? ""}</dd> <dd>{selectedFileTask?.fileName.replace(".zip", "") ?? ""}</dd>
@ -93,7 +93,7 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
<dt>{t(getTranslationID("dictationPage.label.comment"))}</dt> <dt>{t(getTranslationID("dictationPage.label.comment"))}</dt>
<dd>{selectedFileTask?.comment ?? ""}</dd> <dd>{selectedFileTask?.comment ?? ""}</dd>
<dt className={styles.formTitle}> <dt className={styles.formTitle}>
{t(getTranslationID("dictationPage.label.job"))} {t(getTranslationID("filePropertyPopup.label.job"))}
</dt> </dt>
<dt>{t(getTranslationID("dictationPage.label.jobNumber"))}</dt> <dt>{t(getTranslationID("dictationPage.label.jobNumber"))}</dt>
<dd>{selectedFileTask?.jobNumber ?? ""}</dd> <dd>{selectedFileTask?.jobNumber ?? ""}</dd>
@ -114,9 +114,9 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
<dt>{t(getTranslationID("dictationPage.label.transcriptionist"))}</dt> <dt>{t(getTranslationID("dictationPage.label.transcriptionist"))}</dt>
<dd>{selectedFileTask?.typist?.name ?? ""}</dd> <dd>{selectedFileTask?.typist?.name ?? ""}</dd>
<dd className={`${styles.full} ${styles.alignRight}`}> <dd className={`${styles.full} ${styles.alignRight}`}>
<a href="" className={`${styles.buttonText}`}> <a onClick={closePopup} className={`${styles.buttonText}`}>
<img src={close} className={styles.modalTitleIcon} alt="close" /> <img src={close} className={styles.modalTitleIcon} alt="close" />
{t(getTranslationID("dictationPage.label.close"))} {t(getTranslationID("filePropertyPopup.label.close"))}
</a> </a>
</dd> </dd>
</dl> </dl>

View File

@ -121,9 +121,9 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
</h1> </h1>
</div> </div>
<section className={styles.license}> <section className={styles.license}>
<div> <div className={styles.boxFlex}>
<h2 className="">{companyName}</h2> <h2 className="">{companyName}</h2>
<ul className={styles.menuAction}> <ul className={`${styles.menuAction} ${styles.box100}`}>
<li> <li>
{/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */} {/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */}
{selectedRow && ( {selectedRow && (
@ -194,7 +194,17 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
)} )}
</li> </li>
</ul> </ul>
<dl className={`${styles.listVertical} ${styles.marginBtm5}`}> <div className={styles.marginRgt3}>
<dl
className={`${styles.listVertical} ${styles.marginBtm3}`}
>
<h4 className={styles.listHeader}>
{t(
getTranslationID(
"LicenseSummaryPage.label.licenseLabel"
)
)}
</h4>
<dt> <dt>
{t( {t(
getTranslationID( getTranslationID(
@ -206,11 +216,11 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
<dt> <dt>
{t( {t(
getTranslationID( getTranslationID(
"LicenseSummaryPage.label.allocatedLicense" "LicenseSummaryPage.label.freeLicense"
) )
)} )}
</dt> </dt>
<dd>{licenseSummaryInfo.allocatedLicense}</dd> <dd>{licenseSummaryInfo.freeLicense}</dd>
<dt> <dt>
{t( {t(
getTranslationID( getTranslationID(
@ -221,10 +231,12 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
<dd>{licenseSummaryInfo.reusableLicense}</dd> <dd>{licenseSummaryInfo.reusableLicense}</dd>
<dt> <dt>
{t( {t(
getTranslationID("LicenseSummaryPage.label.freeLicense") getTranslationID(
"LicenseSummaryPage.label.allocatedLicense"
)
)} )}
</dt> </dt>
<dd>{licenseSummaryInfo.freeLicense}</dd> <dd>{licenseSummaryInfo.allocatedLicense}</dd>
<dt> <dt>
{t( {t(
getTranslationID( getTranslationID(
@ -235,27 +247,15 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
<dd>{licenseSummaryInfo.expiringWithin14daysLicense}</dd> <dd>{licenseSummaryInfo.expiringWithin14daysLicense}</dd>
<dt> <dt>
{t( {t(
getTranslationID( getTranslationID("LicenseSummaryPage.label.shortage")
"LicenseSummaryPage.label.issueRequesting"
)
)} )}
</dt> </dt>
<dd>{licenseSummaryInfo.issueRequesting}</dd>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.numberOfRequesting"
)
)}
</dt>
<dd>{licenseSummaryInfo.numberOfRequesting}</dd>
<dt>
{t(getTranslationID("LicenseSummaryPage.label.shortage"))}
</dt>
<dd> <dd>
<span <span
className={ className={
licenseSummaryInfo.shortage > 0 ? styles.isAlert : "" licenseSummaryInfo.shortage > 0
? styles.isAlert
: ""
} }
> >
{licenseSummaryInfo.shortage} {licenseSummaryInfo.shortage}
@ -263,15 +263,40 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
</dd> </dd>
<dt> <dt>
{t( {t(
getTranslationID("LicenseSummaryPage.label.storageSize") getTranslationID(
"LicenseSummaryPage.label.issueRequesting"
)
)}
</dt>
<dd>{licenseSummaryInfo.issueRequesting}</dd>
</dl>
</div>
<div>
<dl
className={`${styles.listVertical} ${styles.marginBtm3}`}
>
<h4 className={styles.listHeader}>
{t(
getTranslationID(
"LicenseSummaryPage.label.storageLabel"
)
)}
</h4>
<dt>
{t(
getTranslationID(
"LicenseSummaryPage.label.storageSize"
)
)} )}
</dt> </dt>
<dd>{licenseSummaryInfo.storageSize}GB</dd> <dd>{licenseSummaryInfo.storageSize}GB</dd>
<dt> <dt>
{t(getTranslationID("LicenseSummaryPage.label.usedSize"))} {t(
getTranslationID("LicenseSummaryPage.label.usedSize")
)}
</dt> </dt>
<dd>{licenseSummaryInfo.usedSize}GB</dd> <dd>{licenseSummaryInfo.usedSize}GB</dd>
<dt> <dt className={styles.overLine}>
{t( {t(
getTranslationID( getTranslationID(
"LicenseSummaryPage.label.storageAvailable" "LicenseSummaryPage.label.storageAvailable"
@ -280,14 +305,23 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
</dt> </dt>
<dd> <dd>
{licenseSummaryInfo.isStorageAvailable && ( {licenseSummaryInfo.isStorageAvailable && (
<img src={block} alt="" className={styles.icInTable} /> <img
src={block}
alt=""
className={styles.icInTable}
/>
)} )}
{!licenseSummaryInfo.isStorageAvailable && ( {!licenseSummaryInfo.isStorageAvailable && (
<img src={circle} alt="" className={styles.icInTable} /> <img
src={circle}
alt=""
className={styles.icInTable}
/>
)} )}
</dd> </dd>
</dl> </dl>
</div> </div>
</div>
</section> </section>
</div> </div>
</main> </main>

View File

@ -13,6 +13,7 @@ import {
getTermsInfoAsync, getTermsInfoAsync,
updateAcceptedVersionAsync, updateAcceptedVersionAsync,
selectTier, selectTier,
selectIsLoading,
selectTermVersions, selectTermVersions,
} from "features//terms"; } from "features//terms";
import { selectLocalStorageKeyforIdToken } from "features/login"; import { selectLocalStorageKeyforIdToken } from "features/login";
@ -27,7 +28,7 @@ const TermsPage: React.FC = (): JSX.Element => {
selectLocalStorageKeyforIdToken selectLocalStorageKeyforIdToken
); );
const tier = useSelector(selectTier); const tier = useSelector(selectTier);
const isLoading = useSelector(selectIsLoading);
const [isCheckedEula, setIsCheckedEula] = useState(false); const [isCheckedEula, setIsCheckedEula] = useState(false);
const [isCheckedPrivacyNotice, setIsCheckedPrivacyNotice] = useState(false); const [isCheckedPrivacyNotice, setIsCheckedPrivacyNotice] = useState(false);
const [isCheckedDpa, setIsCheckedDpa] = useState(false); const [isCheckedDpa, setIsCheckedDpa] = useState(false);
@ -88,7 +89,15 @@ const TermsPage: React.FC = (): JSX.Element => {
tier, tier,
dispatch, dispatch,
]); ]);
if (isLoading) {
return (
<>
<Header />
<h3>loading ...</h3>
<Footer />
</>
);
}
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>
<Header /> <Header />
@ -117,7 +126,7 @@ const TermsPage: React.FC = (): JSX.Element => {
> >
{t(getTranslationID("termsPage.label.linkOfEula"))} {t(getTranslationID("termsPage.label.linkOfEula"))}
</a> </a>
{` ${t(getTranslationID("termsPage.label.forOdds"))}`} {` ${t(getTranslationID("termsPage.label.forOdms"))}`}
</p> </p>
<p> <p>
<label> <label>
@ -150,7 +159,7 @@ const TermsPage: React.FC = (): JSX.Element => {
getTranslationID("termsPage.label.linkOfPrivacyNotice") getTranslationID("termsPage.label.linkOfPrivacyNotice")
)} )}
</a> </a>
{` ${t(getTranslationID("termsPage.label.forOdds"))}`} {` ${t(getTranslationID("termsPage.label.forOdms"))}`}
</p> </p>
<p> <p>
<label> <label>
@ -185,7 +194,7 @@ const TermsPage: React.FC = (): JSX.Element => {
> >
{t(getTranslationID("termsPage.label.linkOfDpa"))} {t(getTranslationID("termsPage.label.linkOfDpa"))}
</a> </a>
{` ${t(getTranslationID("termsPage.label.forOdds"))}`} {` ${t(getTranslationID("termsPage.label.forOdms"))}`}
</p> </p>
<p> <p>
<label> <label>

View File

@ -2,13 +2,13 @@
"common": { "common": {
"message": { "message": {
"inputEmptyError": "Pflichtfeld", "inputEmptyError": "Pflichtfeld",
"passwordIncorrectError": "(de)入力されたパスワードがルールを満たしていません。下記のルールを満たすパスワードを入力してください。", "passwordIncorrectError": "Das von Ihnen eingegebene Passwort entspricht nicht den Spezifikationen. Bitte geben Sie das korrekte Passwort ein, wie in den Spezifikationen unten beschrieben.",
"emailIncorrectError": "(de)メールアドレスの形式が不正です。正しいメールアドレスの形式で入力してください。", "emailIncorrectError": "Das Format der E-Mail-Adresse ist ungültig. Bitte geben Sie eine gültige E-Mail-Adresse ein.",
"internalServerError": "Verarbeitung fehlgeschlagen. Bitte versuchen Sie es später noch einmal.", "internalServerError": "Verarbeitung fehlgeschlagen. Bitte versuchen Sie es später noch einmal.",
"listEmpty": "Es gibt 0 Suchergebnisse.", "listEmpty": "Es gibt 0 Suchergebnisse.",
"dialogConfirm": "Möchten Sie die Operation durchführen?", "dialogConfirm": "Möchten Sie die Operation durchführen?",
"success": "Erfolgreich verarbeitet", "success": "Erfolgreich verarbeitet",
"displayDialog": "(de)サインアウトしてもよろしいですか?" "displayDialog": "Möchten Sie sich wirklich abmelden?"
}, },
"label": { "label": {
"cancel": "Abbrechen", "cancel": "Abbrechen",
@ -17,22 +17,21 @@
"save": "Speichern", "save": "Speichern",
"delete": "Löschen", "delete": "Löschen",
"return": "zurückkehren", "return": "zurückkehren",
"operationInsteadOf": "(de)Operation instead of:", "operationInsteadOf": "Betrieb der ODMS Cloud im Auftrag von:",
"headerName": "(de)ODMS Cloud", "headerAccount": "Konto",
"headerAccount": "(de)Account", "headerUser": "Benutzer",
"headerUser": "(de)User", "headerLicense": "Abonnement",
"headerLicense": "(de)License", "headerDictations": "Diktate",
"headerDictations": "(de)Dictations", "headerWorkflow": "Arbeitsablauf",
"headerWorkflow": "(de)Workflow", "headerPartners": "Partner",
"headerPartners": "(de)Partners",
"headerSupport": "(de)Support", "headerSupport": "(de)Support",
"tier1": "(de)Admin", "tier1": "Admin",
"tier2": "(de)BC", "tier2": "BC",
"tier3": "(de)Distributor", "tier3": "Verteiler",
"tier4": "(de)Dealer", "tier4": "Händler",
"tier5": "(de)Customer", "tier5": "Kunde",
"notSelected": "(de)None", "notSelected": "Keine",
"signOutButton": "(de)Sign out" "signOutButton": "Abmelden"
} }
}, },
"topPage": { "topPage": {
@ -49,7 +48,7 @@
"signInButton": "Anmelden", "signInButton": "Anmelden",
"newUser": "Neuer Benutzer", "newUser": "Neuer Benutzer",
"signUpButton": "Benutzerkonto erstellen", "signUpButton": "Benutzerkonto erstellen",
"logoAlt": "(de)OM Dictation Management System in the Cloud" "logoAlt": "OM Dictation Management System in the Cloud"
} }
}, },
"signupPage": { "signupPage": {
@ -75,7 +74,7 @@
"email": "E-Mail-Addresse", "email": "E-Mail-Addresse",
"password": "Passwort", "password": "Passwort",
"termsLink": "Klicken Sie hier, um die Nutzungsbedingungen zu lesen.", "termsLink": "Klicken Sie hier, um die Nutzungsbedingungen zu lesen.",
"termsLinkFor": "(de)for ODDS.", "termsLinkFor": "für ODMS Cloud.",
"termsCheckBox": "Ja, ich stimme den Nutzungsbedingungen zu.", "termsCheckBox": "Ja, ich stimme den Nutzungsbedingungen zu.",
"createAccountButton": "Einreichen" "createAccountButton": "Einreichen"
} }
@ -176,11 +175,12 @@
"freeLicense": "Anzahl ungenutzter Lizenzen", "freeLicense": "Anzahl ungenutzter Lizenzen",
"expiringWithin14daysLicense": "Anzahl der Lizenzen, die innerhalb von 14 Tagen ablaufen", "expiringWithin14daysLicense": "Anzahl der Lizenzen, die innerhalb von 14 Tagen ablaufen",
"issueRequesting": "Gesamtzahl der bestellten Lizenzen", "issueRequesting": "Gesamtzahl der bestellten Lizenzen",
"numberOfRequesting": "Gesamtzahl der Bestellungen",
"shortage": "Mangel", "shortage": "Mangel",
"storageSize": "Lagerung verfügbar", "storageSize": "Lagerung verfügbar",
"usedSize": "Gebrauchter Lagerung", "usedSize": "Gebrauchter Lagerung",
"storageAvailable": "Speicher nicht verfügbar (Menge überschritten)" "storageAvailable": "Speicher nicht verfügbar (Menge überschritten)",
"licenseLabel": "(de)License",
"storageLabel": "(de)Storage"
} }
}, },
"licenseOrderPage": { "licenseOrderPage": {
@ -252,10 +252,7 @@
"fileBackup": "(de)File Backup", "fileBackup": "(de)File Backup",
"downloadForBackup": "(de)Download for backup", "downloadForBackup": "(de)Download for backup",
"applications": "(de)Applications", "applications": "(de)Applications",
"cancelDictation": "(de)Cancel Dictation", "cancelDictation": "Transkription abbrechen"
"general": "(de)General",
"job": "(de)Job",
"close": "(de)Close"
} }
}, },
"cardLicenseIssuePopupPage": { "cardLicenseIssuePopupPage": {
@ -377,29 +374,29 @@
"workflowPage": { "workflowPage": {
"label": { "label": {
"title": "Arbeitsablauf", "title": "Arbeitsablauf",
"addRoutingRule": "(de)Add Routing Rule", "addRoutingRule": "Routing-Regel hinzufügen",
"editRoutingRule": "(de)Edit Routing Rule", "editRoutingRule": "Routing-Regel bearbeiten",
"templateSetting": "(de)Template Setting", "templateSetting": "Vorlageneinstellung",
"worktypeIdSetting": "(de)WorktypeID Setting", "worktypeIdSetting": "Einstellung der Aufgabentypkennung",
"typistGroupSetting": "(de)Transcriptionist Group Setting", "typistGroupSetting": "Gruppeneinstellung für Transkriptionisten",
"authorID": "Autoren-ID", "authorID": "Autoren-ID",
"worktype": "Aufgabentypkennung", "worktype": "Aufgabentypkennung",
"worktypeOptional": "(de)Worktype ID (Optional)", "worktypeOptional": "Aufgabentypkennung (Optional)",
"transcriptionist": "Transkriptionist", "transcriptionist": "Transkriptionist",
"template": "(de)Template", "template": "Vorlage",
"templateOptional": "(de)Template (Optional)", "templateOptional": "Vorlage (Optional)",
"editRule": "(de)Edit Rule", "editRule": "Regel bearbeiten",
"selected": "Ausgewählter transkriptionist", "selected": "Ausgewählter transkriptionist",
"pool": "Transkriptionsliste", "pool": "Transkriptionsliste",
"selectAuthor": "(de)Select Author ID", "selectAuthor": "Autoren-ID auswählen",
"selectWorktypeId": "(de)Select Worktype ID", "selectWorktypeId": "Aufgabentypkennung auswählen",
"selectTemplate": "(de)Select Template" "selectTemplate": "Vorlage auswählen"
}, },
"message": { "message": {
"selectedTypistEmptyError": "(de)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。", "selectedTypistEmptyError": "Transkriptionist oder Transkriptionistgruppe wurde nicht ausgewählt. Bitte wählen Sie eine oder mehrere aus der Transkriptionsliste aus.",
"workflowConflictError": "(de)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。", "workflowConflictError": "Eine Routing-Regel wurde bereits mit der angegebenen Kombination aus AuthorID und WorktypeID registriert. Bitte registrieren Sie sich mit einer anderen Kombination.",
"inputEmptyError": "Pflichtfeld", "inputEmptyError": "Pflichtfeld",
"saveFailedError": "(de)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください" "saveFailedError": "Die Routing-Regel konnte nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut."
} }
}, },
"typistGroupSetting": { "typistGroupSetting": {
@ -433,38 +430,38 @@
"addWorktype": "Aufgabentyp hinzufügen", "addWorktype": "Aufgabentyp hinzufügen",
"editWorktypeId": "Aufgabentypkennung bearbeiten", "editWorktypeId": "Aufgabentypkennung bearbeiten",
"saveChange": "Änderungen speichern", "saveChange": "Änderungen speichern",
"editOptionItems": "(de)Option Item", "editOptionItems": "Optionales Attribut",
"itemLabel": "(de)Item label", "itemLabel": "Artikeletiketten",
"defaultValue": "(de)Default value", "defaultValue": "Standardwert",
"initialValue": "(de)Initial value", "initialValue": "Anfangswert",
"default": "(de)Default", "default": "Standard",
"blank": "(de)Blank", "blank": "Leer",
"lastInput": "(de)Last Input", "lastInput": "Letzte Werteingabe",
"optionItemTerms": "(de)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ." "optionItemTerms": "Die Artikeletiketten und der Anfangswert können alphanumerische Zeichen und Symbole enthalten. Die folgenden Symbole können nicht verwendet werden:\\/:*?\"<>|."
}, },
"message": { "message": {
"worktypeIdIncorrectError": "Die von Ihnen eingegebene Aufgabentypkennung entspricht nicht den Spezifikationen. Bitte geben Sie den korrekten Arbeitstyp ein, wie in den Spezifikationen unten beschrieben.", "worktypeIdIncorrectError": "Die von Ihnen eingegebene Aufgabentypkennung entspricht nicht den Spezifikationen. Bitte geben Sie den korrekten Arbeitstyp ein, wie in den Spezifikationen unten beschrieben.",
"alreadyWorktypeIdExistError": "Diese Aufgabentypkennung ist derzeit registriert. Bitte registrieren Sie sich mit einer anderen Worktype-ID.", "alreadyWorktypeIdExistError": "Diese Aufgabentypkennung ist derzeit registriert. Bitte registrieren Sie sich mit einer anderen Worktype-ID.",
"worktypeIDLimitError": "Die Aufgabentypkennung kann nicht hinzugefügt werden, da die maximale Anzahl an Registrierungen erreicht wurde.", "worktypeIDLimitError": "Die Aufgabentypkennung kann nicht hinzugefügt werden, da die maximale Anzahl an Registrierungen erreicht wurde.",
"optionItemInvalidError": "(de)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "Die Einstellung des Anfangswerts wurde nicht abgeschlossen. Bitte legen Sie den Anfangswert fest oder ändern Sie den Standardwert.",
"worktypeIdAlreadyDeletedError": "(de)WorktypeIDは既に削除されています。画面を更新し、再度ご確認ください", "worktypeIdAlreadyDeletedError": "Aufgabentypkennung wurde bereits gelöscht. Bitte aktualisieren Sie Ihren Bildschirm und überprüfen Sie es erneut.",
"optionItemSaveFailedError": "(de)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "Optionales Attribut konnte nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut.",
"optionItemIncorrectError": "(de)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "Die eingegebene Artikeletiketten oder der Anfangswert entspricht nicht den Regeln. Bitte geben Sie einen Wert ein, der den folgenden Regeln entspricht.",
"updateActiveWorktypeFailedError": "(de)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", "updateActiveWorktypeFailedError": "Die aktive Aufgabentypkennung konnte nicht gespeichert werden. Bitte aktualisieren Sie Ihren Bildschirm und überprüfen Sie es erneut.",
"worktypeInUseError": "(de)このWorktype IDはルーティングルールで使用されているため削除できません。", "worktypeInUseError": "Diese Aufgabentypkennung kann nicht gelöscht werden, da sie derzeit in einer Routing-Regel verwendet wird.",
"updateWorktypeFailedError": "(de)WorktypeIDの保存に失敗しました。画面を更新し、再度ご確認ください" "updateWorktypeFailedError": "Aufgabentypkennung konnte nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut."
} }
}, },
"templateFilePage": { "templateFilePage": {
"label": { "label": {
"title": "(de)Template List", "title": "Vorlagenliste",
"addTemplate": "(de)Add Template", "addTemplate": "Vorlage hinzufügen",
"fileName": "(de)Flie Name", "fileName": "Dateiname",
"chooseFile": "(de)Choose File", "chooseFile": "Datei aussuchen",
"notFileChosen": "(de)- Not file chosen -", "notFileChosen": "- Keine Datei ausgewählt -",
"fileSizeTerms": "(de)ファイルサイズは5MBまでです。", "fileSizeTerms": "Die maximale Dateigröße, die gespeichert werden kann, beträgt 5 MB.",
"fileSizeError": "(de)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。", "fileSizeError": "Die ausgewählte Dateigröße ist zu groß. Bitte wählen Sie eine Datei mit einer Größe von 5 MB oder weniger aus.",
"fileEmptyError": "(de)ファイル選択は必須です。ファイルを選択してください。" "fileEmptyError": "Dateiauswahl ist erforderlich. Bitte wählen Sie eine Datei aus."
} }
}, },
"partnerPage": { "partnerPage": {
@ -473,7 +470,7 @@
"addAccount": "Konto hinzufügen", "addAccount": "Konto hinzufügen",
"name": "Name der Firma", "name": "Name der Firma",
"category": "Kontoebene", "category": "Kontoebene",
"accountId": "Autoren-ID", "accountId": "Konto-ID",
"country": "Land", "country": "Land",
"primaryAdmin": "Hauptadministrator", "primaryAdmin": "Hauptadministrator",
"email": "Email", "email": "Email",
@ -482,61 +479,60 @@
"deleteAccount": "Konto löschen" "deleteAccount": "Konto löschen"
}, },
"message": { "message": {
"delegateNotAllowedError": "(de)パートナーの代行操作が許可されていません。画面を更新し、再度ご確認ください。", "delegateNotAllowedError": "Aktionen im Namen des Partners sind nicht zulässig. Bitte aktualisieren Sie den Bildschirm und überprüfen Sie ihn erneut.",
"deleteFailedError": "(de)代行操作に失敗しました。画面を更新し、再度ご確認ください。", "deleteFailedError": "Der Delegierungsvorgang ist fehlgeschlagen. Bitte aktualisieren Sie den Bildschirm und überprüfen Sie ihn erneut.",
"delegateCancelError": "(de)代行操作の許可が取り消されたため、代行操作を終了しました。" "delegateCancelError": "Der delegierte Vorgang wurde beendet, da die Berechtigung für den delegierten Vorgang widerrufen wurde."
} }
}, },
"accountPage": { "accountPage": {
"label": { "label": {
"title": "(de)Account", "title": "Konto",
"fileDeleteSetting": "(de)File Delete Setting", "fileDeleteSetting": "Einstellung zum Löschen von Dateien",
"accountInformation": "(de)Account Information", "accountInformation": "Kontoinformationen",
"companyName": "(de)Company Name", "companyName": "Name der Firma",
"accountID": "(de)Account ID", "accountID": "Konto-ID",
"yourCategory": "(de)Your Category", "yourCategory": "Konto Typ",
"yourCountry": "(de)Your Country", "yourCountry": "Land",
"yourDealer": "(de)Your DealerUpper layer", "yourDealer": "Händler",
"selectDealer": "(de)Select Dealer", "selectDealer": "Händler auswählen",
"dealerManagement": "(de)Dealer Management", "dealerManagement": "Erlauben Sie dem Händler, Änderungen vorzunehmen",
"administratorInformation": "(de)Administrator Information", "administratorInformation": "Administratorinformationen",
"primaryAdministrator": "(de)Primary Administrator", "primaryAdministrator": "Hauptadministrator",
"secondaryAdministrator": "(de)Secondary Administrator", "secondaryAdministrator": "Zweiter Administrator",
"emailAddress": "(de)E-mail address", "emailAddress": "E-Mail-Addresse",
"selectSecondaryAdministrator": "(de)Select Secondary Administrator", "selectSecondaryAdministrator": "Sekundäradministrator auswählen",
"saveChanges": "(de)Save Changes", "saveChanges": "Änderungen speichern",
"deleteAccount": "(de)Delete Account" "deleteAccount": "Konto löschen"
}, },
"message": { "message": {
"updateAccountFailedError": "(de)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください" "updateAccountFailedError": "Kontoinformationen konnten nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut."
} }
}, },
"deleteAccountPopup": { "deleteAccountPopup": {
"label": { "label": {
"title": "(de)Delete Account", "title": "Konto löschen",
"subTitle": "(de)Delete your account", "subTitle": "Lösche deinen Konto?",
"cautionOfDeleteingAccountData": "(de)Deleting your account will remove all of your audio files and\nlicenses from system. and you'll cannot use ODMS Cloud.\nThis cannot be undone.", "cautionOfDeleteingAccountData": "Durch das Löschen Ihres Kontos werden alle Benutzerinformationen, Lizenzen und Diktatdateien aus dem System entfernt. Gelöschte Informationen können nicht wiederhergestellt werden.",
"deleteButton": "(de)Delete account", "deleteButton": "Konto löschen",
"cancelButton": "(de)Cancel" "cancelButton": "Abbrechen"
} }
}, },
"accountDeleteSuccess": { "accountDeleteSuccess": {
"label": { "label": {
"title": "(de)Account Delete Success", "title": "Kontolöschung erfolgreich",
"message": "(de)Your account has been deleted. Thank you for using our services.", "message": "Dein Account wurde gelöscht. Vielen Dank, dass Sie die ODMS Cloud nutzen.",
"backToTopPageLink": "(de)Back to TOP Page" "backToTopPageLink": "Zurück zur Startseite"
} }
}, },
"termsPage": { "termsPage": {
"label": { "label": {
"title": "(de)Terms of Use has updated. Please confirm again.", "title": "Die Nutzungsbedingungen wurden aktualisiert. Für die weitere Nutzung der ODMS Cloud ist eine erneute Bestätigung erforderlich.",
"linkOfEula": "(de)Click here to read the terms of use.", "linkOfEula": "Klicken Sie hier, um die Endbenutzer-Lizenzvereinbarung zu lesen.",
"linkOfDpa": "(de)Click here to read the terms of use.", "linkOfDpa": "Klicken Sie hier, um die Datenverarbeitungsvereinbarung zu lesen.",
"linkOfPrivacyNotice": "(de)Click here to read the terms of use.", "checkBoxForConsent": "Ja, ich stimme den Nutzungsbedingungen zu.",
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.", "forOdms": "für ODMS Cloud.",
"forOdds": "(de)for ODDS.", "button": "Fortsetzen",
"button": "(de)Continue", "linkOfPrivacyNotice": "Klicken Sie hier, um die Datenschutzerklärung zu lesen."
"linkOfPrivacyNotice": "(de)Click here to read the terms of use."
} }
}, },
"supportPage": { "supportPage": {

View File

@ -2,13 +2,13 @@
"common": { "common": {
"message": { "message": {
"inputEmptyError": "Mandatory Field", "inputEmptyError": "Mandatory Field",
"passwordIncorrectError": "入力されたパスワードがルールを満たしていません。下記のルールを満たすパスワードを入力してください。", "passwordIncorrectError": "The password you entered does not meet specifications. Please enter the correct password as outlined in the specifications below.",
"emailIncorrectError": "メールアドレスの形式が不正です。正しいメールアドレスの形式で入力してください。", "emailIncorrectError": "The format of the e-mail address is not valid. Please enter a valid email address.",
"internalServerError": "Processing failed. Please try again later. ", "internalServerError": "Processing failed. Please try again later. ",
"listEmpty": "There are 0 search results.", "listEmpty": "There are 0 search results.",
"dialogConfirm": "Do you want to perform the operation?", "dialogConfirm": "Do you want to perform the operation?",
"success": "Successfully Processed", "success": "Successfully Processed",
"displayDialog": "サインアウトしてもよろしいですか?" "displayDialog": "Are you sure you want to sign out?"
}, },
"label": { "label": {
"cancel": "Cancel", "cancel": "Cancel",
@ -17,11 +17,10 @@
"save": "Save", "save": "Save",
"delete": "Delete", "delete": "Delete",
"return": "Return", "return": "Return",
"operationInsteadOf": "Operation instead of:", "operationInsteadOf": "Operating the ODMS Cloud on behalf of:",
"headerName": "ODMS Cloud",
"headerAccount": "Account", "headerAccount": "Account",
"headerUser": "User", "headerUser": "User",
"headerLicense": "License", "headerLicense": "Subscription",
"headerDictations": "Dictations", "headerDictations": "Dictations",
"headerWorkflow": "Workflow", "headerWorkflow": "Workflow",
"headerPartners": "Partners", "headerPartners": "Partners",
@ -75,7 +74,7 @@
"email": "Email Address", "email": "Email Address",
"password": "Password", "password": "Password",
"termsLink": "Click here to read the terms of use", "termsLink": "Click here to read the terms of use",
"termsLinkFor": "for ODDS.", "termsLinkFor": "for OMDS Cloud.",
"termsCheckBox": "Yes, I agree to the terms of use.", "termsCheckBox": "Yes, I agree to the terms of use.",
"createAccountButton": "Submit" "createAccountButton": "Submit"
} }
@ -176,11 +175,12 @@
"freeLicense": "Number of unused licenses", "freeLicense": "Number of unused licenses",
"expiringWithin14daysLicense": "Number of licenses expiring within 14 days", "expiringWithin14daysLicense": "Number of licenses expiring within 14 days",
"issueRequesting": "Total number of licenses on order", "issueRequesting": "Total number of licenses on order",
"numberOfRequesting": "Total number of orders",
"shortage": "Shortage", "shortage": "Shortage",
"storageSize": "Storage Available", "storageSize": "Storage Available",
"usedSize": "Storage Used", "usedSize": "Storage Used",
"storageAvailable": "Storage Unavailable (Exceeded Amount)" "storageAvailable": "Storage Unavailable (Exceeded Amount)",
"licenseLabel": "License",
"storageLabel": "Storage"
} }
}, },
"licenseOrderPage": { "licenseOrderPage": {
@ -252,10 +252,7 @@
"fileBackup": "File Backup", "fileBackup": "File Backup",
"downloadForBackup": "Download for backup", "downloadForBackup": "Download for backup",
"applications": "Applications", "applications": "Applications",
"cancelDictation": "Cancel Dictation", "cancelDictation": "Cancel Transcription"
"general": "General",
"job": "Job",
"close": "Close"
} }
}, },
"cardLicenseIssuePopupPage": { "cardLicenseIssuePopupPage": {
@ -396,10 +393,10 @@
"selectTemplate": "Select Template" "selectTemplate": "Select Template"
}, },
"message": { "message": {
"selectedTypistEmptyError": "Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。", "selectedTypistEmptyError": "Transcriptionist, or Transcriptionist Group has not been selected. Please select one or more from the Transcription List.",
"workflowConflictError": "指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。", "workflowConflictError": "A routing rule has already been registered with the specified AuthorID and WorktypeID combination. Please register with different combination.",
"inputEmptyError": "Mandatory Field", "inputEmptyError": "Mandatory Field",
"saveFailedError": "ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください" "saveFailedError": "Failed to save the routing rule. Please refresh the screen and try again."
} }
}, },
"typistGroupSetting": { "typistGroupSetting": {
@ -434,37 +431,37 @@
"editWorktypeId": "Edit Worktype ID", "editWorktypeId": "Edit Worktype ID",
"saveChange": "Save Changes", "saveChange": "Save Changes",
"editOptionItems": "Option Item", "editOptionItems": "Option Item",
"itemLabel": "Item label", "itemLabel": "Item labels",
"defaultValue": "Default value", "defaultValue": "Default value",
"initialValue": "Initial value", "initialValue": "Initial value",
"default": "Default", "default": "Default",
"blank": "Blank", "blank": "Blank",
"lastInput": "Last Input", "lastInput": "Last input value",
"optionItemTerms": "The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ." "optionItemTerms": "The Item label and Initial value can contain alphanumeric and symbols. The following symbols cannot be used:\\/:*?\"<>|."
}, },
"message": { "message": {
"worktypeIdIncorrectError": "The Worktype ID you entered does not meet specifications. Please enter the correct Worktype as outlined in the specifications below.", "worktypeIdIncorrectError": "The Worktype ID you entered does not meet specifications. Please enter the correct Worktype as outlined in the specifications below.",
"alreadyWorktypeIdExistError": "This Worktype ID is currently registered. Please register with a different Worktype ID.", "alreadyWorktypeIdExistError": "This Worktype ID is currently registered. Please register with a different Worktype ID.",
"worktypeIDLimitError": "Worktype ID cannot be added because it has reached the maximum number of registrations.", "worktypeIDLimitError": "Worktype ID cannot be added because it has reached the maximum number of registrations.",
"optionItemInvalidError": "Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "Initial value setting has not been completed. Please set the Initial value or change the Default value.",
"worktypeIdAlreadyDeletedError": "WorktypeIDは既に削除されています。画面を更新し、再度ご確認ください", "worktypeIdAlreadyDeletedError": "WorktypeID has already been deleted. Please refresh your screen and check again.",
"optionItemSaveFailedError": "オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "Failed to save Option Item. Please refresh the screen and try again.",
"optionItemIncorrectError": "入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "The entered Item label or Initial value does not meet the rules. Please enter a value that meets the rules below.",
"updateActiveWorktypeFailedError": "Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", "updateActiveWorktypeFailedError": "Failed to save Active WorktypeID. Please refresh your screen and check again.",
"worktypeInUseError": "このWorktype IDはルーティングルールで使用されているため削除できません。", "worktypeInUseError": "This Worktype ID cannot be deleted because it is currently being used in a routing rule.",
"updateWorktypeFailedError": "WorktypeIDの保存に失敗しました。画面を更新し、再度ご確認ください" "updateWorktypeFailedError": "Failed to save WorktypeID. Please refresh the screen and try again."
} }
}, },
"templateFilePage": { "templateFilePage": {
"label": { "label": {
"title": "Template List", "title": "Template List",
"addTemplate": "Add Template", "addTemplate": "Add Template",
"fileName": "Flie Name", "fileName": "File Name",
"chooseFile": "Choose File", "chooseFile": "Select file",
"notFileChosen": "- Not file chosen -", "notFileChosen": "- No file selected -",
"fileSizeTerms": "ファイルサイズは5MBまでです。", "fileSizeTerms": "The maximum file size that can be saved is 5MB.",
"fileSizeError": "選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。", "fileSizeError": "The selected file size is too large. Please select a file that is 5MB or less in size.",
"fileEmptyError": "ファイル選択は必須です。ファイルを選択してください。" "fileEmptyError": "File selection is required. Please select a file."
} }
}, },
"partnerPage": { "partnerPage": {
@ -482,9 +479,9 @@
"deleteAccount": "Delete Account" "deleteAccount": "Delete Account"
}, },
"message": { "message": {
"delegateNotAllowedError": "パートナーの代行操作が許可されていません。画面を更新し、再度ご確認ください。", "delegateNotAllowedError": "Actions on behalf of partner are not allowed. Please refresh the screen and check again.",
"deleteFailedError": "代行操作に失敗しました。画面を更新し、再度ご確認ください。", "deleteFailedError": "Delegate operation failed. Please refresh the screen and check again.",
"delegateCancelError": "代行操作の許可が取り消されたため、代行操作を終了しました。" "delegateCancelError": "The delegated operation has been terminated because permission for the delegated operation has been revoked."
} }
}, },
"accountPage": { "accountPage": {
@ -494,49 +491,48 @@
"accountInformation": "Account Information", "accountInformation": "Account Information",
"companyName": "Company Name", "companyName": "Company Name",
"accountID": "Account ID", "accountID": "Account ID",
"yourCategory": "Your Category", "yourCategory": "Account Type",
"yourCountry": "Your Country", "yourCountry": "Country",
"yourDealer": "Your DealerUpper layer", "yourDealer": "Dealer",
"selectDealer": "Select Dealer", "selectDealer": "Select Dealer",
"dealerManagement": "Dealer Management", "dealerManagement": "Dealer Management",
"administratorInformation": "Administrator Information", "administratorInformation": "Administrator Information",
"primaryAdministrator": "Primary Administrator", "primaryAdministrator": "Primary administrator",
"secondaryAdministrator": "Secondary Administrator", "secondaryAdministrator": "Secondary Administrator",
"emailAddress": "E-mail address", "emailAddress": "Email Address",
"selectSecondaryAdministrator": "Select Secondary Administrator", "selectSecondaryAdministrator": "Select Secondary Administrator",
"saveChanges": "Save Changes", "saveChanges": "Save Changes",
"deleteAccount": "Delete Account" "deleteAccount": "Delete Account"
}, },
"message": { "message": {
"updateAccountFailedError": "アカウント情報の保存に失敗しました。画面を更新し、再度実行してください" "updateAccountFailedError": "Failed to save account information. Please refresh the screen and try again."
} }
}, },
"deleteAccountPopup": { "deleteAccountPopup": {
"label": { "label": {
"title": "Delete Account", "title": "Delete Account",
"subTitle": "Delete your account", "subTitle": "Delete your account",
"cautionOfDeleteingAccountData": "Deleting your account will remove all of your audio files and\nlicenses from system. and you'll cannot use ODMS Cloud.\nThis cannot be undone.", "cautionOfDeleteingAccountData": "Deleting your account will remove all user information, licenses, and dictation files from the system. Deleted information cannot be restored.",
"deleteButton": "Delete account", "deleteButton": "Delete Account",
"cancelButton": "Cancel" "cancelButton": "Cancel"
} }
}, },
"accountDeleteSuccess": { "accountDeleteSuccess": {
"label": { "label": {
"title": "Account Delete Success", "title": "Account Delete Success",
"message": "Your account has been deleted. Thank you for using our services.", "message": "Your account has been deleted. Thank you for using the ODMS Cloud.",
"backToTopPageLink": "Back to TOP Page" "backToTopPageLink": "Back to TOP Page"
} }
}, },
"termsPage": { "termsPage": {
"label": { "label": {
"title": "Terms of Use has updated. Please confirm again.", "title": "Terms of Use have been updated. Reconfirmation is required to continue using the ODMS Cloud.",
"linkOfEula": "Click here to read the terms of use.", "linkOfEula": "Click here to read the End User License Agreement.",
"linkOfDpa": "Click here to read the terms of use.", "linkOfDpa": "Click here to read the Data Processing Agreement.",
"linkOfPrivacyNotice": "Click here to read the terms of use.",
"checkBoxForConsent": "Yes, I agree to the terms of use.", "checkBoxForConsent": "Yes, I agree to the terms of use.",
"forOdds": "for ODDS.", "forOdms": "for ODMS Cloud.",
"button": "Continue", "button": "Continue",
"linkOfPrivacyNotice": "Click here to read the terms of use." "linkOfPrivacyNotice": "Click here to read the Privacy Notice."
} }
}, },
"supportPage": { "supportPage": {

View File

@ -2,13 +2,13 @@
"common": { "common": {
"message": { "message": {
"inputEmptyError": "Campo obligatorio", "inputEmptyError": "Campo obligatorio",
"passwordIncorrectError": "(es)入力されたパスワードがルールを満たしていません。下記のルールを満たすパスワードを入力してください。", "passwordIncorrectError": "La contraseña que ingresó no cumple con las especificaciones. Ingrese la contraseña correcta como se describe en las especificaciones a continuación.",
"emailIncorrectError": "(es)メールアドレスの形式が不正です。正しいメールアドレスの形式で入力してください。", "emailIncorrectError": "El formato de la dirección de correo electrónico no es válido. Por favor, introduce una dirección de correo electrónico válida.",
"internalServerError": "El procesamiento falló. Por favor, inténtelo de nuevo más tarde.", "internalServerError": "El procesamiento falló. Por favor, inténtelo de nuevo más tarde.",
"listEmpty": "Hay 0 resultados de búsqueda.", "listEmpty": "Hay 0 resultados de búsqueda.",
"dialogConfirm": "¿Quieres realizar la operación?", "dialogConfirm": "¿Quieres realizar la operación?",
"success": "Procesado con éxito", "success": "Procesado con éxito",
"displayDialog": "(es)サインアウトしてもよろしいですか?" "displayDialog": "¿Estás seguro de que quieres cerrar sesión?"
}, },
"label": { "label": {
"cancel": "Cancelar", "cancel": "Cancelar",
@ -17,22 +17,21 @@
"save": "Ahorrar", "save": "Ahorrar",
"delete": "Delete", "delete": "Delete",
"return": "Devolver", "return": "Devolver",
"operationInsteadOf": "(es)Operation instead of:", "operationInsteadOf": "Operar la nube ODMS en nombre de:",
"headerName": "(es)ODMS Cloud", "headerAccount": "Cuenta",
"headerAccount": "(es)Account", "headerUser": "Usuario",
"headerUser": "(es)User", "headerLicense": "Suscripción",
"headerLicense": "(es)License", "headerDictations": "Dictado",
"headerDictations": "(es)Dictations", "headerWorkflow": "flujo de trabajo",
"headerWorkflow": "(es)Workflow", "headerPartners": "Socios",
"headerPartners": "(es)Partners",
"headerSupport": "(es)Support", "headerSupport": "(es)Support",
"tier1": "(es)Admin", "tier1": "Admin",
"tier2": "(es)BC", "tier2": "BC",
"tier3": "(es)Distributor", "tier3": "Distribuidor",
"tier4": "(es)Dealer", "tier4": "Concesionario",
"tier5": "(es)Customer", "tier5": "Cliente",
"notSelected": "(es)None", "notSelected": "Ninguno",
"signOutButton": "(es)Sign out" "signOutButton": "cerrar sesión"
} }
}, },
"topPage": { "topPage": {
@ -49,7 +48,7 @@
"signInButton": "Iniciar sesión", "signInButton": "Iniciar sesión",
"newUser": "Nuevo usuario", "newUser": "Nuevo usuario",
"signUpButton": "Crear una cuenta", "signUpButton": "Crear una cuenta",
"logoAlt": "(es)OM Dictation Management System in the Cloud" "logoAlt": "OM Dictation Management System in the Cloud"
} }
}, },
"signupPage": { "signupPage": {
@ -75,7 +74,7 @@
"email": "Dirección de correo electrónico", "email": "Dirección de correo electrónico",
"password": "Contraseña", "password": "Contraseña",
"termsLink": "Haga clic aquí para leer el término de uso.", "termsLink": "Haga clic aquí para leer el término de uso.",
"termsLinkFor": "(es)for ODDS.", "termsLinkFor": "para la nube ODMS.",
"termsCheckBox": "Sí, estoy de acuerdo con los términos de uso.", "termsCheckBox": "Sí, estoy de acuerdo con los términos de uso.",
"createAccountButton": "Entregar" "createAccountButton": "Entregar"
} }
@ -176,11 +175,12 @@
"freeLicense": "Número de licencias sin usar", "freeLicense": "Número de licencias sin usar",
"expiringWithin14daysLicense": "Número de licencias que vencen en 14 días", "expiringWithin14daysLicense": "Número de licencias que vencen en 14 días",
"issueRequesting": "Número total de licencias en pedido", "issueRequesting": "Número total de licencias en pedido",
"numberOfRequesting": "Número total de pedidos",
"shortage": "Escasez", "shortage": "Escasez",
"storageSize": "Almacenamiento disponible", "storageSize": "Almacenamiento disponible",
"usedSize": "Almacenamiento utilizado", "usedSize": "Almacenamiento utilizado",
"storageAvailable": "Almacenamiento no disponible (cantidad excedida)" "storageAvailable": "Almacenamiento no disponible (cantidad excedida)",
"licenseLabel": "(es)License",
"storageLabel": "(es)Storage"
} }
}, },
"licenseOrderPage": { "licenseOrderPage": {
@ -252,10 +252,7 @@
"fileBackup": "(es)File Backup", "fileBackup": "(es)File Backup",
"downloadForBackup": "(es)Download for backup", "downloadForBackup": "(es)Download for backup",
"applications": "(es)Applications", "applications": "(es)Applications",
"cancelDictation": "(es)Cancel Dictation", "cancelDictation": "Cancelar transcripción"
"general": "(es)General",
"job": "(es)Job",
"close": "(es)Close"
} }
}, },
"cardLicenseIssuePopupPage": { "cardLicenseIssuePopupPage": {
@ -377,29 +374,29 @@
"workflowPage": { "workflowPage": {
"label": { "label": {
"title": "flujo de trabajo", "title": "flujo de trabajo",
"addRoutingRule": "(es)Add Routing Rule", "addRoutingRule": "Agregar regla de enrutamiento",
"editRoutingRule": "(es)Edit Routing Rule", "editRoutingRule": "Editar regla de enrutamiento",
"templateSetting": "(es)Template Setting", "templateSetting": "Configuración de plantilla",
"worktypeIdSetting": "(es)WorktypeID Setting", "worktypeIdSetting": "Configuración de ID de tipo de trabajo",
"typistGroupSetting": "(es)Transcriptionist Group Setting", "typistGroupSetting": "Configuración del grupo transcriptor",
"authorID": "ID de autor", "authorID": "ID de autor",
"worktype": "ID de tipo de trabajo", "worktype": "ID de tipo de trabajo",
"worktypeOptional": "(es)Worktype ID (Optional)", "worktypeOptional": "ID de tipo de trabajo (Opcional)",
"transcriptionist": "Transcriptor", "transcriptionist": "Transcriptor",
"template": "(es)Template", "template": "Plantilla",
"templateOptional": "(es)Template (Optional)", "templateOptional": "Plantilla (Opcional)",
"editRule": "(es)Edit Rule", "editRule": "Editar regla",
"selected": "Transcriptor seleccionado", "selected": "Transcriptor seleccionado",
"pool": "Lista de transcriptor", "pool": "Lista de transcriptor",
"selectAuthor": "(es)Select Author ID", "selectAuthor": "Seleccionar ID de autor",
"selectWorktypeId": "(es)Select Worktype ID", "selectWorktypeId": "Seleccionar ID de tipo de trabajo",
"selectTemplate": "(es)Select Template" "selectTemplate": "Seleccionar Plantilla"
}, },
"message": { "message": {
"selectedTypistEmptyError": "(es)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。", "selectedTypistEmptyError": "No se ha seleccionado el transcriptor o el grupo de transcriptores. Seleccione uno o más de la lista de transcripción.",
"workflowConflictError": "(es)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。", "workflowConflictError": "Ya se ha registrado una regla de enrutamiento con la combinación AuthorID y WorktypeID especificada. Regístrese con una combinación diferente.",
"inputEmptyError": "Campo obligatorio", "inputEmptyError": "Campo obligatorio",
"saveFailedError": "(es)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください" "saveFailedError": "No se pudo guardar la regla de enrutamiento. Actualice la pantalla e inténtelo de nuevo."
} }
}, },
"typistGroupSetting": { "typistGroupSetting": {
@ -433,38 +430,38 @@
"addWorktype": "Agregar tipo de trabajo", "addWorktype": "Agregar tipo de trabajo",
"editWorktypeId": "Editar ID de tipo de trabajo", "editWorktypeId": "Editar ID de tipo de trabajo",
"saveChange": "Guardar cambios", "saveChange": "Guardar cambios",
"editOptionItems": "(es)Option Item", "editOptionItems": "Elemento opcional",
"itemLabel": "(es)Item label", "itemLabel": "Etiquetas de elementos",
"defaultValue": "(es)Default value", "defaultValue": "Valor por defecto",
"initialValue": "(es)Initial value", "initialValue": "Valor inicial",
"default": "(es)Default", "default": "Por defecto",
"blank": "(es)Blank", "blank": "Vacío",
"lastInput": "(es)Last Input", "lastInput": "Última introducción de valor",
"optionItemTerms": "(es)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ." "optionItemTerms": "La Etiquetas de elementos y el valor inicial pueden contener símbolos y alfanuméricos. No se pueden utilizar los siguientes símbolos:\\/:*?\"<>|."
}, },
"message": { "message": {
"worktypeIdIncorrectError": "El ID de tipo de trabajo que ingresó no cumple con las especificaciones. Ingrese el tipo de trabajo correcto como se describe en las especificaciones a continuación.", "worktypeIdIncorrectError": "El ID de tipo de trabajo que ingresó no cumple con las especificaciones. Ingrese el tipo de trabajo correcto como se describe en las especificaciones a continuación.",
"alreadyWorktypeIdExistError": "Este ID de tipo de trabajo está registrado actualmente. Regístrese con una ID de tipo de trabajo diferente.", "alreadyWorktypeIdExistError": "Este ID de tipo de trabajo está registrado actualmente. Regístrese con una ID de tipo de trabajo diferente.",
"worktypeIDLimitError": "No se puede agregar el ID de tipo de trabajo porque ha alcanzado el número máximo de registros.", "worktypeIDLimitError": "No se puede agregar el ID de tipo de trabajo porque ha alcanzado el número máximo de registros.",
"optionItemInvalidError": "(es)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "La configuración del valor inicial no se ha completado. Establezca el valor inicial o cambie el valor predeterminado.",
"worktypeIdAlreadyDeletedError": "(es)WorktypeIDは既に削除されています。画面を更新し、再度ご確認ください", "worktypeIdAlreadyDeletedError": "El ID de tipo de trabajo ya se ha eliminado. Actualice su pantalla y verifique nuevamente.",
"optionItemSaveFailedError": "(es)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "No se pudo guardar el Elemento opcional. Actualice la pantalla e inténtelo de nuevo.",
"optionItemIncorrectError": "(es)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "La Etiquetas de elementos o el valor inicial no cumple con las reglas. Ingrese un valor que cumpla con las reglas a continuación.",
"updateActiveWorktypeFailedError": "(es)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", "updateActiveWorktypeFailedError": "No se pudo guardar el ID de tipo de trabajo activo. Actualice su pantalla y verifique nuevamente.",
"worktypeInUseError": "(es)このWorktype IDはルーティングルールで使用されているため削除できません。", "worktypeInUseError": "Este ID de tipo de trabajo no se puede eliminar porque actualmente se está utilizando en una regla de enrutamiento.",
"updateWorktypeFailedError": "(es)WorktypeIDの保存に失敗しました。画面を更新し、再度ご確認ください" "updateWorktypeFailedError": "No se pudo guardar el ID de tipo de trabajo. Actualice la pantalla e inténtelo de nuevo."
} }
}, },
"templateFilePage": { "templateFilePage": {
"label": { "label": {
"title": "(es)Template List", "title": "Lista de plantillas",
"addTemplate": "(es)Add Template", "addTemplate": "Agregar plantilla",
"fileName": "(es)Flie Name", "fileName": "Nombre del archivo",
"chooseFile": "(es)Choose File", "chooseFile": "Seleccione Archivo",
"notFileChosen": "(es)- Not file chosen -", "notFileChosen": "- Ningún archivo seleccionado -",
"fileSizeTerms": "(es)ファイルサイズは5MBまでです。", "fileSizeTerms": "El tamaño máximo de archivo que se puede guardar es de 5 MB.",
"fileSizeError": "(es)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。", "fileSizeError": "El tamaño del archivo seleccionado es demasiado grande. Seleccione un archivo que tenga un tamaño de 5 MB o menos.",
"fileEmptyError": "(es)ファイル選択は必須です。ファイルを選択してください。" "fileEmptyError": "Se requiere selección de archivos. Por favor seleccione un archivo."
} }
}, },
"partnerPage": { "partnerPage": {
@ -473,70 +470,69 @@
"addAccount": "Añadir cuenta", "addAccount": "Añadir cuenta",
"name": "Nombre de empresa", "name": "Nombre de empresa",
"category": "Nivel de cuenta", "category": "Nivel de cuenta",
"accountId": "ID de autor", "accountId": "ID de la cuenta",
"country": "País", "country": "País",
"primaryAdmin": "Administrador primario", "primaryAdmin": "Administrador primario",
"email": "Email", "email": "Email",
"dealerManagement": "Permitir que el distribuidor realice los cambios", "dealerManagement": "Permitir que el concesionario realice los cambios",
"partners": "Socios", "partners": "Socios",
"deleteAccount": "Borrar cuenta" "deleteAccount": "Borrar cuenta"
}, },
"message": { "message": {
"delegateNotAllowedError": "(es)パートナーの代行操作が許可されていません。画面を更新し、再度ご確認ください。", "delegateNotAllowedError": "No se permiten acciones en nombre del socio. Actualice la pantalla y verifique nuevamente.",
"deleteFailedError": "(es)代行操作に失敗しました。画面を更新し、再度ご確認ください。", "deleteFailedError": "La operación del delegado falló. Actualice la pantalla y verifique nuevamente.",
"delegateCancelError": "(es)代行操作の許可が取り消されたため、代行操作を終了しました。" "delegateCancelError": "La operación delegada finalizó porque se revocó el permiso para la operación delegada."
} }
}, },
"accountPage": { "accountPage": {
"label": { "label": {
"title": "(es)Account", "title": "Cuenta",
"fileDeleteSetting": "(es)File Delete Setting", "fileDeleteSetting": "Configuración de eliminación de archivos",
"accountInformation": "(es)Account Information", "accountInformation": "Información de la cuenta",
"companyName": "(es)Company Name", "companyName": "Nombre de empresa",
"accountID": "(es)Account ID", "accountID": "ID de la cuenta",
"yourCategory": "(es)Your Category", "yourCategory": "Tipo de cuenta",
"yourCountry": "(es)Your Country", "yourCountry": "País",
"yourDealer": "(es)Your DealerUpper layer", "yourDealer": "Concesionario",
"selectDealer": "(es)Select Dealer", "selectDealer": "Seleccionar Concesionario",
"dealerManagement": "(es)Dealer Management", "dealerManagement": "Permitir que el concesionario realice los cambios",
"administratorInformation": "(es)Administrator Information", "administratorInformation": "Información del administrador",
"primaryAdministrator": "(es)Primary Administrator", "primaryAdministrator": "Administrador primario",
"secondaryAdministrator": "(es)Secondary Administrator", "secondaryAdministrator": "Administrador secundario",
"emailAddress": "(es)E-mail address", "emailAddress": "Dirección de correo electrónico",
"selectSecondaryAdministrator": "(es)Select Secondary Administrator", "selectSecondaryAdministrator": "Seleccionar administrador secundario",
"saveChanges": "(es)Save Changes", "saveChanges": "Guardar cambios",
"deleteAccount": "(es)Delete Account" "deleteAccount": "Borrar cuenta"
}, },
"message": { "message": {
"updateAccountFailedError": "(es)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください" "updateAccountFailedError": "No se pudo guardar la información de la cuenta. Actualice la pantalla e inténtelo de nuevo."
} }
}, },
"deleteAccountPopup": { "deleteAccountPopup": {
"label": { "label": {
"title": "(es)Delete Account", "title": "Borrar cuenta",
"subTitle": "(es)Delete your account", "subTitle": "¿Eliminar tu cuenta?",
"cautionOfDeleteingAccountData": "(es)Deleting your account will remove all of your audio files and\nlicenses from system. and you'll cannot use ODMS Cloud.\nThis cannot be undone.", "cautionOfDeleteingAccountData": "Al eliminar su cuenta, se eliminará toda la información del usuario, las licencias y los archivos de dictado del sistema. La información eliminada no se puede restaurar.",
"deleteButton": "(es)Delete account", "deleteButton": "Borrar cuenta",
"cancelButton": "(es)Cancel" "cancelButton": "Cancelar"
} }
}, },
"accountDeleteSuccess": { "accountDeleteSuccess": {
"label": { "label": {
"title": "(es)Account Delete Success", "title": "Eliminación exitosa de la cuenta",
"message": "(es)Your account has been deleted. Thank you for using our services.", "message": "Tu cuenta ha sido eliminada. Gracias por utilizar la nube ODMS.",
"backToTopPageLink": "(es)Back to TOP Page" "backToTopPageLink": "Volver a la página superior"
} }
}, },
"termsPage": { "termsPage": {
"label": { "label": {
"title": "(es)Terms of Use has updated. Please confirm again.", "title": "Los Términos de uso han sido actualizados. Se requiere reconfirmación para continuar usando ODMS Cloud.",
"linkOfEula": "(es)Click here to read the terms of use.", "linkOfEula": "Haga clic aquí para leer el Acuerdo de licencia de usuario final.",
"linkOfDpa": "(es)Click here to read the terms of use.", "linkOfDpa": "Haga clic aquí para leer el Acuerdo de procesamiento de datos.",
"linkOfPrivacyNotice": "(es)Click here to read the terms of use.", "checkBoxForConsent": "Sí, acepto los términos de uso.",
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.", "forOdms": "para la nube ODMS.",
"forOdds": "(es)for ODDS.", "button": "Continuar",
"button": "(es)Continue", "linkOfPrivacyNotice": "Haga clic aquí para leer el Aviso de Privacidad."
"linkOfPrivacyNotice": "(es)Click here to read the terms of use."
} }
}, },
"supportPage": { "supportPage": {

View File

@ -2,13 +2,13 @@
"common": { "common": {
"message": { "message": {
"inputEmptyError": "Champ obligatoire", "inputEmptyError": "Champ obligatoire",
"passwordIncorrectError": "(fr)入力されたパスワードがルールを満たしていません。下記のルールを満たすパスワードを入力してください。", "passwordIncorrectError": "Le mot de passe que vous avez entré ne répond pas aux spécifications. Veuillez saisir le mot de passe correct, comme indiqué dans les spécifications ci-dessous.",
"emailIncorrectError": "(fr)メールアドレスの形式が不正です。正しいメールアドレスの形式で入力してください。", "emailIncorrectError": "Le format de l'adresse e-mail n'est pas valide. S'il vous plaît, mettez une adresse email valide.",
"internalServerError": "Le traitement a échoué. Veuillez réessayer plus tard.", "internalServerError": "Le traitement a échoué. Veuillez réessayer plus tard.",
"listEmpty": "Il y a 0 résultats de recherche.", "listEmpty": "Il y a 0 résultats de recherche.",
"dialogConfirm": "Voulez-vous effectuer l'opération?", "dialogConfirm": "Voulez-vous effectuer l'opération?",
"success": "Traité avec succès", "success": "Traité avec succès",
"displayDialog": "(fr)サインアウトしてもよろしいですか?" "displayDialog": "Êtes-vous certain de vouloir vous déconnecter?"
}, },
"label": { "label": {
"cancel": "Annuler", "cancel": "Annuler",
@ -17,22 +17,21 @@
"save": "Sauvegarder", "save": "Sauvegarder",
"delete": "Delete", "delete": "Delete",
"return": "Retour", "return": "Retour",
"operationInsteadOf": "(fr)Operation instead of:", "operationInsteadOf": "Exploiter le Cloud ODMS pour le compte de :",
"headerName": "(fr)ODMS Cloud", "headerAccount": "Compte",
"headerAccount": "(fr)Account", "headerUser": "Utilisateur",
"headerUser": "(fr)User", "headerLicense": "Abonnement",
"headerLicense": "(fr)License", "headerDictations": "Dictées",
"headerDictations": "(fr)Dictations", "headerWorkflow": "Flux de travail",
"headerWorkflow": "(fr)Workflow", "headerPartners": "Partenaires",
"headerPartners": "(fr)Partners",
"headerSupport": "(fr)Support", "headerSupport": "(fr)Support",
"tier1": "(fr)Admin", "tier1": "Admin",
"tier2": "(fr)BC", "tier2": "BC",
"tier3": "(fr)Distributor", "tier3": "Distributeur",
"tier4": "(fr)Dealer", "tier4": "Concessionnaire",
"tier5": "(fr)Customer", "tier5": "Client",
"notSelected": "(fr)None", "notSelected": "Aucune",
"signOutButton": "(fr)Sign out" "signOutButton": "se déconnecter"
} }
}, },
"topPage": { "topPage": {
@ -49,7 +48,7 @@
"signInButton": "S'identifier", "signInButton": "S'identifier",
"newUser": "Nouvel utilisateur", "newUser": "Nouvel utilisateur",
"signUpButton": "Créer un compte", "signUpButton": "Créer un compte",
"logoAlt": "(fr)OM Dictation Management System in the Cloud" "logoAlt": "OM Dictation Management System in the Cloud"
} }
}, },
"signupPage": { "signupPage": {
@ -75,7 +74,7 @@
"email": "Adresse e-mail", "email": "Adresse e-mail",
"password": "Mot de passe", "password": "Mot de passe",
"termsLink": "Cliquez ici pour lire les conditions d'utilisation.", "termsLink": "Cliquez ici pour lire les conditions d'utilisation.",
"termsLinkFor": "(fr)for ODDS.", "termsLinkFor": "pour ODMS Cloud.",
"termsCheckBox": "Oui, j'accepte les conditions d'utilisation.", "termsCheckBox": "Oui, j'accepte les conditions d'utilisation.",
"createAccountButton": "Soumettre" "createAccountButton": "Soumettre"
} }
@ -176,11 +175,12 @@
"freeLicense": "Nombre de licences inutilisées", "freeLicense": "Nombre de licences inutilisées",
"expiringWithin14daysLicense": "Nombre de licences expirant dans les 14 jours", "expiringWithin14daysLicense": "Nombre de licences expirant dans les 14 jours",
"issueRequesting": "Nombre total de licences commandées", "issueRequesting": "Nombre total de licences commandées",
"numberOfRequesting": "Nombre total de commandes",
"shortage": "Pénurie", "shortage": "Pénurie",
"storageSize": "Stockage disponible", "storageSize": "Stockage disponible",
"usedSize": "Stockage utilisé", "usedSize": "Stockage utilisé",
"storageAvailable": "Stockage indisponible (montant dépassée)" "storageAvailable": "Stockage indisponible (montant dépassée)",
"licenseLabel": "(fr)License",
"storageLabel": "(fr)Storage"
} }
}, },
"licenseOrderPage": { "licenseOrderPage": {
@ -252,10 +252,7 @@
"fileBackup": "(fr)File Backup", "fileBackup": "(fr)File Backup",
"downloadForBackup": "(fr)Download for backup", "downloadForBackup": "(fr)Download for backup",
"applications": "(fr)Applications", "applications": "(fr)Applications",
"cancelDictation": "(fr)Cancel Dictation", "cancelDictation": "Annuler la transcription"
"general": "(fr)General",
"job": "(fr)Job",
"close": "(fr)Close"
} }
}, },
"cardLicenseIssuePopupPage": { "cardLicenseIssuePopupPage": {
@ -377,29 +374,29 @@
"workflowPage": { "workflowPage": {
"label": { "label": {
"title": "Flux de travail", "title": "Flux de travail",
"addRoutingRule": "(fr)Add Routing Rule", "addRoutingRule": "Ajouter une règle de routage",
"editRoutingRule": "(fr)Edit Routing Rule", "editRoutingRule": "Modifier la règle de routage",
"templateSetting": "(fr)Template Setting", "templateSetting": "Paramètre de Masque",
"worktypeIdSetting": "(fr)WorktypeID Setting", "worktypeIdSetting": "Paramètre d'ID du type de travail",
"typistGroupSetting": "(fr)Transcriptionist Group Setting", "typistGroupSetting": "Paramètre de groupe de transcriptionniste",
"authorID": "Identifiant Auteur", "authorID": "Identifiant Auteur",
"worktype": "Identifiant du Type de travail", "worktype": "Identifiant du Type de travail",
"worktypeOptional": "(fr)Worktype ID (Optional)", "worktypeOptional": "Identifiant du Type de travail (Facultatif)",
"transcriptionist": "Transcriptionniste", "transcriptionist": "Transcriptionniste",
"template": "(fr)Template", "template": "Masque",
"templateOptional": "(fr)Template (Optional)", "templateOptional": "Masque (Facultatif)",
"editRule": "(fr)Edit Rule", "editRule": "Modifier la règle",
"selected": "Transcriptionniste sélectionné", "selected": "Transcriptionniste sélectionné",
"pool": "Liste de transcriptionniste", "pool": "Liste de transcriptionniste",
"selectAuthor": "(fr)Select Author ID", "selectAuthor": "Sélectionner le Identifiant Auteur",
"selectWorktypeId": "(fr)Select Worktype ID", "selectWorktypeId": "Sélectionner le Identifiant du Type de travail",
"selectTemplate": "(fr)Select Template" "selectTemplate": "Sélectionner le Masque"
}, },
"message": { "message": {
"selectedTypistEmptyError": "(fr)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。", "selectedTypistEmptyError": "Transcriptionist ou Transcriptionist Group na pas été sélectionné. Veuillez en sélectionner un ou plusieurs dans la liste de transcription.",
"workflowConflictError": "(fr)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。", "workflowConflictError": "Une règle de routage a déjà été enregistrée avec la combinaison AuthorID et WorktypeID spécifiée. Veuillez vous inscrire avec une combinaison différente.",
"inputEmptyError": "Champ obligatoire", "inputEmptyError": "Champ obligatoire",
"saveFailedError": "(fr)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください" "saveFailedError": "Échec de l'enregistrement de la règle de routage. Veuillez actualiser l'écran et réessayer."
} }
}, },
"typistGroupSetting": { "typistGroupSetting": {
@ -433,38 +430,38 @@
"addWorktype": "Ajouter type de travail", "addWorktype": "Ajouter type de travail",
"editWorktypeId": "Modifier l'ID du type de travail", "editWorktypeId": "Modifier l'ID du type de travail",
"saveChange": "Sauvegarder les modifications", "saveChange": "Sauvegarder les modifications",
"editOptionItems": "(fr)Option Item", "editOptionItems": "Elément doption",
"itemLabel": "(fr)Item label", "itemLabel": "Étiquettes d'élément",
"defaultValue": "(fr)Default value", "defaultValue": "Valeur par défaut",
"initialValue": "(fr)Initial value", "initialValue": "Valeur initiale",
"default": "(fr)Default", "default": "Défaut",
"blank": "(fr)Blank", "blank": "Vide",
"lastInput": "(fr)Last Input", "lastInput": "Valeur saisie la dernière fois",
"optionItemTerms": "(fr)The Item label and Initial value should be alphanumeric and symbols, but not include: \\ / : * ? “ < > | ." "optionItemTerms": "Étiquettes d'élément et la valeur initiale peuvent contenir des caractères alphanumériques et des symboles. Les symboles suivants ne peuvent pas être utilisés :\\/:*?\"<>|."
}, },
"message": { "message": {
"worktypeIdIncorrectError": "L'ID du type de travail que vous avez saisi ne répond pas aux spécifications. Veuillez saisir le type de travail correct, comme indiqué dans les spécifications ci-dessous.", "worktypeIdIncorrectError": "L'ID du type de travail que vous avez saisi ne répond pas aux spécifications. Veuillez saisir le type de travail correct, comme indiqué dans les spécifications ci-dessous.",
"alreadyWorktypeIdExistError": "Cet ID de type de travail est actuellement enregistré. Veuillez vous inscrire avec un identifiant de type de travail différent.", "alreadyWorktypeIdExistError": "Cet ID de type de travail est actuellement enregistré. Veuillez vous inscrire avec un identifiant de type de travail différent.",
"worktypeIDLimitError": "L'ID du type de travail ne peut pas être ajouté car il a atteint le nombre maximum d'enregistrements.", "worktypeIDLimitError": "L'ID du type de travail ne peut pas être ajouté car il a atteint le nombre maximum d'enregistrements.",
"optionItemInvalidError": "(fr)Default valueがDefaultに設定されている場合、Initial valueは入力が必須です。", "optionItemInvalidError": "Le réglage de la valeur initiale n'est pas terminé. Veuillez définir la valeur initiale ou modifier la valeur par défaut.",
"worktypeIdAlreadyDeletedError": "(fr)WorktypeIDは既に削除されています。画面を更新し、再度ご確認ください", "worktypeIdAlreadyDeletedError": "Identifiant du Type de travail a déjà été supprimé. Veuillez actualiser votre écran et vérifier à nouveau.",
"optionItemSaveFailedError": "(fr)オプションアイテムの保存に失敗しました。画面を更新し、再度実行してください", "optionItemSaveFailedError": "Échec de l'enregistrement de l'élément d'option. Veuillez actualiser l'écran et réessayer.",
"optionItemIncorrectError": "(fr)入力されたItem labelまたはInitial valueがルールを満たしていません。下記のルールを満たす値を入力してください", "optionItemIncorrectError": "Le Elément doption ou la valeur initiale saisi ne respecte pas les règles. Veuillez saisir une valeur qui répond aux règles ci-dessous.",
"updateActiveWorktypeFailedError": "(fr)Active WorktypeIDの保存に失敗しました。画面を更新し、再度実行してください", "updateActiveWorktypeFailedError": "Échec de l'enregistrement de l'Active Identifiant du Type de travail. Veuillez actualiser votre écran et vérifier à nouveau.",
"worktypeInUseError": "(fr)このWorktype IDはルーティングルールで使用されているため削除できません。", "worktypeInUseError": "Cet Identifiant du Type de travail ne peut pas être supprimé car il est actuellement utilisé dans une règle de routage.",
"updateWorktypeFailedError": "(fr)WorktypeIDの保存に失敗しました。画面を更新し、再度ご確認ください" "updateWorktypeFailedError": "Échec de l'enregistrement de Identifiant du Type de travail. Veuillez actualiser l'écran et réessayer."
} }
}, },
"templateFilePage": { "templateFilePage": {
"label": { "label": {
"title": "(fr)Template List", "title": "Liste des modèles",
"addTemplate": "(fr)Add Template", "addTemplate": "Ajouter un modèle",
"fileName": "(fr)Flie Name", "fileName": "Nom de fichier",
"chooseFile": "(fr)Choose File", "chooseFile": "Choisir le fichier",
"notFileChosen": "(fr)- Not file chosen -", "notFileChosen": "- Aucun fichier sélectionné -",
"fileSizeTerms": "(fr)ファイルサイズは5MBまでです。", "fileSizeTerms": "La taille maximale du fichier pouvant être enregistré est de 5 Mo.",
"fileSizeError": "(fr)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。", "fileSizeError": "La taille du fichier sélectionné est trop grande. Veuillez sélectionner un fichier d'une taille maximale de 5 Mo.",
"fileEmptyError": "(fr)ファイル選択は必須です。ファイルを選択してください。" "fileEmptyError": "La sélection de fichiers est requise. Veuillez sélectionner un fichier."
} }
}, },
"partnerPage": { "partnerPage": {
@ -473,70 +470,69 @@
"addAccount": "Ajouter compte", "addAccount": "Ajouter compte",
"name": "Nom de l'entreprise", "name": "Nom de l'entreprise",
"category": "Niveau compte", "category": "Niveau compte",
"accountId": "Identifiant Auteur", "accountId": "identifiant de compte",
"country": "Pays", "country": "Pays",
"primaryAdmin": "Administrateur principal", "primaryAdmin": "Administrateur principal",
"email": "Email", "email": "Email",
"dealerManagement": "Autoriser le revendeur à modifier les paramètres", "dealerManagement": "Autoriser le concessionnaire à modifier les paramètres",
"partners": "Partenaires", "partners": "Partenaires",
"deleteAccount": "Supprimer le compte" "deleteAccount": "Supprimer le compte"
}, },
"message": { "message": {
"delegateNotAllowedError": "(fr)パートナーの代行操作が許可されていません。画面を更新し、再度ご確認ください。", "delegateNotAllowedError": "Les actions au nom du partenaire ne sont pas autorisées. Veuillez actualiser l'écran et vérifier à nouveau.",
"deleteFailedError": "(fr)代行操作に失敗しました。画面を更新し、再度ご確認ください。", "deleteFailedError": "Lopération de délégation a échoué. Veuillez actualiser l'écran et vérifier à nouveau.",
"delegateCancelError": "(fr)代行操作の許可が取り消されたため、代行操作を終了しました。" "delegateCancelError": "L'opération déléguée a été interrompue car l'autorisation pour l'opération déléguée a été révoquée."
} }
}, },
"accountPage": { "accountPage": {
"label": { "label": {
"title": "(fr)Account", "title": "Compte",
"fileDeleteSetting": "(fr)File Delete Setting", "fileDeleteSetting": "Paramètre de suppression de fichier",
"accountInformation": "(fr)Account Information", "accountInformation": "Information sur le compte",
"companyName": "(fr)Company Name", "companyName": "Nom de l'entreprise",
"accountID": "(fr)Account ID", "accountID": "identifiant de compte",
"yourCategory": "(fr)Your Category", "yourCategory": "Type de compte",
"yourCountry": "(fr)Your Country", "yourCountry": "Pays",
"yourDealer": "(fr)Your DealerUpper layer", "yourDealer": "Concessionnaire",
"selectDealer": "(fr)Select Dealer", "selectDealer": "Sélectionner le Concessionnaire",
"dealerManagement": "(fr)Dealer Management", "dealerManagement": "Autoriser le concessionnaire à modifier les paramètres",
"administratorInformation": "(fr)Administrator Information", "administratorInformation": "Informations sur l'administrateur",
"primaryAdministrator": "(fr)Primary Administrator", "primaryAdministrator": "Administrateur principal",
"secondaryAdministrator": "(fr)Secondary Administrator", "secondaryAdministrator": "Administrateur secondaire",
"emailAddress": "(fr)E-mail address", "emailAddress": "Adresse e-mail",
"selectSecondaryAdministrator": "(fr)Select Secondary Administrator", "selectSecondaryAdministrator": "Sélectionner le administrateur secondaire",
"saveChanges": "(fr)Save Changes", "saveChanges": "Sauvegarder les modifications",
"deleteAccount": "(fr)Delete Account" "deleteAccount": "Supprimer le compte"
}, },
"message": { "message": {
"updateAccountFailedError": "(fr)アカウント情報の保存に失敗しました。画面を更新し、再度実行してください" "updateAccountFailedError": "Échec de l'enregistrement des informations du compte. Veuillez actualiser l'écran et réessayer."
} }
}, },
"deleteAccountPopup": { "deleteAccountPopup": {
"label": { "label": {
"title": "(fr)Delete Account", "title": "Supprimer le compte",
"subTitle": "(fr)Delete your account", "subTitle": "Supprimer votre compte?",
"cautionOfDeleteingAccountData": "(fr)Deleting your account will remove all of your audio files and\nlicenses from system. and you'll cannot use ODMS Cloud.\nThis cannot be undone.", "cautionOfDeleteingAccountData": "La suppression de votre compte supprimera toutes les informations utilisateur, licences et fichiers de dictée du système. Les informations supprimées ne peuvent pas être restaurées.",
"deleteButton": "(fr)Delete account", "deleteButton": "Supprimer le compte",
"cancelButton": "(fr)Cancel" "cancelButton": "Annuler"
} }
}, },
"accountDeleteSuccess": { "accountDeleteSuccess": {
"label": { "label": {
"title": "(fr)Account Delete Success", "title": "Succès de la suppression du compte",
"message": "(fr)Your account has been deleted. Thank you for using our services.", "message": "Votre compte a été supprimé. Merci d'utiliser le Cloud ODMS.",
"backToTopPageLink": "(fr)Back to TOP Page" "backToTopPageLink": "Retour à la page supérieure"
} }
}, },
"termsPage": { "termsPage": {
"label": { "label": {
"title": "(fr)Terms of Use has updated. Please confirm again.", "title": "Les conditions d'utilisation ont été mises à jour. Une reconfirmation est requise pour continuer à utiliser le cloud ODMS.",
"linkOfEula": "(fr)Click here to read the terms of use.", "linkOfEula": "Cliquez ici pour lire le contrat de licence utilisateur final.",
"linkOfDpa": "(fr)Click here to read the terms of use.", "linkOfDpa": "Cliquez ici pour lire l'accord de traitement des données.",
"linkOfPrivacyNotice": "(fr)Click here to read the terms of use.", "checkBoxForConsent": "Oui, j'accepte les conditions d'utilisation.",
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.", "forOdms": "pour ODMS Cloud.",
"forOdds": "(fr)for ODDS.", "button": "Continuer",
"button": "(fr)Continue", "linkOfPrivacyNotice": "Cliquez ici pour lire l'avis de confidentialité."
"linkOfPrivacyNotice": "(fr)Click here to read the terms of use."
} }
}, },
"supportPage": { "supportPage": {

View File

@ -1,6 +1,7 @@
import { import {
LICENSE_EXPIRATION_DAYS, LICENSE_EXPIRATION_DAYS,
LICENSE_EXPIRATION_THRESHOLD_DAYS, LICENSE_EXPIRATION_THRESHOLD_DAYS,
LICENSE_EXPIRATION_TIME_WITH_TIMEZONE,
TRIAL_LICENSE_EXPIRATION_DAYS, TRIAL_LICENSE_EXPIRATION_DAYS,
} from "../../constants"; } from "../../constants";
@ -28,6 +29,19 @@ export class DateWithDayEndTime extends Date {
} }
} }
// 翌日の日付を取得する
export class DateWithNextDayEndTime extends Date {
constructor(...args: any[]) {
if (args.length === 0) {
super(); // 引数がない場合、現在の日付で初期化
} else {
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
}
this.setDate(this.getDate() + 1);
this.setHours(23, 59, 59, 999); // 時分秒を"23:59:59.999"に固定
}
}
// ライセンスの算出用に、閾値となる時刻23:59:59.999)の日付を取得する // ライセンスの算出用に、閾値となる時刻23:59:59.999)の日付を取得する
export class ExpirationThresholdDate extends Date { export class ExpirationThresholdDate extends Date {
constructor(...args: any[]) { constructor(...args: any[]) {
@ -49,6 +63,8 @@ export class NewTrialLicenseExpirationDate extends Date {
} else { } else {
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
} }
// タイムゾーンをカバーするために現在時刻に8時間を加算してから、30日後の日付を取得する
this.setHours(this.getHours() + LICENSE_EXPIRATION_TIME_WITH_TIMEZONE);
this.setDate(this.getDate() + TRIAL_LICENSE_EXPIRATION_DAYS); this.setDate(this.getDate() + TRIAL_LICENSE_EXPIRATION_DAYS);
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
this.setMilliseconds(0); this.setMilliseconds(0);
@ -63,6 +79,8 @@ export class NewAllocatedLicenseExpirationDate extends Date {
} else { } else {
super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す super(...(args as [string])); // 引数がある場合、引数をそのままDateクラスのコンストラクタに渡す
} }
// タイムゾーンをカバーするために現在時刻に8時間を加算してから、365日後の日付を取得する
this.setHours(this.getHours() + LICENSE_EXPIRATION_TIME_WITH_TIMEZONE);
this.setDate(this.getDate() + LICENSE_EXPIRATION_DAYS); this.setDate(this.getDate() + LICENSE_EXPIRATION_DAYS);
this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定 this.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
this.setMilliseconds(0); this.setMilliseconds(0);

View File

@ -19,54 +19,54 @@ export const TIERS = {
* East USに保存する国リスト * East USに保存する国リスト
* @const {number} * @const {number}
*/ */
export const BLOB_STORAGE_REGION_US = ['CA', 'KY', 'US']; export const BLOB_STORAGE_REGION_US = ["CA", "KY", "US"];
/** /**
* Australia Eastに保存する国リスト * Australia Eastに保存する国リスト
* @const {number} * @const {number}
*/ */
export const BLOB_STORAGE_REGION_AU = ['AU', 'NZ']; export const BLOB_STORAGE_REGION_AU = ["AU", "NZ"];
/** /**
* North Europeに保存する国リスト * North Europeに保存する国リスト
* @const {number} * @const {number}
*/ */
export const BLOB_STORAGE_REGION_EU = [ export const BLOB_STORAGE_REGION_EU = [
'AT', "AT",
'BE', "BE",
'BG', "BG",
'HR', "HR",
'CY', "CY",
'CZ', "CZ",
'DK', "DK",
'EE', "EE",
'FI', "FI",
'FR', "FR",
'DE', "DE",
'GR', "GR",
'HU', "HU",
'IS', "IS",
'IE', "IE",
'IT', "IT",
'LV', "LV",
'LI', "LI",
'LT', "LT",
'LU', "LU",
'MT', "MT",
'NL', "NL",
'NO', "NO",
'PL', "PL",
'PT', "PT",
'RO', "RO",
'RS', "RS",
'SK', "SK",
'SI', "SI",
'ZA', "ZA",
'ES', "ES",
'SE', "SE",
'CH', "CH",
'TR', "TR",
'GB', "GB",
]; ];
/** /**
@ -74,8 +74,8 @@ export const BLOB_STORAGE_REGION_EU = [
* @const {string[]} * @const {string[]}
*/ */
export const ADMIN_ROLES = { export const ADMIN_ROLES = {
ADMIN: 'admin', ADMIN: "admin",
STANDARD: 'standard', STANDARD: "standard",
} as const; } as const;
/** /**
@ -83,9 +83,9 @@ export const ADMIN_ROLES = {
* @const {string[]} * @const {string[]}
*/ */
export const USER_ROLES = { export const USER_ROLES = {
NONE: 'none', NONE: "none",
AUTHOR: 'author', AUTHOR: "author",
TYPIST: 'typist', TYPIST: "typist",
} as const; } as const;
/** /**
@ -93,9 +93,9 @@ export const USER_ROLES = {
* @const {string[]} * @const {string[]}
*/ */
export const LICENSE_ISSUE_STATUS = { export const LICENSE_ISSUE_STATUS = {
ISSUE_REQUESTING: 'Issue Requesting', ISSUE_REQUESTING: "Issue Requesting",
ISSUED: 'Issued', ISSUED: "Issued",
CANCELED: 'Order Canceled', CANCELED: "Order Canceled",
}; };
/** /**
@ -103,28 +103,28 @@ export const LICENSE_ISSUE_STATUS = {
* @const {string[]} * @const {string[]}
*/ */
export const LICENSE_TYPE = { export const LICENSE_TYPE = {
TRIAL: 'TRIAL', TRIAL: "TRIAL",
NORMAL: 'NORMAL', NORMAL: "NORMAL",
CARD: 'CARD', CARD: "CARD",
} as const; } as const;
/** /**
* *
* @const {string[]} * @const {string[]}
*/ */
export const LICENSE_ALLOCATED_STATUS = { export const LICENSE_ALLOCATED_STATUS = {
UNALLOCATED: 'Unallocated', UNALLOCATED: "Unallocated",
ALLOCATED: 'Allocated', ALLOCATED: "Allocated",
REUSABLE: 'Reusable', REUSABLE: "Reusable",
DELETED: 'Deleted', DELETED: "Deleted",
} as const; } as const;
/** /**
* *
* @const {string[]} * @const {string[]}
*/ */
export const SWITCH_FROM_TYPE = { export const SWITCH_FROM_TYPE = {
NONE: 'NONE', NONE: "NONE",
CARD: 'CARD', CARD: "CARD",
TRIAL: 'TRIAL', TRIAL: "TRIAL",
} as const; } as const;
/** /**
@ -139,6 +139,12 @@ export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
*/ */
export const LICENSE_EXPIRATION_DAYS = 365; export const LICENSE_EXPIRATION_DAYS = 365;
/**
* 8
* @const {number}
*/
export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8;
/** /**
* *
* @const {number} * @const {number}
@ -156,36 +162,36 @@ export const OPTION_ITEM_NUM = 10;
* @const {string[]} * @const {string[]}
*/ */
export const TASK_STATUS = { export const TASK_STATUS = {
UPLOADED: 'Uploaded', UPLOADED: "Uploaded",
PENDING: 'Pending', PENDING: "Pending",
IN_PROGRESS: 'InProgress', IN_PROGRESS: "InProgress",
FINISHED: 'Finished', FINISHED: "Finished",
BACKUP: 'Backup', BACKUP: "Backup",
} as const; } as const;
/** /**
* *
*/ */
export const TASK_LIST_SORTABLE_ATTRIBUTES = [ export const TASK_LIST_SORTABLE_ATTRIBUTES = [
'JOB_NUMBER', "JOB_NUMBER",
'STATUS', "STATUS",
'ENCRYPTION', "ENCRYPTION",
'AUTHOR_ID', "AUTHOR_ID",
'WORK_TYPE', "WORK_TYPE",
'FILE_NAME', "FILE_NAME",
'FILE_LENGTH', "FILE_LENGTH",
'FILE_SIZE', "FILE_SIZE",
'RECORDING_STARTED_DATE', "RECORDING_STARTED_DATE",
'RECORDING_FINISHED_DATE', "RECORDING_FINISHED_DATE",
'UPLOAD_DATE', "UPLOAD_DATE",
'TRANSCRIPTION_STARTED_DATE', "TRANSCRIPTION_STARTED_DATE",
'TRANSCRIPTION_FINISHED_DATE', "TRANSCRIPTION_FINISHED_DATE",
] as const; ] as const;
/** /**
* *
*/ */
export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const; export const SORT_DIRECTIONS = ["ASC", "DESC"] as const;
/** /**
* *
@ -198,18 +204,18 @@ export const TAG_MAX_COUNT = 20;
* *
*/ */
export const PNS = { export const PNS = {
WNS: 'wns', WNS: "wns",
APNS: 'apns', APNS: "apns",
}; };
/** /**
* *
*/ */
export const USER_LICENSE_STATUS = { export const USER_LICENSE_STATUS = {
NORMAL: 'Normal', NORMAL: "Normal",
NO_LICENSE: 'NoLicense', NO_LICENSE: "NoLicense",
ALERT: 'Alert', ALERT: "Alert",
RENEW: 'Renew', RENEW: "Renew",
}; };
/** /**
@ -234,9 +240,9 @@ export const WORKTYPE_MAX_COUNT = 20;
* worktypeのDefault値の取りうる値 * worktypeのDefault値の取りうる値
**/ **/
export const OPTION_ITEM_VALUE_TYPE = { export const OPTION_ITEM_VALUE_TYPE = {
DEFAULT: 'Default', DEFAULT: "Default",
BLANK: 'Blank', BLANK: "Blank",
LAST_INPUT: 'LastInput', LAST_INPUT: "LastInput",
} as const; } as const;
/** /**
@ -244,20 +250,46 @@ export const OPTION_ITEM_VALUE_TYPE = {
* @const {string[]} * @const {string[]}
*/ */
export const ADB2C_SIGN_IN_TYPE = { export const ADB2C_SIGN_IN_TYPE = {
EMAILADDRESS: 'emailAddress', EMAILADDRESS: "emailAddress",
} as const; } as const;
/** /**
* MANUAL_RECOVERY_REQUIRED * MANUAL_RECOVERY_REQUIRED
* @const {string} * @const {string}
*/ */
export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]'; export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]";
/** /**
* *
* @const {string[]} * @const {string[]}
*/ */
export const TERM_TYPE = { export const TERM_TYPE = {
EULA: 'EULA', EULA: "EULA",
DPA: 'DPA', DPA: "DPA",
} as const; } as const;
/**
* HTTPメソッド
* @const {string[]}
*/
export const HTTP_METHODS = {
POST: "POST",
GET: "GET",
DELETE: "DELETE",
HEAD: "HEAD",
PATCH: "PATCH",
PUT: "PUT",
OPTIONS: "OPTIONS",
TRACE: "TRACE",
CONNECT: "CONNECT",
};
/**
* HTTPステータスコード
* @const {string[]}
*/
export const HTTP_STATUS_CODES = {
OK: 200,
BAD_REQUEST: 400,
INTERNAL_SERVER_ERROR: 500,
};

View File

@ -8,6 +8,7 @@ import {
UpdateDateColumn, UpdateDateColumn,
OneToOne, OneToOne,
JoinColumn, JoinColumn,
OneToMany,
} from "typeorm"; } from "typeorm";
@Entity({ name: "accounts" }) @Entity({ name: "accounts" })
@ -73,4 +74,7 @@ export class Account {
@OneToOne(() => User, (user) => user.id) @OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: "secondary_admin_user_id" }) @JoinColumn({ name: "secondary_admin_user_id" })
secondaryAdminUser: User | null; secondaryAdminUser: User | null;
@OneToMany(() => User, (user) => user.id)
user: User[] | null;
} }

View File

@ -6,6 +6,7 @@ import {
UpdateDateColumn, UpdateDateColumn,
JoinColumn, JoinColumn,
OneToOne, OneToOne,
ManyToOne,
} from "typeorm"; } from "typeorm";
import { bigintTransformer } from "../common/entity"; import { bigintTransformer } from "../common/entity";
import { User } from "./user.entity"; import { User } from "./user.entity";
@ -61,3 +62,54 @@ export class License {
@JoinColumn({ name: "allocated_user_id" }) @JoinColumn({ name: "allocated_user_id" })
user: User | null; user: User | null;
} }
@Entity({ name: "license_allocation_history" })
export class LicenseAllocationHistory {
@PrimaryGeneratedColumn()
id: number;
@Column()
user_id: number;
@Column()
license_id: number;
@Column()
is_allocated: boolean;
@Column()
account_id: number;
@Column()
executed_at: Date;
@Column()
switch_from_type: string;
@Column({ nullable: true, type: "datetime" })
deleted_at: Date | null;
@Column({ nullable: true, type: "datetime" })
created_by: string | null;
@CreateDateColumn({
default: () => "datetime('now', 'localtime')",
type: "datetime",
})
created_at: Date;
@Column({ nullable: true, type: "datetime" })
updated_by: string | null;
@UpdateDateColumn({
default: () => "datetime('now', 'localtime')",
type: "datetime",
})
updated_at: Date;
@ManyToOne(() => License, (licenses) => licenses.id, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: "license_id" })
license: License | null;
}

View File

@ -5,8 +5,11 @@ import {
CreateDateColumn, CreateDateColumn,
UpdateDateColumn, UpdateDateColumn,
OneToOne, OneToOne,
JoinColumn,
ManyToOne,
} from "typeorm"; } from "typeorm";
import { License } from "./license.entity"; import { License } from "./license.entity";
import { Account } from "./account.entity";
@Entity({ name: "users" }) @Entity({ name: "users" })
export class User { export class User {
@ -73,6 +76,12 @@ export class User {
}) // defaultはSQLite用設定値.本番用は別途migrationで設定 }) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date; updated_at: Date;
@ManyToOne(() => Account, (account) => account.user, {
createForeignKeyConstraints: false,
}) // createForeignKeyConstraintsはSQLite用設定値.本番用は別途migrationで設定
@JoinColumn({ name: "account_id" })
account: Account | null;
@OneToOne(() => License, (license) => license.user) @OneToOne(() => License, (license) => license.user)
license: License | null; license: License | null;
} }

View File

@ -0,0 +1,387 @@
import { app, InvocationContext, Timer } from "@azure/functions";
import { Between, DataSource, In, MoreThan, Repository } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import * as dotenv from "dotenv";
import {
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
SWITCH_FROM_TYPE,
TIERS,
USER_ROLES,
} from "../constants";
import {
DateWithDayEndTime,
DateWithZeroTime,
NewAllocatedLicenseExpirationDate,
} from "../common/types/types";
export async function licenseAutoAllocationProcessing(
context: InvocationContext,
datasource: DataSource,
dateToTrigger?: Date
): Promise<void> {
try {
context.log("[IN]licenseAutoAllocationProcessing");
// ライセンスの有効期間判定用
let currentDateZeroTime = new DateWithZeroTime();
let currentDateEndTime = new DateWithDayEndTime();
if (dateToTrigger) {
currentDateZeroTime = new DateWithZeroTime(dateToTrigger);
currentDateEndTime = new DateWithDayEndTime(dateToTrigger);
}
// 自動更新対象の候補となるアカウントを取得
const accountRepository = datasource.getRepository(Account);
const targetAccounts = await accountRepository.find({
where: {
tier: TIERS.TIER5,
},
});
// 自動更新対象となるアカウント・ユーザーを取得
const autoAllocationLists = await findTargetUser(
context,
datasource,
targetAccounts,
currentDateZeroTime,
currentDateEndTime
);
// 対象となるアカウント数分ループ
for (const autoAllocationList of autoAllocationLists) {
// ライセンスを割り当てる
await allocateLicense(
context,
datasource,
autoAllocationList,
currentDateZeroTime,
currentDateEndTime
);
}
} catch (e) {
context.log("licenseAutoAllocationProcessing failed.");
context.error(e);
throw e;
} finally {
context.log("[OUT]licenseAutoAllocationProcessing");
}
}
export async function licenseAutoAllocation(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
try {
context.log("[IN]licenseAutoAllocation");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let datasource: DataSource;
try {
datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License, LicenseAllocationHistory],
});
await datasource.initialize();
} catch (e) {
context.log("database initialize failed.");
context.error(e);
throw e;
}
await licenseAutoAllocationProcessing(context, datasource);
} catch (e) {
context.log("licenseAutoAllocation failed.");
context.error(e);
throw e;
} finally {
context.log("[OUT]licenseAutoAllocation");
}
}
/**
*
* @param context
* @param datasource
* @param targetAccounts
* @param currentDateZeroTime
* @param currentDateEndTime
* @returns autoAllocationList[] IDリスト
*/
export async function findTargetUser(
context: InvocationContext,
datasource: DataSource,
targetAccounts: Account[],
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<autoAllocationList[]> {
try {
context.log("[IN]findTargetUser");
const autoAllocationList = [] as autoAllocationList[];
// ライセンス期限が今日で自動更新対象のユーザーを取得
const userRepository = datasource.getRepository(User);
for (const account of targetAccounts) {
// Author→Typist→Noneの優先度で割り当てたいので、roleごとに個別で取得
const targetAuthorUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.AUTHOR,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
const targetTypistUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.TYPIST,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
const targetNoneUsers = await userRepository.find({
where: {
account_id: account.id,
auto_renew: true,
role: USER_ROLES.NONE,
license: {
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
},
relations: {
license: true,
},
});
// Author→Typist→Noneの順で配列に格納
const userIds = [] as number[];
for (const user of targetAuthorUsers) {
userIds.push(Number(user.id));
}
for (const user of targetTypistUsers) {
userIds.push(Number(user.id));
}
for (const user of targetNoneUsers) {
userIds.push(Number(user.id));
}
// 対象ユーザーが0件なら自動更新リストには含めない
if (userIds.length !== 0) {
autoAllocationList.push({
accountId: account.id,
userIds: userIds,
});
}
}
return autoAllocationList;
} catch (e) {
context.error(e);
context.log("findTargetUser failed.");
throw e;
} finally {
context.log("[OUT]findTargetUser");
}
}
/**
*
* @param context
* @param licenseRepository
* @param accountId ID
* @returns License
*/
export async function getAutoAllocatableLicense(
context: InvocationContext,
licenseRepository: Repository<License>,
accountId: number,
currentDateEndTime: DateWithDayEndTime
): Promise<License | undefined> {
try {
context.log("[IN]getAutoAllocatableLicense");
// 割り当て可能なライセンスを取得
const license = await licenseRepository.findOne({
where: {
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime) || null,
},
order: {
expiry_date: "ASC",
},
});
if (!license) {
// 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する
return undefined;
}
return license;
} catch (e) {
context.error(e);
context.log("getAutoAllocatableLicense failed.");
throw e;
} finally {
context.log("[OUT]getAutoAllocatableLicense");
}
}
/**
*
* @param context
* @param datasource
* @param account ID
* @param currentDateZeroTime
* @param currentDateEndTime
*/
export async function allocateLicense(
context: InvocationContext,
datasource: DataSource,
autoAllocationList: autoAllocationList,
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<void> {
try {
context.log("[IN]allocateLicense");
// 自動更新対象ユーザーにライセンスを割り当てる
let hasAllocatebleLicense = true;
for (const userId of autoAllocationList.userIds) {
await datasource.transaction(async (entityManager) => {
const licenseRepository = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory
);
// 割り当て可能なライセンスを取得する(自動割り当て用)
const autoAllocatableLicense = await getAutoAllocatableLicense(
context,
licenseRepository,
autoAllocationList.accountId,
currentDateEndTime
);
// 割り当て可能なライセンスが存在しなければreturnし、その後ループ終了
if (!autoAllocatableLicense) {
context.log(`allocatable license not exist.`);
hasAllocatebleLicense = false;
return;
}
// ライセンスが直前で手動割り当てされていたら、自動割り当てしない
const allocatedLicense = await licenseRepository.findOne({
where: {
allocated_user_id: userId,
expiry_date: Between(currentDateZeroTime, currentDateEndTime),
},
});
if (!allocatedLicense) {
context.log(`skip auto allocation. userID:${userId}`);
return;
}
// 古いライセンスの割り当て解除
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null;
await licenseRepository.save(allocatedLicense);
// ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory();
deallocationHistory.user_id = userId;
deallocationHistory.license_id = allocatedLicense.id;
deallocationHistory.account_id = autoAllocationList.accountId;
deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory);
// 新規ライセンス割り当て
autoAllocatableLicense.status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
autoAllocatableLicense.allocated_user_id = userId;
// 有効期限が未設定なら365日後に設定
if (!autoAllocatableLicense.expiry_date) {
autoAllocatableLicense.expiry_date =
new NewAllocatedLicenseExpirationDate();
}
await licenseRepository.save(autoAllocatableLicense);
context.log(
`license allocated. userID:${userId}, licenseID:${autoAllocatableLicense.id}`
);
// ライセンス割り当て履歴テーブルを更新するための処理
// 直近割り当てたライセンス種別を取得
const oldLicenseType = await licenseAllocationHistoryRepo.findOne({
relations: {
license: true,
},
where: { user_id: userId, is_allocated: true },
order: { executed_at: "DESC" },
});
let switchFromType = "";
if (oldLicenseType && oldLicenseType.license) {
switch (oldLicenseType.license.type) {
case LICENSE_TYPE.CARD:
switchFromType = SWITCH_FROM_TYPE.CARD;
break;
case LICENSE_TYPE.TRIAL:
switchFromType = SWITCH_FROM_TYPE.TRIAL;
break;
default:
switchFromType = SWITCH_FROM_TYPE.NONE;
break;
}
} else {
switchFromType = SWITCH_FROM_TYPE.NONE;
}
// ライセンス割り当て履歴テーブルへ登録
const allocationHistory = new LicenseAllocationHistory();
allocationHistory.user_id = userId;
allocationHistory.license_id = autoAllocatableLicense.id;
allocationHistory.account_id = autoAllocationList.accountId;
allocationHistory.is_allocated = true;
allocationHistory.executed_at = new Date();
// TODO switchFromTypeの値については「PBI1234: 第一階層として、ライセンス数推移情報をCSV出力したい」で正式対応
allocationHistory.switch_from_type = switchFromType;
await licenseAllocationHistoryRepo.save(allocationHistory);
});
// 割り当て可能なライセンスが存在しなければループ終了
if (!hasAllocatebleLicense) {
break;
}
}
} catch (e) {
// エラーが発生しても次のアカウントへの処理は継続させるため、例外をthrowせずにreturnだけする
context.error(e);
context.log("allocateLicense failed.");
return;
} finally {
context.log("[OUT]allocateLicense");
}
}
app.timer("licenseAutoAllocation", {
schedule: "0 0 16 * * *",
handler: licenseAutoAllocation,
});
class autoAllocationList {
accountId: number;
userIds: number[];
}

View File

@ -0,0 +1,91 @@
import {
HttpRequest,
HttpResponseInit,
InvocationContext,
app,
HttpMethod,
} from "@azure/functions";
import { licenseAutoAllocationProcessing } from "./licenseAutoAllocation";
import * as dotenv from "dotenv";
import { DataSource } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants";
export async function licenseAutoAllocationManualRetry(
req: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
context.log(req);
try {
if (req.method === HTTP_METHODS.POST) {
const queryParams = new URLSearchParams(req.url.split("&")[1]); // クエリパラメータを取得
const requestedDate = queryParams.get("date");
context.log("requestedDate: " + requestedDate);
let dateToTrigger: Date;
if (requestedDate) {
// パラメータのチェックを行うYYYY-MM-DD形式
if (!requestedDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
context.log("Invalid date format.");
return {
status: HTTP_STATUS_CODES.BAD_REQUEST,
body: "Invalid date format.",
};
}
dateToTrigger = new Date(requestedDate);
} else {
dateToTrigger = new Date();
}
context.log("[IN]licenseAutoAllocationManualRetry");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let datasource: DataSource;
try {
datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License, LicenseAllocationHistory],
});
await datasource.initialize();
} catch (e) {
context.log("database initialize failed.");
context.error(e);
throw e;
}
await licenseAutoAllocationProcessing(context, datasource, dateToTrigger);
context.log("Automatic license allocation has been triggered.");
return {
status: HTTP_STATUS_CODES.OK,
body: "Automatic license allocation has been triggered.",
};
} else {
context.log("Please use the POST method.");
return {
status: HTTP_STATUS_CODES.BAD_REQUEST,
body: "Please use the POST method.",
};
}
} catch (e) {
context.log("licenseAutoAllocationManualRetry failed.");
context.error(e);
return {
status: HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR,
body: "licenseAutoAllocationManualRetry failed.",
};
} finally {
context.log("[OUT]licenseAutoAllocationManualRetry");
}
}
// httpトリガは定時処理licenseAutoAllocationの異常時の手動再実行用
app.http("licenseAutoAllocationManualRetry", {
methods: [HTTP_METHODS.POST as HttpMethod],
authLevel: "function",
handler: licenseAutoAllocationManualRetry,
});

View File

@ -58,26 +58,6 @@ Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Ma
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten. Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Español>
Estimado(a) ${companyName},
Una o más de sus licencias de ODMS Cloud asignadas caducarán en un plazo de 14 días. No hay una cantidad suficiente de licencias no asignadas en su inventario para emitirlas a usuarios con licencias vencidas.
Recuento de licencias insuficiente: ${shortage}
Solicite licencias anuales adicionales a su ${dealer} para asegurarse de tener suficiente inventario.
Puede asignar licencias a los usuarios de forma automática o manual. A los usuarios con la opción Asignación automática habilitada (predeterminada) se les asignará su licencia automáticamente desde su inventario de licencias en la fecha de vencimiento. Si desactiva la opción Asignar automáticamente, deberá asignar licencias manualmente.
Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.
URL: https://odmscloud.omsystem.com/
Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.
Si recibió este correo electrónico por error, elimínelo de su sistema.
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.
<Français> <Français>
Chère/Cher ${companyName}, Chère/Cher ${companyName},
@ -137,26 +117,6 @@ URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p> Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p>
<h3>&lt;Español&gt;</h3>
<p>Estimado(a) ${companyName},</p>
<p>Una o más de sus licencias de ODMS Cloud asignadas caducarán en un plazo de 14 días. No hay una cantidad suficiente de licencias no asignadas en su inventario para emitirlas a usuarios con licencias vencidas.</p>
<p>Recuento de licencias insuficiente: ${shortage}</p>
<p>Solicite licencias anuales adicionales a su ${dealer} para asegurarse de tener suficiente inventario.</p>
<p>Puede asignar licencias a los usuarios de forma automática o manual. A los usuarios con la opción Asignación automática habilitada (predeterminada) se les asignará su licencia automáticamente desde su inventario de licencias en la fecha de vencimiento. Si desactiva la opción Asignar automáticamente, deberá asignar licencias manualmente.</p>
<p>Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.</p>
<p>Si recibió este correo electrónico por error, elimínelo de su sistema.<br>
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.</p>
<h3>&lt;Français&gt;</h3> <h3>&lt;Français&gt;</h3>
<p>Chère/Cher ${companyName},</p> <p>Chère/Cher ${companyName},</p>

View File

@ -52,23 +52,6 @@ Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Ma
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten. Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Español>
Estimado(a) ${companyName},
Una o más de sus licencias de ODMS Cloud asignadas caducarán hoy.
Número de licencias que vencen: ${ExpiringSoonUserCount}
Si no tiene una cantidad suficiente de licencias, deberá solicitar licencias anuales a su ${dealer} y asignarlas a los usuarios cuyas licencias están por vencer.
Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.
URL: https://odmscloud.omsystem.com/
Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.
Si recibió este correo electrónico por error, elimínelo de su sistema.
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.
<Français> <Français>
Chère/Cher ${companyName}, Chère/Cher ${companyName},
@ -119,23 +102,6 @@ URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p> Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p>
<h3>&lt;Español&gt;</h3>
<p>Estimado(a) ${companyName},</p>
<p>Una o más de sus licencias de ODMS Cloud asignadas caducarán hoy.<br>
Número de licencias que vencen: ${ExpiringSoonUserCount}</p>
<p>Si no tiene una cantidad suficiente de licencias, deberá solicitar licencias anuales a su ${dealer} y asignarlas a los usuarios cuyas licencias están por vencer.</p>
<p>Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.</p>
<p>Si recibió este correo electrónico por error, elimínelo de su sistema.<br>
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.</p>
<h3>&lt;Français&gt;</h3> <h3>&lt;Français&gt;</h3>
<p>Chère/Cher ${companyName},</p> <p>Chère/Cher ${companyName},</p>

View File

@ -3,7 +3,7 @@ import { DataSource } from "typeorm";
import { User } from "../../entity/user.entity"; import { User } from "../../entity/user.entity";
import { Account } from "../../entity/account.entity"; import { Account } from "../../entity/account.entity";
import { ADMIN_ROLES, USER_ROLES } from "../../constants"; import { ADMIN_ROLES, USER_ROLES } from "../../constants";
import { License } from "../../entity/license.entity"; import { License, LicenseAllocationHistory } from "../../entity/license.entity";
type InitialTestDBState = { type InitialTestDBState = {
tier1Accounts: { account: Account; users: User[] }[]; tier1Accounts: { account: Account; users: User[] }[];
@ -196,3 +196,34 @@ export const createLicense = async (
}); });
identifiers.pop() as License; identifiers.pop() as License;
}; };
export const selectLicenseByAllocatedUser = async (
datasource: DataSource,
userId: number
): Promise<{ license: License | null }> => {
const license = await datasource.getRepository(License).findOne({
where: {
allocated_user_id: userId,
},
});
return { license };
};
export const selectLicenseAllocationHistory = async (
datasource: DataSource,
userId: number,
licence_id: number
): Promise<{ licenseAllocationHistory: LicenseAllocationHistory | null }> => {
const licenseAllocationHistory = await datasource
.getRepository(LicenseAllocationHistory)
.findOne({
where: {
user_id: userId,
license_id: licence_id,
},
order: {
executed_at: "DESC",
},
});
return { licenseAllocationHistory };
};

View File

@ -0,0 +1,658 @@
import { DataSource } from "typeorm";
import { licenseAutoAllocationProcessing } from "../functions/licenseAutoAllocation";
import {
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
USER_ROLES,
} from "../constants";
import { DateWithDayEndTime } from "../common/types/types";
import {
makeTestAccount,
createLicense,
makeTestUser,
selectLicenseByAllocatedUser,
selectLicenseAllocationHistory,
} from "./common/utility";
import * as dotenv from "dotenv";
import { InvocationContext } from "@azure/functions";
describe("licenseAutoAllocation", () => {
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: "sqlite",
database: ":memory:",
logging: false,
entities: [__dirname + "/../../**/*.entity{.ts,.js}"],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it("有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
const user4 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 更新対象ではないユーザーauto_renewがfalse
const user5 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
auto_renew: false,
});
// 更新対象のユーザーAuthor二人目
const user6 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
});
// 更新対象のユーザー(ただしライセンスが足りない)
const user7 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
await createLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user2.id,
null,
null,
null
);
await createLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user3.id,
null,
null,
null
);
await createLicense(
source,
20,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await createLicense(
source,
5,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user5.id,
null,
null,
null
);
await createLicense(
source,
6,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user6.id,
null,
null,
null
);
await createLicense(
source,
7,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user7.id,
null,
null,
null
);
// 割り当て済みの更新対象ではないライセンス
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
source,
4,
nextDate,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user4.id,
null,
null,
null
);
// 有効期限が先の未割当ライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 5; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
const date = new Date();
date.setDate(date.getDate() + 30);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
source,
200,
date,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(context, source);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
const user4Allocated = await selectLicenseByAllocatedUser(source, user4.id);
const user5Allocated = await selectLicenseByAllocatedUser(source, user5.id);
const user6Allocated = await selectLicenseByAllocatedUser(source, user6.id);
const user7Allocated = await selectLicenseByAllocatedUser(source, user7.id);
const admin2Allocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
user1.id,
104
);
// Author、Typist、Noneの優先順位で割り当てられていることを確認
expect(user1Allocated.license?.id).toBe(104);
expect(user2Allocated.license?.id).toBe(101);
expect(user3Allocated.license?.id).toBe(103);
// 有効期限がまだあるので、ライセンスが更新されていないことを確認
expect(user4Allocated.license?.id).toBe(4);
// auto_renewがfalseなので、ライセンスが更新されていないことを確認
expect(user5Allocated.license?.id).toBe(5);
// 複数Authorがいる場合、それぞれに割り当てられていることを確認
expect(user6Allocated.license?.id).toBe(102);
// ライセンスが足りない場合、ライセンスが更新されていないことを確認
expect(user7Allocated.license?.id).toBe(7);
// 複数アカウント分の処理が正常に行われていることの確認
expect(admin2Allocated.license?.id).toBe(200);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
user1.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
});
it("有効期限が指定日のライセンスが自動更新されること(リトライ用)", async () => {
if (!source) fail();
const context = new InvocationContext();
// 11/22の日付を作成
const dateSeptember22 = new Date();
dateSeptember22.setMonth(11);
dateSeptember22.setDate(22);
dateSeptember22.setHours(23, 59, 59);
const currentDateEndTime = new DateWithDayEndTime(dateSeptember22);
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
const user4 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 更新対象ではないユーザーauto_renewがfalse
const user5 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
auto_renew: false,
});
// 割り当て済みで有効期限が12/31のライセンス
await createLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
await createLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user2.id,
null,
null,
null
);
await createLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user3.id,
null,
null,
null
);
await createLicense(
source,
20,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await createLicense(
source,
5,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user5.id,
null,
null,
null
);
// 割り当て済みの更新対象ではないライセンス
const nextDate = new Date();
nextDate.setDate(dateSeptember22.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
source,
4,
nextDate,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user4.id,
null,
null,
null
);
// 有効期限が先の未割当ライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 10; i++) {
const date = new Date();
date.setDate(dateSeptember22.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
const dateMarch31 = new Date();
dateMarch31.setMonth(12);
dateMarch31.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
dateMarch31.setMilliseconds(0);
await createLicense(
source,
200,
dateMarch31,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(context, source, dateSeptember22);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
const user4Allocated = await selectLicenseByAllocatedUser(source, user4.id);
const user5Allocated = await selectLicenseByAllocatedUser(source, user5.id);
const admin2Allocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
user1.id,
103
);
// Author、Typist、Noneの優先順位で割り当てられていることを確認
expect(user1Allocated.license?.id).toBe(103);
expect(user2Allocated.license?.id).toBe(101);
expect(user3Allocated.license?.id).toBe(102);
// 有効期限がまだあるので、ライセンスが更新されていないことを確認
expect(user4Allocated.license?.id).toBe(4);
// auto_renewがfalseなので、ライセンスが更新されていないことを確認
expect(user5Allocated.license?.id).toBe(5);
// 複数アカウント分の処理が正常に行われていることの確認
expect(admin2Allocated.license?.id).toBe(200);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
user1.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
});
it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => {
if (!source) fail();
const context = new InvocationContext();
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
await createLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user2.id,
null,
null,
null
);
await createLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user3.id,
null,
null,
null
);
await licenseAutoAllocationProcessing(context, source);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
// ライセンスが更新されていないことを確認
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
});
it("tier4のアカウントのため、ライセンスが自動更新されない", async () => {
if (!source) fail();
const context = new InvocationContext();
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 4 },
{ role: `${USER_ROLES.NONE}` }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
await createLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user2.id,
null,
null,
null
);
await createLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user3.id,
null,
null,
null
);
// 有効期限が先の未割当ライセンスを作成
// idが100101のものは有効期限が当日、翌日なので自動割り当て対象外
// idが102のものから割り当てられる
for (let i = 0; i < 10; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
await licenseAutoAllocationProcessing(context, source);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
// ライセンスが更新されていないことを確認
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
});
});

View File

@ -1,7 +1,7 @@
{ {
"terminal.integrated.shell.linux": "/bin/bash", "terminal.integrated.shell.linux": "/bin/bash",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": "explicit"
}, },
"eslint.format.enable": false, "eslint.format.enable": false,
"[javascript]": { "[javascript]": {

View File

@ -0,0 +1,5 @@
-- +migrate Up
ALTER TABLE `license_orders` ADD INDEX `idx_from_account_id_and_po_number` (from_account_id,po_number);
-- +migrate Down
ALTER TABLE `license_orders` DROP INDEX `idx_from_account_id_and_po_number`;

View File

@ -1,5 +1,9 @@
{ {
"$schema": "https://json.schemastore.org/nest-cli", "$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src" "sourceRoot": "src",
"compilerOptions": {
"assets": ["templates/**/*.html", "templates/**/*.txt"],
"watchAssets": true
}
} }

View File

@ -23,6 +23,8 @@ export const ErrorCodes = [
'E000107', // トークン不足エラー 'E000107', // トークン不足エラー
'E000108', // トークン権限エラー 'E000108', // トークン権限エラー
'E000301', // ADB2Cへのリクエスト上限超過エラー 'E000301', // ADB2Cへのリクエスト上限超過エラー
'E000401', // IPアドレス未設定エラー
'E000501', // リクエストID未設定エラー
'E010001', // パラメータ形式不正エラー 'E010001', // パラメータ形式不正エラー
'E010201', // 未認証ユーザエラー 'E010201', // 未認証ユーザエラー
'E010202', // 認証済ユーザエラー 'E010202', // 認証済ユーザエラー

View File

@ -12,6 +12,8 @@ export const errors: Errors = {
E000107: 'Token is not exist Error.', E000107: 'Token is not exist Error.',
E000108: 'Token authority failed Error.', E000108: 'Token authority failed Error.',
E000301: 'ADB2C request limit exceeded Error', E000301: 'ADB2C request limit exceeded Error',
E000401: 'IP address not found Error.',
E000501: 'Request ID not found Error.',
E010001: 'Param invalid format Error.', E010001: 'Param invalid format Error.',
E010201: 'Email not verified user Error.', E010201: 'Email not verified user Error.',
E010202: 'Email already verified user Error.', E010202: 'Email already verified user Error.',

View File

@ -1,8 +1,32 @@
import { Request } from 'express';
import { Context } from './types'; import { Context } from './types';
export const makeContext = ( export const makeContext = (
externalId: string, externalId: string,
requestId: string,
delegationId?: string, delegationId?: string,
): Context => { ): Context => {
return new Context(externalId, delegationId); return new Context(externalId, requestId, delegationId);
};
// リクエストヘッダーからrequestIdを取得する
export const retrieveRequestId = (req: Request): string | undefined => {
return req.header('x-request-id');
};
/**
* IPアドレスを取得します
* @param {Request}
* @return {string | undefined}
*/
export const retrieveIp = (req: Request): string | undefined => {
// ローカル環境では直近の送信元IPを取得する
if (process.env.STAGE === 'local') {
return req.ip;
}
const ip = req.header('x-forwarded-for');
if (typeof ip === 'string') {
return ip;
}
return undefined;
}; };

View File

@ -1,4 +1,4 @@
import { Context } from './types'; import { Context } from './types';
import { makeContext } from './context'; import { makeContext, retrieveRequestId, retrieveIp } from './context';
export { Context, makeContext }; export { Context, makeContext, retrieveRequestId, retrieveIp };

View File

@ -3,23 +3,32 @@ export class Context {
* APIの操作ユーザーを追跡するためのID * APIの操作ユーザーを追跡するためのID
*/ */
trackingId: string; trackingId: string;
/**
* APIの操作ユーザーのIPアドレス
*/
ip: string;
/**
* ID
*/
requestId: string;
/** /**
* APIの代行操作ユーザーを追跡するためのID * APIの代行操作ユーザーを追跡するためのID
*/ */
delegationId?: string | undefined; delegationId?: string | undefined;
constructor(externalId: string, delegationId?: string) { constructor(externalId: string, requestId: string, delegationId?: string) {
this.trackingId = externalId; this.trackingId = externalId;
this.delegationId = delegationId; this.delegationId = delegationId;
this.requestId = requestId;
} }
/** /**
* *
*/ */
getTrackingId(): string { getTrackingId(): string {
if (this.delegationId) { if (this.delegationId) {
return `${this.trackingId} by ${this.delegationId}`; return `${this.requestId}_${this.trackingId} by ${this.delegationId}`;
} else { } else {
return this.trackingId; return `${this.requestId}_${this.trackingId}`;
} }
} }
} }

View File

@ -1,11 +1,16 @@
import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
@Injectable() @Injectable()
export class LoggerMiddleware implements NestMiddleware { export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger(LoggerMiddleware.name); private readonly logger = new Logger(LoggerMiddleware.name);
use(req: Request, res: Response, next: () => void): void { use(req: Request, res: Response, next: () => void): void {
// ここで一意のリクエストIDを生成して、リクエストヘッダーに設定する
const requestId = uuidv4();
req.headers['x-request-id'] = requestId;
this.logger.log(this.createReqMsg(req)); this.logger.log(this.createReqMsg(req));
res.on('close', () => { res.on('close', () => {
@ -15,13 +20,17 @@ export class LoggerMiddleware implements NestMiddleware {
} }
private createReqMsg(req: Request): string { private createReqMsg(req: Request): string {
const message = `Request [url=${req.url}, method=${req.method}]`; const message = `[${req.header('x-request-id')}] Request [url=${
req.url
}, method=${req.method}]`;
return message; return message;
} }
private createResMsg(res: Response): string { private createResMsg(res: Response): string {
const message = `Response [statusCode=${res.statusCode}, message=${res.statusMessage}]`; const message = `[${res.req.header('x-request-id')}] Response [statusCode=${
res.statusCode
}, message=${res.statusMessage}]`;
return message; return message;
} }

View File

@ -0,0 +1,143 @@
import {
ObjectLiteral,
Repository,
EntityTarget,
UpdateResult,
DeleteResult,
UpdateQueryBuilder,
Brackets,
FindOptionsWhere,
} from 'typeorm';
import { Context } from '../log';
/**
* VS Code上で型解析エラーが発生するためtypeorm内の型定義と同一の型定義をここに記述する
*/
type QueryDeepPartialEntity<T> = _QueryDeepPartialEntity<
ObjectLiteral extends T ? unknown : T
>;
type _QueryDeepPartialEntity<T> = {
[P in keyof T]?:
| (T[P] extends Array<infer U>
? Array<_QueryDeepPartialEntity<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<_QueryDeepPartialEntity<U>>
: _QueryDeepPartialEntity<T[P]>)
| (() => string);
};
interface InsertEntityOptions {
id: number;
}
const insertEntity = async <T extends InsertEntityOptions & ObjectLiteral>(
entity: EntityTarget<T>,
repository: Repository<T>,
value: QueryDeepPartialEntity<T>,
isCommentOut: boolean,
context: Context,
): Promise<T> => {
let query = repository.createQueryBuilder().insert().into(entity);
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
const result = await query.values(value).execute();
// result.identifiers[0].idがnumber型でない場合はエラー
if (typeof result.identifiers[0].id !== 'number') {
throw new Error('Failed to insert entity');
}
const where: FindOptionsWhere<T> = { id: result.identifiers[0].id } as T;
// 結果をもとにセレクトする
const inserted = await repository.findOne({
where,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!inserted) {
throw new Error('Failed to insert entity');
}
return inserted;
};
const insertEntities = async <T extends InsertEntityOptions & ObjectLiteral>(
entity: EntityTarget<T>,
repository: Repository<T>,
values: QueryDeepPartialEntity<T>[],
isCommentOut: boolean,
context: Context,
): Promise<T[]> => {
let query = repository.createQueryBuilder().insert().into(entity);
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
const result = await query.values(values).execute();
// 挿入するレコードが0で、結果も0であれば、からの配列を返す
if (values.length === 0 && result.identifiers.length === 0) {
return [];
}
// 挿入するレコード数と挿入されたレコード数が一致しない場合はエラー
if (result.identifiers.length !== values.length) {
throw new Error('Failed to insert entities');
}
const where: FindOptionsWhere<T>[] = result.identifiers.map((i) => {
// idがnumber型でない場合はエラー
if (typeof i.id !== 'number') {
throw new Error('Failed to insert entities');
}
return { id: i.id } as T;
});
// 結果をもとにセレクトする
const inserted = await repository.find({
where,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!inserted) {
throw new Error('Failed to insert entity');
}
return inserted;
};
const updateEntity = async <T extends ObjectLiteral>(
repository: Repository<T>,
criteria:
| string
| ((qb: UpdateQueryBuilder<T>) => string)
| Brackets
| ObjectLiteral
| ObjectLiteral[],
values: QueryDeepPartialEntity<T>,
isCommentOut: boolean,
context: Context,
): Promise<UpdateResult> => {
let query = repository.createQueryBuilder().update();
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
return await query.set(values).where(criteria).execute();
};
const deleteEntity = async <T extends ObjectLiteral>(
repository: Repository<T>,
criteria: string | Brackets | ObjectLiteral | ObjectLiteral[],
isCommentOut: boolean,
context: Context,
): Promise<DeleteResult> => {
let query = repository.createQueryBuilder().delete();
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
return await query.where(criteria).execute();
};
export { insertEntity, insertEntities, updateEntity, deleteEntity };

View File

@ -34,6 +34,7 @@ export const overrideAdB2cService = <TService>(
context: Context, context: Context,
externalIds: string[], externalIds: string[],
) => Promise<AdB2cUser[]>; ) => Promise<AdB2cUser[]>;
getUser?: (context: Context, externalId: string) => Promise<AdB2cUser>;
}, },
): void => { ): void => {
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得 // テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
@ -62,6 +63,12 @@ export const overrideAdB2cService = <TService>(
writable: true, writable: true,
}); });
} }
if (overrides.getUser) {
Object.defineProperty(obj, obj.getUser.name, {
value: overrides.getUser,
writable: true,
});
}
}; };
/** /**
@ -73,12 +80,6 @@ export const overrideAdB2cService = <TService>(
export const overrideSendgridService = <TService>( export const overrideSendgridService = <TService>(
service: TService, service: TService,
overrides: { overrides: {
createMailContentFromEmailConfirm?: (
context: Context,
accountId: number,
userId: number,
email: string,
) => Promise<{ subject: string; text: string; html: string }>;
createMailContentFromEmailConfirmForNormalUser?: ( createMailContentFromEmailConfirmForNormalUser?: (
accountId: number, accountId: number,
userId: number, userId: number,
@ -113,13 +114,6 @@ export const overrideSendgridService = <TService>(
}); });
} }
if (overrides.createMailContentFromEmailConfirm) {
Object.defineProperty(obj, obj.createMailContentFromEmailConfirm.name, {
value: overrides.createMailContentFromEmailConfirm,
writable: true,
});
}
if (overrides.createMailContentFromEmailConfirmForNormalUser) { if (overrides.createMailContentFromEmailConfirmForNormalUser) {
Object.defineProperty( Object.defineProperty(
obj, obj,

View File

@ -295,3 +295,9 @@ export const TERM_TYPE = {
* @const {string} * @const {string}
*/ */
export const USER_AUDIO_FORMAT = 'DS2(QP)'; export const USER_AUDIO_FORMAT = 'DS2(QP)';
/**
* NODE_ENVの値
* @const {string[]}
*/
export const NODE_ENV_TEST = 'test';

View File

@ -9,6 +9,7 @@ import {
Param, Param,
Query, Query,
HttpException, HttpException,
Logger,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
ApiOperation, ApiOperation,
@ -77,14 +78,14 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { retrieveAuthorizationToken } from '../../common/http/helper'; import { retrieveAuthorizationToken } from '../../common/http/helper';
import { AccessToken } from '../../common/token'; import { AccessToken } from '../../common/token';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { makeContext } from '../../common/log'; import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { v4 as uuidv4 } from 'uuid';
@ApiTags('accounts') @ApiTags('accounts')
@Controller('accounts') @Controller('accounts')
export class AccountsController { export class AccountsController {
private readonly logger = new Logger(AccountsController.name);
constructor( constructor(
private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService, private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService,
private readonly authService: AuthService, private readonly authService: AuthService,
@ -109,6 +110,7 @@ export class AccountsController {
@ApiOperation({ operationId: 'createAccount' }) @ApiOperation({ operationId: 'createAccount' })
async createAccount( async createAccount(
@Body() body: CreateAccountRequest, @Body() body: CreateAccountRequest,
@Req() req: Request,
): Promise<CreateAccountResponse> { ): Promise<CreateAccountResponse> {
const { const {
companyName, companyName,
@ -123,7 +125,24 @@ export class AccountsController {
} = body; } = body;
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.createAccount( await this.accountService.createAccount(
context, context,
@ -178,6 +197,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -186,7 +221,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const response = await this.accountService.getLicenseSummary( const response = await this.accountService.getLicenseSummary(
context, context,
body.accountId, body.accountId,
@ -232,6 +269,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -240,7 +293,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
//アカウントID取得処理 //アカウントID取得処理
const accountInfo = await this.accountService.getAccountInfo( const accountInfo = await this.accountService.getAccountInfo(
context, context,
@ -283,6 +338,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -291,7 +362,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const authors = await this.accountService.getAuthors(context, userId); const authors = await this.accountService.getAuthors(context, userId);
@ -330,6 +403,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -338,7 +427,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const typists = await this.accountService.getTypists(context, userId); const typists = await this.accountService.getTypists(context, userId);
@ -377,6 +468,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -385,7 +492,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const typistGroups = await this.accountService.getTypistGroups( const typistGroups = await this.accountService.getTypistGroups(
context, context,
@ -441,6 +550,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -450,7 +575,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const typistGroup = await this.accountService.getTypistGroup( const typistGroup = await this.accountService.getTypistGroup(
context, context,
@ -506,6 +632,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -514,7 +656,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.createTypistGroup( await this.accountService.createTypistGroup(
context, context,
userId, userId,
@ -572,6 +716,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -581,7 +741,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.updateTypistGroup( await this.accountService.updateTypistGroup(
context, context,
@ -637,6 +798,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -646,7 +823,8 @@ export class AccountsController {
} }
const { userId, tier } = decodedAccessToken as AccessToken; const { userId, tier } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.createPartnerAccount( await this.accountService.createPartnerAccount(
context, context,
@ -699,6 +877,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -708,7 +902,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const getPartnerLicensesResponse = const getPartnerLicensesResponse =
await this.accountService.getPartnerLicenses( await this.accountService.getPartnerLicenses(
@ -759,6 +954,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -768,7 +979,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const getOrderHistoriesResponse = const getOrderHistoriesResponse =
await this.accountService.getOrderHistories( await this.accountService.getOrderHistories(
@ -825,6 +1037,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -834,7 +1062,8 @@ export class AccountsController {
} }
const { userId, tier } = decodedAccessToken as AccessToken; const { userId, tier } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.issueLicense( await this.accountService.issueLicense(
context, context,
orderedAccountId, orderedAccountId,
@ -857,8 +1086,25 @@ export class AccountsController {
type: ErrorResponse, type: ErrorResponse,
}) })
@ApiOperation({ operationId: 'getDealers' }) @ApiOperation({ operationId: 'getDealers' })
async getDealers(): Promise<GetDealersResponse> { async getDealers(@Req() req: Request): Promise<GetDealersResponse> {
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
return await this.accountService.getDealers(context); return await this.accountService.getDealers(context);
} }
@ -907,6 +1153,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -916,7 +1178,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.cancelIssue( await this.accountService.cancelIssue(
context, context,
@ -957,6 +1220,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -966,7 +1245,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const worktypes = await this.accountService.getWorktypes(context, userId); const worktypes = await this.accountService.getWorktypes(context, userId);
return worktypes; return worktypes;
@ -1012,6 +1292,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1021,7 +1317,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.createWorktype( await this.accountService.createWorktype(
context, context,
userId, userId,
@ -1074,6 +1371,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1083,7 +1396,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.updateWorktype( await this.accountService.updateWorktype(
context, context,
@ -1136,6 +1450,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1145,7 +1475,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.deleteWorktype(context, userId, id); await this.accountService.deleteWorktype(context, userId, id);
return {}; return {};
@ -1191,6 +1522,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1200,7 +1547,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const optionItems = await this.accountService.getOptionItems( const optionItems = await this.accountService.getOptionItems(
context, context,
@ -1253,6 +1601,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1262,7 +1626,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.updateOptionItems( await this.accountService.updateOptionItems(
context, context,
@ -1314,6 +1679,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1323,7 +1704,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.updateActiveWorktype(context, userId, id); await this.accountService.updateActiveWorktype(context, userId, id);
return {}; return {};
@ -1372,6 +1754,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1381,7 +1779,8 @@ export class AccountsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const response = await this.accountService.getPartners( const response = await this.accountService.getPartners(
context, context,
userId, userId,
@ -1439,6 +1838,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1447,7 +1862,9 @@ export class AccountsController {
); );
} }
const { userId, tier } = decodedAccessToken as AccessToken; const { userId, tier } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.updateAccountInfo( await this.accountService.updateAccountInfo(
context, context,
@ -1499,6 +1916,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1507,7 +1940,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.accountService.deleteAccountAndData(context, userId, accountId); await this.accountService.deleteAccountAndData(context, userId, accountId);
return {}; return {};
@ -1532,8 +1967,25 @@ export class AccountsController {
@ApiOperation({ operationId: 'getAccountInfoMinimalAccess' }) @ApiOperation({ operationId: 'getAccountInfoMinimalAccess' })
async getAccountInfoMinimalAccess( async getAccountInfoMinimalAccess(
@Body() body: GetAccountInfoMinimalAccessRequest, @Body() body: GetAccountInfoMinimalAccessRequest,
@Req() req: Request,
): Promise<GetAccountInfoMinimalAccessResponse> { ): Promise<GetAccountInfoMinimalAccessResponse> {
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
// IDトークンの検証 // IDトークンの検証
const idToken = await this.authService.getVerifiedIdToken( const idToken = await this.authService.getVerifiedIdToken(
@ -1591,6 +2043,22 @@ export class AccountsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -1599,7 +2067,9 @@ export class AccountsController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const companyName = await this.accountService.getCompanyName( const companyName = await this.accountService.getCompanyName(
context, context,
body.accountId, body.accountId,

View File

@ -13,9 +13,9 @@ import { User } from '../../repositories/users/entity/user.entity';
import { import {
TIERS, TIERS,
USER_ROLES, USER_ROLES,
ADB2C_SIGN_IN_TYPE,
OPTION_ITEM_VALUE_TYPE, OPTION_ITEM_VALUE_TYPE,
MANUAL_RECOVERY_REQUIRED, MANUAL_RECOVERY_REQUIRED,
LICENSE_ISSUE_STATUS,
} from '../../constants'; } from '../../constants';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { import {
@ -71,6 +71,7 @@ import {
WorktypeIdMaxCountError, WorktypeIdMaxCountError,
WorktypeIdNotFoundError, WorktypeIdNotFoundError,
} from '../../repositories/worktypes/errors/types'; } from '../../repositories/worktypes/errors/types';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
@Injectable() @Injectable()
export class AccountsService { export class AccountsService {
@ -111,6 +112,7 @@ export class AccountsService {
const { licenseSummary, isStorageAvailable } = const { licenseSummary, isStorageAvailable } =
await this.accountRepository.getLicenseSummaryInfo( await this.accountRepository.getLicenseSummaryInfo(
context,
accountId, accountId,
currentDate, currentDate,
expiringSoonDate, expiringSoonDate,
@ -228,6 +230,7 @@ export class AccountsService {
// アカウントと管理者をセットで作成 // アカウントと管理者をセットで作成
const { newAccount, adminUser } = const { newAccount, adminUser } =
await this.accountRepository.createAccount( await this.accountRepository.createAccount(
context,
companyName, companyName,
country, country,
dealerAccountId, dealerAccountId,
@ -284,23 +287,11 @@ export class AccountsService {
} }
try { try {
// メールの内容を構成 await this.sendgridService.sendMailWithU102(
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirm(
context, context,
email,
account.id, account.id,
user.id, user.id,
email,
);
// メールを送信
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
); );
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -376,7 +367,7 @@ export class AccountsService {
} | params: { accountId: ${accountId}, userId: ${userId} };`, } | params: { accountId: ${accountId}, userId: ${userId} };`,
); );
try { try {
await this.accountRepository.deleteAccount(accountId, userId); await this.accountRepository.deleteAccount(context, accountId, userId);
this.logger.log( this.logger.log(
`[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`, `[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`,
); );
@ -439,17 +430,20 @@ export class AccountsService {
} | params: { ` + `externalId: ${externalId}, };`, } | params: { ` + `externalId: ${externalId}, };`,
); );
try { try {
let userInfo: User; const userInfo = await this.usersRepository.findUserByExternalId(
userInfo = await this.usersRepository.findUserByExternalId(externalId); context,
externalId,
);
let accountInfo: Account; const accountInfo = await this.accountRepository.findAccountById(
accountInfo = await this.accountRepository.findAccountById( context,
userInfo.account_id, userInfo.account_id,
); );
let parentInfo: Account | undefined; let parentInfo: Account | undefined;
if (accountInfo.parent_account_id) { if (accountInfo.parent_account_id) {
parentInfo = await this.accountRepository.findAccountById( parentInfo = await this.accountRepository.findAccountById(
context,
accountInfo.parent_account_id, accountInfo.parent_account_id,
); );
} }
@ -505,8 +499,12 @@ export class AccountsService {
// TypistGroup取得 // TypistGroup取得
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); const user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
const userGroups = await this.userGroupsRepository.getUserGroups( const userGroups = await this.userGroupsRepository.getUserGroups(
context,
user.account_id, user.account_id,
); );
@ -543,10 +541,12 @@ export class AccountsService {
try { try {
const { account_id } = await this.usersRepository.findUserByExternalId( const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
const { name, userGroupMembers } = const { name, userGroupMembers } =
await this.userGroupsRepository.getTypistGroup( await this.userGroupsRepository.getTypistGroup(
context,
account_id, account_id,
typistGroupId, typistGroupId,
); );
@ -602,16 +602,13 @@ export class AccountsService {
// Typist取得 // Typist取得
try { try {
const typistUsers = await this.usersRepository.findTypistUsers( const typistUsers = await this.usersRepository.findTypistUsers(
context,
externalId, externalId,
); );
const externalIds = typistUsers.map((x) => x.external_id); const externalIds = typistUsers.map((x) => x.external_id);
// B2Cからユーザー名を取得する // B2Cからユーザー名を取得する
const trackingId = new Context(context.trackingId); const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
const adb2cUsers = await this.adB2cService.getUsers(
trackingId,
externalIds,
);
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);
@ -655,6 +652,7 @@ export class AccountsService {
try { try {
const { account } = await this.usersRepository.findUserByExternalId( const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
@ -665,6 +663,7 @@ export class AccountsService {
} }
const authorUsers = await this.usersRepository.findAuthorUsers( const authorUsers = await this.usersRepository.findAuthorUsers(
context,
account.id, account.id,
); );
@ -737,7 +736,10 @@ export class AccountsService {
try { try {
// アクセストークンからユーザーIDを取得する // アクセストークンからユーザーIDを取得する
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(creatorUserId) await this.usersRepository.findUserByExternalId(
context,
creatorUserId,
)
).account_id; ).account_id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -787,6 +789,7 @@ export class AccountsService {
// アカウントと管理者をセットで作成 // アカウントと管理者をセットで作成
const { newAccount, adminUser } = const { newAccount, adminUser } =
await this.accountRepository.createAccount( await this.accountRepository.createAccount(
context,
companyName, companyName,
country, country,
myAccountId, myAccountId,
@ -848,7 +851,8 @@ export class AccountsService {
); );
await this.sendgridService.sendMail( await this.sendgridService.sendMail(
context, context,
email, [email],
[],
this.mailFrom, this.mailFrom,
subject, subject,
text, text,
@ -913,6 +917,7 @@ export class AccountsService {
const getPartnerLicenseResult = const getPartnerLicenseResult =
await this.accountRepository.getPartnerLicense( await this.accountRepository.getPartnerLicense(
context,
accountId, accountId,
currentDate, currentDate,
expiringSoonDate, expiringSoonDate,
@ -941,7 +946,7 @@ export class AccountsService {
for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) { for (const childPartnerLicenseFromRepository of getPartnerLicenseResult.childPartnerLicensesFromRepository) {
const { allocatableLicenseWithMargin, expiringSoonLicense } = const { allocatableLicenseWithMargin, expiringSoonLicense } =
childPartnerLicenseFromRepository; childPartnerLicenseFromRepository;
let childShortage: number = 0; let childShortage = 0;
if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) { if (childPartnerLicenseFromRepository.tier === TIERS.TIER5) {
if ( if (
allocatableLicenseWithMargin === undefined || allocatableLicenseWithMargin === undefined ||
@ -1012,6 +1017,7 @@ export class AccountsService {
try { try {
const licenseHistoryInfo = const licenseHistoryInfo =
await this.licensesRepository.getLicenseOrderHistoryInfo( await this.licensesRepository.getLicenseOrderHistoryInfo(
context,
accountId, accountId,
offset, offset,
limit, limit,
@ -1023,16 +1029,10 @@ export class AccountsService {
const returnLicenseOrder: LicenseOrder = { const returnLicenseOrder: LicenseOrder = {
issueDate: issueDate:
licenseOrder.issued_at !== null licenseOrder.issued_at !== null
? new Date(licenseOrder.issued_at) ? new Date(licenseOrder.issued_at).toISOString()
.toISOString()
.substring(0, 10)
.replace(/-/g, '/')
: undefined, : undefined,
numberOfOrder: licenseOrder.quantity, numberOfOrder: licenseOrder.quantity,
orderDate: new Date(licenseOrder.ordered_at) orderDate: new Date(licenseOrder.ordered_at).toISOString(),
.toISOString()
.substring(0, 10)
.replace(/-/g, '/'),
poNumber: licenseOrder.po_number, poNumber: licenseOrder.po_number,
status: licenseOrder.status, status: licenseOrder.status,
}; };
@ -1083,14 +1083,57 @@ export class AccountsService {
try { try {
// アクセストークンからユーザーIDを取得する // アクセストークンからユーザーIDを取得する
const myAccountId = ( const myAccountId = (
await this.usersRepository.findUserByExternalId(userId) await this.usersRepository.findUserByExternalId(context, userId)
).account_id; ).account_id;
await this.licensesRepository.issueLicense( const { issuedOrderId } = await this.licensesRepository.issueLicense(
context,
orderedAccountId, orderedAccountId,
myAccountId, myAccountId,
tier, tier,
poNumber, poNumber,
); );
try {
// 発行済みの注文をID指定して取得する
const orderLicense = await this.licensesRepository.getLicenseOrder(
context,
orderedAccountId,
poNumber,
issuedOrderId,
);
if (orderLicense == null) {
throw new Error(
`issue target order not found. fromAccountId: ${orderedAccountId}, poNumber:${poNumber}`,
);
}
// 未発行の注文の場合、エラー
if (orderLicense.status !== LICENSE_ISSUE_STATUS.ISSUED) {
throw new AlreadyIssuedError(
`An order for PONumber:${poNumber} has not been issued.`,
);
}
// 注文したアカウントと自分のアカウントの情報を取得
const customer = await this.getAccountInformation(
context,
orderedAccountId,
);
const dealer = await this.getAccountInformation(context, myAccountId);
await this.sendgridService.sendMailWithU107(
context,
customer.adminEmails,
customer.companyName,
orderLicense.quantity,
poNumber,
dealer.adminEmails,
dealer.companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -1131,7 +1174,9 @@ export class AccountsService {
); );
try { try {
const dealerAccounts = await this.accountRepository.findDealerAccounts(); const dealerAccounts = await this.accountRepository.findDealerAccounts(
context,
);
const dealers: GetDealersResponse = { const dealers: GetDealersResponse = {
dealers: dealerAccounts.map((dealerAccount): Dealer => { dealers: dealerAccounts.map((dealerAccount): Dealer => {
@ -1181,10 +1226,12 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id } = await this.usersRepository.findUserByExternalId( const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
// API実行ユーザーのアカウントIDでタイピストグループを作成し、タイピストグループとtypistIdsのユーザーを紐付ける // API実行ユーザーのアカウントIDでタイピストグループを作成し、タイピストグループとtypistIdsのユーザーを紐付ける
await this.userGroupsRepository.createTypistGroup( await this.userGroupsRepository.createTypistGroup(
context,
typistGroupName, typistGroupName,
typistIds, typistIds,
account_id, account_id,
@ -1244,11 +1291,13 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id } = await this.usersRepository.findUserByExternalId( const { account_id } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
// タイピストグループと所属するタイピストを更新する // タイピストグループと所属するタイピストを更新する
await this.userGroupsRepository.updateTypistGroup( await this.userGroupsRepository.updateTypistGroup(
context,
account_id, account_id,
typistGroupId, typistGroupId,
typistGroupName, typistGroupName,
@ -1314,7 +1363,7 @@ export class AccountsService {
try { try {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(extarnalId) await this.usersRepository.findUserByExternalId(context, extarnalId)
).account_id; ).account_id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -1333,6 +1382,7 @@ export class AccountsService {
// 注文元アカウントIDの親世代を取得 // 注文元アカウントIDの親世代を取得
const parentAccountIds = await this.accountRepository.getHierarchyParents( const parentAccountIds = await this.accountRepository.getHierarchyParents(
context,
orderedAccountId, orderedAccountId,
); );
// 自身が存在しない場合、エラー // 自身が存在しない場合、エラー
@ -1348,7 +1398,42 @@ export class AccountsService {
try { try {
// 発行キャンセル処理 // 発行キャンセル処理
await this.accountRepository.cancelIssue(orderedAccountId, poNumber); const { canceledIssueLicenseOrderId } =
await this.accountRepository.cancelIssue(
context,
orderedAccountId,
poNumber,
);
try {
// 発行キャンセルされ、発行済状態から注文中状態に戻った注文を取得する
const order = await this.licensesRepository.getLicenseOrder(
context,
orderedAccountId,
poNumber,
canceledIssueLicenseOrderId,
);
if (order == null) {
throw new Error('order not found.');
}
const { quantity, from_account_id, to_account_id } = order;
const customer = await this.getAccountInformation(
context,
from_account_id,
);
const dealer = await this.getAccountInformation(context, to_account_id);
await this.sendgridService.sendMailWithU109(
context,
dealer.adminEmails,
dealer.companyName,
quantity,
poNumber,
customer.adminEmails,
customer.companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信の例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) { switch (e.constructor) {
@ -1398,11 +1483,11 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// ワークタイプ一覧とActiveWorktypeIDを取得する // ワークタイプ一覧とActiveWorktypeIDを取得する
const { worktypes, active_worktype_id } = const { worktypes, active_worktype_id } =
await this.worktypesRepository.getWorktypes(accountId); await this.worktypesRepository.getWorktypes(context, accountId);
return { return {
worktypes: worktypes.map((x) => ({ worktypes: worktypes.map((x) => ({
@ -1461,9 +1546,10 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
await this.worktypesRepository.createWorktype( await this.worktypesRepository.createWorktype(
context,
accountId, accountId,
worktypeId, worktypeId,
description, description,
@ -1531,10 +1617,11 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// ワークタイプを更新する // ワークタイプを更新する
await this.worktypesRepository.updateWorktype( await this.worktypesRepository.updateWorktype(
context,
accountId, accountId,
id, id,
worktypeId, worktypeId,
@ -1597,7 +1684,7 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account, account_id: accountId } = const { account, account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
if (!account) { if (!account) {
throw new AccountNotFoundError( throw new AccountNotFoundError(
@ -1606,7 +1693,7 @@ export class AccountsService {
} }
// ワークタイプを削除する // ワークタイプを削除する
await this.worktypesRepository.deleteWorktype(accountId, id); await this.worktypesRepository.deleteWorktype(context, accountId, id);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -1673,10 +1760,11 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// オプションアイテム一覧を取得する // オプションアイテム一覧を取得する
const optionItems = await this.worktypesRepository.getOptionItems( const optionItems = await this.worktypesRepository.getOptionItems(
context,
accountId, accountId,
id, id,
); );
@ -1742,10 +1830,11 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// オプションアイテムを更新する // オプションアイテムを更新する
await this.worktypesRepository.updateOptionItems( await this.worktypesRepository.updateOptionItems(
context,
accountId, accountId,
id, id,
// initialValueはdefaultValueTypeがDEFAULTの場合以外は空文字を設定する // initialValueはdefaultValueTypeがDEFAULTの場合以外は空文字を設定する
@ -1808,10 +1897,14 @@ export class AccountsService {
try { try {
// 外部IDをもとにユーザー情報を取得する // 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// ActiveWorktypeを更新する // ActiveWorktypeを更新する
await this.accountRepository.updateActiveWorktypeId(accountId, id); await this.accountRepository.updateActiveWorktypeId(
context,
accountId,
id,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -1865,9 +1958,10 @@ export class AccountsService {
try { try {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
const partnersRecords = await this.accountRepository.getPartners( const partnersRecords = await this.accountRepository.getPartners(
context,
accountId, accountId,
limit, limit,
offset, offset,
@ -1891,10 +1985,8 @@ export class AccountsService {
); );
} }
const primaryAdmin = adb2cUser.displayName; const { displayName: primaryAdmin, emailAddress: mail } =
const mail = adb2cUser.identities?.find( getUserNameAndMailAddress(adb2cUser);
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
)?.issuerAssignedId;
if (!mail) { if (!mail) {
throw new Error( throw new Error(
`adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`, `adb2c user mail not found. externalId: ${db.primaryAccountExternalId}`,
@ -1962,9 +2054,10 @@ export class AccountsService {
); );
try { try {
const { account_id: accountId } = const { account_id: accountId, account } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
await this.accountRepository.updateAccountInfo( await this.accountRepository.updateAccountInfo(
context,
accountId, accountId,
tier, tier,
delegationPermission, delegationPermission,
@ -1972,6 +2065,52 @@ export class AccountsService {
parentAccountId, parentAccountId,
secondryAdminUserId, secondryAdminUserId,
); );
// メール送信処理
try {
if (account === null) {
throw new Error(`account not found. accountId: ${accountId}`);
}
let dealerName: string | null = null;
if (parentAccountId !== undefined) {
const dealer = await this.accountRepository.findAccountById(
context,
parentAccountId,
);
dealerName = dealer.company_name;
}
const { external_id: externalId } =
await this.usersRepository.findUserById(context, primaryAdminUserId);
const primaryAdmin = await this.adB2cService.getUser(
context,
externalId,
);
const {
displayName: primaryAdminName,
emailAddress: primaryAdminEmail,
} = getUserNameAndMailAddress(primaryAdmin);
if (!primaryAdminEmail) {
throw new Error(
`adb2c user mail not found. externalId: ${externalId}`,
);
}
await this.sendgridService.sendMailWithU112(
context,
primaryAdminName,
primaryAdminEmail,
account.company_name,
dealerName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -2020,10 +2159,15 @@ export class AccountsService {
); );
let country: string; let country: string;
let dbUsers: User[]; let dbUsers: User[];
// メール送信に必要な情報
let companyName: string | null = null;
let primaryAdminName: string | null = null;
let primaryAdminEmail: string | null = null;
try { try {
// パラメータとトークンから取得したアカウントIDの突き合わせ // パラメータとトークンから取得したアカウントIDの突き合わせ
const { account_id: myAccountId } = const { account_id: myAccountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
if (myAccountId !== accountId) { if (myAccountId !== accountId) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000108'), makeErrorResponse('E000108'),
@ -2033,10 +2177,41 @@ export class AccountsService {
// アカウント削除前に必要な情報を退避する // アカウント削除前に必要な情報を退避する
const targetAccount = await this.accountRepository.findAccountById( const targetAccount = await this.accountRepository.findAccountById(
context,
accountId, accountId,
); );
// メール送信に必要な情報を取得する
try {
companyName = targetAccount.company_name;
if (!targetAccount.primary_admin_user_id) {
throw new Error(
`primary_admin_user_id not found. accountId: ${accountId}`,
);
}
const primaryAdmin = await this.usersRepository.findUserById(
context,
targetAccount.primary_admin_user_id,
);
const adb2cAdmin = await this.adB2cService.getUser(
context,
primaryAdmin.external_id,
);
const { displayName, emailAddress } =
getUserNameAndMailAddress(adb2cAdmin);
primaryAdminName = displayName;
primaryAdminEmail = emailAddress ?? null;
} catch (e) {
// メール送信に関する例外はログだけ出して握りつぶす
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
// 削除対象アカウントを削除する // 削除対象アカウントを削除する
dbUsers = await this.accountRepository.deleteAccountAndInsertArchives( dbUsers = await this.accountRepository.deleteAccountAndInsertArchives(
context,
accountId, accountId,
); );
this.logger.log( this.logger.log(
@ -2090,6 +2265,29 @@ export class AccountsService {
); );
} }
// メール送信処理
try {
if (companyName === null) {
throw new Error('companyName is null');
}
if (primaryAdminName === null) {
throw new Error('primaryAdminName is null');
}
if (primaryAdminEmail === null) {
throw new Error('primaryAdminEmail is null');
}
await this.sendgridService.sendMailWithU111(
context,
primaryAdminName,
primaryAdminEmail,
companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
this.logger.log( this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`, `[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`,
); );
@ -2113,6 +2311,7 @@ export class AccountsService {
try { try {
const { account } = await this.usersRepository.findUserByExternalId( const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
if (!account) { if (!account) {
@ -2172,6 +2371,7 @@ export class AccountsService {
try { try {
const { company_name } = await this.accountRepository.findAccountById( const { company_name } = await this.accountRepository.findAccountById(
context,
accountId, accountId,
); );
@ -2202,4 +2402,51 @@ export class AccountsService {
); );
} }
} }
/**
* IDを指定して
* @param context
* @param accountId ID
* @returns /
*/
private async getAccountInformation(
context: Context,
accountId: number,
): Promise<{
companyName: string;
adminEmails: string[];
}> {
// アカウントIDから企業名を取得する
const { company_name } = await this.accountRepository.findAccountById(
context,
accountId,
);
// 管理者一覧を取得
const admins = await this.usersRepository.findAdminUsers(
context,
accountId,
);
const adminExternalIDs = admins.map((x) => x.external_id);
// ADB2Cから管理者IDを元にメールアドレスを取得する
const usersInfo = await this.adB2cService.getUsers(
context,
adminExternalIDs,
);
// 生のAzure AD B2Cのユーザー情報からメールアドレスを抽出する
const adminEmails = usersInfo.map((x) => {
const { emailAddress } = getUserNameAndMailAddress(x);
if (emailAddress == null) {
throw new Error(`admin email-address is not found. id=${x.id}`);
}
return emailAddress;
});
return {
companyName: company_name,
adminEmails: adminEmails,
};
}
} }

View File

@ -44,11 +44,6 @@ export type AdB2cMockValue = {
getUsers: AdB2cUser[] | Error; getUsers: AdB2cUser[] | Error;
}; };
export type SendGridMockValue = { export type SendGridMockValue = {
createMailContentFromEmailConfirm: {
subject: string;
text: string;
html: string;
};
createMailContentFromEmailConfirmForNormalUser: { createMailContentFromEmailConfirmForNormalUser: {
subject: string; subject: string;
text: string; text: string;
@ -242,20 +237,8 @@ export const makeConfigMock = (value: ConfigMockValue) => {
}; };
}; };
export const makeSendGridServiceMock = (value: SendGridMockValue) => { export const makeSendGridServiceMock = (value: SendGridMockValue) => {
const { const { createMailContentFromEmailConfirmForNormalUser, sendMail } = value;
createMailContentFromEmailConfirm,
createMailContentFromEmailConfirmForNormalUser,
sendMail,
} = value;
return { return {
createMailContentFromEmailConfirm:
createMailContentFromEmailConfirm instanceof Error
? jest
.fn<Promise<void>, []>()
.mockRejectedValue(createMailContentFromEmailConfirm)
: jest
.fn<Promise<{ subject: string; text: string; html: string }>, []>()
.mockResolvedValue(createMailContentFromEmailConfirm),
createMailContentFromEmailConfirmForNormalUser: createMailContentFromEmailConfirmForNormalUser:
createMailContentFromEmailConfirmForNormalUser instanceof Error createMailContentFromEmailConfirmForNormalUser instanceof Error
? jest ? jest
@ -475,7 +458,6 @@ export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => {
export const makeDefaultSendGridlValue = (): SendGridMockValue => { export const makeDefaultSendGridlValue = (): SendGridMockValue => {
return { return {
sendMail: undefined, sendMail: undefined,
createMailContentFromEmailConfirm: { subject: '', text: '', html: '' },
createMailContentFromEmailConfirmForNormalUser: { createMailContentFromEmailConfirmForNormalUser: {
subject: 'Verify your new account', subject: 'Verify your new account',
text: `The verification URL.`, text: `The verification URL.`,

View File

@ -56,13 +56,14 @@ export const createLicenseSetExpiryDateAndStatus = async (
accountId: number, accountId: number,
expiryDate: Date | null, expiryDate: Date | null,
status: string, status: string,
allocated_user_id?: number | null,
): Promise<void> => { ): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({ const { identifiers } = await datasource.getRepository(License).insert({
expiry_date: expiryDate, expiry_date: expiryDate,
account_id: accountId, account_id: accountId,
type: 'NORMAL', type: 'NORMAL',
status: status, status: status,
allocated_user_id: null, allocated_user_id: allocated_user_id,
order_id: null, order_id: null,
deleted_at: null, deleted_at: null,
delete_order_id: null, delete_order_id: null,

View File

@ -24,51 +24,336 @@ import { OPTION_ITEM_VALUE_TYPE } from '../../../constants';
export class CreateAccountRequest { export class CreateAccountRequest {
@ApiProperty() @ApiProperty()
@MaxLength(255)
companyName: string;
@ApiProperty({
description: '国名(ISO 3166-1 alpha-2)',
minLength: 2,
maxLength: 2,
})
@MinLength(2)
@MaxLength(2)
country: string;
@ApiProperty({ required: false })
@Type(() => Number)
@IsInt()
@IsOptional()
dealerAccountId?: number;
@ApiProperty()
@MaxLength(256) // AzureAdB2Cの仕様上、256文字まで[https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/user-profile-attributes]
adminName: string;
@ApiProperty()
@IsEmail({ blacklisted_chars: '*' })
adminMail: string;
@ApiProperty()
@IsAdminPasswordvalid()
adminPassword: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' })
@MaxLength(255)
acceptedEulaVersion: string;
@ApiProperty({ description: '同意済みプライバシーポリシーのバージョン' })
@MaxLength(255)
acceptedPrivacyNoticeVersion: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' })
@MaxLength(255)
acceptedDpaVersion: string;
@ApiProperty({ description: 'reCAPTCHA Token' })
token: string;
}
export class GetLicenseSummaryRequest {
@ApiProperty()
@Type(() => Number)
@IsInt()
@Min(1)
accountId: number;
}
export class GetTypistGroupRequest {
@ApiProperty()
@Type(() => Number)
@IsInt()
@Min(1)
typistGroupId: number;
}
export class CreateTypistGroupRequest {
@ApiProperty({ minLength: 1, maxLength: 50 })
@MinLength(1)
@MaxLength(50)
typistGroupName: string;
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
@ArrayMinSize(1)
@IsArray()
@IsInt({ each: true })
@Min(1, { each: true })
@IsUnique()
typistIds: number[];
}
export class UpdateTypistGroupRequest {
@ApiProperty({ minLength: 1, maxLength: 50 })
@MinLength(1)
@MaxLength(50)
typistGroupName: string;
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
@ArrayMinSize(1)
@IsArray()
@IsInt({ each: true })
@Min(1, { each: true })
@IsUnique()
typistIds: number[];
}
export class UpdateTypistGroupRequestParam {
@ApiProperty()
@Type(() => Number)
@IsInt()
@Min(1)
typistGroupId: number;
}
export class CreatePartnerAccountRequest {
@ApiProperty()
@MaxLength(255)
companyName: string; companyName: string;
@ApiProperty({ @ApiProperty({
description: '国名(ISO 3166-1 alpha-2)', description: '国名(ISO 3166-1 alpha-2)',
minLength: 2, minLength: 2,
maxLength: 2, maxLength: 2,
}) })
@MinLength(2)
@MaxLength(2)
country: string; country: string;
@ApiProperty({ required: false })
@IsInt()
@IsOptional()
dealerAccountId?: number;
@ApiProperty() @ApiProperty()
@MaxLength(256) // AzureAdB2Cの仕様上、256文字まで[https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/user-profile-attributes]
adminName: string; adminName: string;
@ApiProperty() @ApiProperty()
@IsEmail() @IsEmail({ blacklisted_chars: '*' })
adminMail: string; email: string;
@ApiProperty()
@IsAdminPasswordvalid()
adminPassword: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' })
acceptedEulaVersion: string;
@ApiProperty({ description: '同意済みプライバシーポリシーのバージョン' })
acceptedPrivacyNoticeVersion: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' })
acceptedDpaVersion: string;
@ApiProperty({ description: 'reCAPTCHA Token' })
token: string;
} }
export class GetPartnerLicensesRequest {
@ApiProperty()
@IsInt()
@Type(() => Number)
@Min(0)
limit: number;
@ApiProperty()
@IsInt()
@Type(() => Number)
@Min(0)
offset: number;
@ApiProperty()
@IsInt()
@Type(() => Number)
accountId: number;
}
export class GetOrderHistoriesRequest {
@ApiProperty({ description: '取得件数' })
@IsInt()
@Min(0)
@Type(() => Number)
limit: number;
@ApiProperty({ description: '開始位置' })
@IsInt()
@Min(0)
@Type(() => Number)
offset: number;
@ApiProperty({ description: 'アカウントID' })
@IsInt()
@Type(() => Number)
accountId: number;
}
export class IssueLicenseRequest {
@ApiProperty({ description: '注文元アカウントID' })
@IsInt()
@Type(() => Number)
orderedAccountId: number;
@ApiProperty({ description: 'POナンバー' })
@Matches(/^[A-Z0-9]+$/)
poNumber: string;
}
export class CancelIssueRequest {
@ApiProperty({ description: '注文元アカウントID' })
@IsInt()
@Type(() => Number)
orderedAccountId: number;
@ApiProperty({ description: 'POナンバー' })
@Matches(/^[A-Z0-9]+$/)
poNumber: string;
}
export class CreateWorktypesRequest {
@ApiProperty({ minLength: 1, maxLength: 255, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })
@MaxLength(255)
@IsOptional()
description?: string;
}
export class UpdateWorktypesRequest {
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })
@MaxLength(255)
@IsOptional()
description?: string;
}
export class PostWorktypeOptionItem {
@ApiProperty({ maxLength: 16 })
@MaxLength(16)
@IsRecorderAllowed()
itemLabel: string;
@ApiProperty({
maxLength: 20,
description: `${Object.values(OPTION_ITEM_VALUE_TYPE).join(' / ')}`,
})
@MaxLength(20)
@IsIn(Object.values(OPTION_ITEM_VALUE_TYPE))
defaultValueType: string;
@ApiProperty({ maxLength: 20 })
@MaxLength(20)
@IsRecorderAllowed()
@IsInitialValue()
initialValue: string;
}
export class GetOptionItemsRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(1)
id: number;
}
export class UpdateOptionItemsRequest {
@ApiProperty({
maxItems: 10,
minItems: 10,
type: [PostWorktypeOptionItem],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => PostWorktypeOptionItem)
@ArrayMinSize(10)
@ArrayMaxSize(10)
optionItems: PostWorktypeOptionItem[];
}
export class UpdateOptionItemsRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(1)
id: number;
}
export class UpdateWorktypeRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(1)
id: number;
}
export class DeleteWorktypeRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(1)
id: number;
}
export class PostActiveWorktypeRequest {
@ApiProperty({
required: false,
description: 'Active WorkTypeIDにするWorktypeの内部ID',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
id?: number;
}
export class GetPartnersRequest {
@ApiProperty({ description: '取得件数' })
@IsInt()
@Min(0)
@Type(() => Number)
limit: number;
@ApiProperty({ description: '開始位置' })
@IsInt()
@Min(0)
@Type(() => Number)
offset: number;
}
export class UpdateAccountInfoRequest {
@ApiProperty({ description: '親アカウントのID', required: false })
@Type(() => Number)
@IsInt()
@IsOptional()
parentAccountId?: number;
@ApiProperty({ description: '代行操作許可' })
@Type(() => Boolean)
delegationPermission: boolean;
@ApiProperty({ description: 'プライマリ管理者ID' })
@Type(() => Number)
@IsInt()
primaryAdminUserId: number;
@ApiProperty({ description: 'セカンダリ管理者ID', required: false })
@Type(() => Number)
@IsInt()
@IsOptional()
secondryAdminUserId?: number;
}
export class DeleteAccountRequest {
@ApiProperty({ description: 'アカウントID' })
@Type(() => Number)
@IsInt()
accountId: number;
}
export class GetAccountInfoMinimalAccessRequest {
@ApiProperty({ description: 'idトークン' })
idToken: string;
}
export class GetCompanyNameRequest {
@ApiProperty()
@IsInt()
@Type(() => Number)
accountId: number;
}
// ==============================
// RESPONSE
// ==============================
export class CreateAccountResponse {} export class CreateAccountResponse {}
export class GetLicenseSummaryRequest {
@ApiProperty()
accountId: number;
}
export class LicenseSummaryInfo {
totalLicense: number;
allocatedLicense: number;
reusableLicense: number;
freeLicense: number;
expiringSoonLicense: number;
issueRequesting: number;
numberOfRequesting: number;
allocatableLicenseWithMargin: number;
}
export class GetLicenseSummaryResponse { export class GetLicenseSummaryResponse {
@ApiProperty() @ApiProperty()
totalLicense: number; totalLicense: number;
@ -104,6 +389,11 @@ export class GetLicenseSummaryResponse {
isStorageAvailable: boolean; isStorageAvailable: boolean;
} }
export class GetCompanyNameResponse {
@ApiProperty()
companyName: string;
}
export class Account { export class Account {
@ApiProperty() @ApiProperty()
accountId: number; accountId: number;
@ -133,11 +423,6 @@ export class Account {
parentAccountName?: string; parentAccountName?: string;
} }
export class GetMyAccountResponse {
@ApiProperty({ type: Account })
account: Account;
}
export class Author { export class Author {
@ApiProperty({ description: 'Authorユーザーの内部ID' }) @ApiProperty({ description: 'Authorユーザーの内部ID' })
id: number; id: number;
@ -145,11 +430,6 @@ export class Author {
authorId: string; authorId: string;
} }
export class GetAuthorsResponse {
@ApiProperty({ type: [Author] })
authors: Author[];
}
export class Typist { export class Typist {
@ApiProperty({ @ApiProperty({
description: 'TypistのユーザーID', description: 'TypistのユーザーID',
@ -160,11 +440,6 @@ export class Typist {
name: string; name: string;
} }
export class GetTypistsResponse {
@ApiProperty({ type: [Typist] })
typists: Typist[];
}
export class TypistGroup { export class TypistGroup {
@ApiProperty({ @ApiProperty({
description: 'TypistGroupのID', description: 'TypistGroupのID',
@ -175,18 +450,26 @@ export class TypistGroup {
name: string; name: string;
} }
export class GetMyAccountResponse {
@ApiProperty({ type: Account })
account: Account;
}
export class GetAuthorsResponse {
@ApiProperty({ type: [Author] })
authors: Author[];
}
export class GetTypistsResponse {
@ApiProperty({ type: [Typist] })
typists: Typist[];
}
export class GetTypistGroupsResponse { export class GetTypistGroupsResponse {
@ApiProperty({ type: [TypistGroup] }) @ApiProperty({ type: [TypistGroup] })
typistGroups: TypistGroup[]; typistGroups: TypistGroup[];
} }
export class GetTypistGroupRequest {
@ApiProperty()
@Type(() => Number)
@IsInt()
@Min(0)
typistGroupId: number;
}
export class GetTypistGroupResponse { export class GetTypistGroupResponse {
@ApiProperty() @ApiProperty()
typistGroupName: string; typistGroupName: string;
@ -194,72 +477,12 @@ export class GetTypistGroupResponse {
typistIds: number[]; typistIds: number[];
} }
export class CreateTypistGroupRequest {
@ApiProperty({ minLength: 1, maxLength: 50 })
@MinLength(1)
@MaxLength(50)
typistGroupName: string;
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
@ArrayMinSize(1)
@IsArray()
@IsInt({ each: true })
@Min(0, { each: true })
@IsUnique()
typistIds: number[];
}
export class CreateTypistGroupResponse {} export class CreateTypistGroupResponse {}
export class UpdateTypistGroupRequest {
@ApiProperty({ minLength: 1, maxLength: 50 })
@MinLength(1)
@MaxLength(50)
typistGroupName: string;
@ApiProperty({ minItems: 1, isArray: true, type: 'integer' })
@ArrayMinSize(1)
@IsArray()
@IsInt({ each: true })
@Min(0, { each: true })
@IsUnique()
typistIds: number[];
}
export class UpdateTypistGroupRequestParam {
@ApiProperty()
@Type(() => Number)
@IsInt()
@Min(0)
typistGroupId: number;
}
export class UpdateTypistGroupResponse {} export class UpdateTypistGroupResponse {}
export class CreatePartnerAccountRequest {
@ApiProperty()
companyName: string;
@ApiProperty({
description: '国名(ISO 3166-1 alpha-2)',
minLength: 2,
maxLength: 2,
})
country: string;
@ApiProperty()
adminName: string;
@ApiProperty()
@IsEmail()
email: string;
}
export class CreatePartnerAccountResponse {} export class CreatePartnerAccountResponse {}
export class GetPartnerLicensesRequest {
@ApiProperty()
@IsInt()
limit: number;
@ApiProperty()
@IsInt()
offset: number;
@ApiProperty()
accountId: number;
}
export class PartnerLicenseInfo { export class PartnerLicenseInfo {
@ApiProperty({ description: 'アカウントID' }) @ApiProperty({ description: 'アカウントID' })
@ -291,7 +514,6 @@ export class PartnerLicenseInfo {
}) })
issueRequesting: number; issueRequesting: number;
} }
export class GetPartnerLicensesResponse { export class GetPartnerLicensesResponse {
@ApiProperty() @ApiProperty()
total: number; total: number;
@ -301,30 +523,6 @@ export class GetPartnerLicensesResponse {
childrenPartnerLicenses: PartnerLicenseInfo[]; childrenPartnerLicenses: PartnerLicenseInfo[];
} }
// 第五階層のshortage算出用
export class PartnerLicenseInfoForShortage {
expiringSoonLicense?: number;
allocatableLicenseWithMargin?: number;
}
// RepositoryからPartnerLicenseInfoに関する情報を取得する際の型
export type PartnerLicenseInfoForRepository = Omit<
PartnerLicenseInfo & PartnerLicenseInfoForShortage,
'shortage'
>;
export class GetOrderHistoriesRequest {
@ApiProperty({ description: '取得件数' })
@IsInt()
@Min(0)
limit: number;
@ApiProperty({ description: '開始位置' })
@IsInt()
@Min(0)
offset: number;
@ApiProperty({ description: 'アカウントID' })
accountId: number;
}
export class LicenseOrder { export class LicenseOrder {
@ApiProperty({ description: '注文日付' }) @ApiProperty({ description: '注文日付' })
@ -338,6 +536,7 @@ export class LicenseOrder {
@ApiProperty({ description: '注文状態' }) @ApiProperty({ description: '注文状態' })
status: string; status: string;
} }
export class GetOrderHistoriesResponse { export class GetOrderHistoriesResponse {
@ApiProperty({ description: '合計件数' }) @ApiProperty({ description: '合計件数' })
total: number; total: number;
@ -345,16 +544,9 @@ export class GetOrderHistoriesResponse {
orderHistories: LicenseOrder[]; orderHistories: LicenseOrder[];
} }
export class IssueLicenseRequest {
@ApiProperty({ description: '注文元アカウントID' })
orderedAccountId: number;
@ApiProperty({ description: 'POナンバー' })
@Matches(/^[A-Z0-9]+$/)
poNumber: string;
}
export class IssueLicenseResponse {} export class IssueLicenseResponse {}
export class Dealer { export class Dealer {
@ApiProperty({ description: 'アカウントID' }) @ApiProperty({ description: 'アカウントID' })
id: number; id: number;
@ -363,22 +555,15 @@ export class Dealer {
@ApiProperty({ description: '国名(ISO 3166-1 alpha-2)' }) @ApiProperty({ description: '国名(ISO 3166-1 alpha-2)' })
country: string; country: string;
} }
export class GetDealersResponse { export class GetDealersResponse {
@ApiProperty({ type: [Dealer] }) @ApiProperty({ type: [Dealer] })
dealers: Dealer[]; dealers: Dealer[];
} }
export class CancelIssueRequest {
@ApiProperty({ description: '注文元アカウントID' })
orderedAccountId: number;
@ApiProperty({ description: 'POナンバー' })
@Matches(/^[A-Z0-9]+$/)
poNumber: string;
}
export class CancelIssueResponse {} export class CancelIssueResponse {}
export class Worktype { export class Worktype {
@ApiProperty({ description: 'WorktypeのID' }) @ApiProperty({ description: 'WorktypeのID' })
id: number; id: number;
@ -399,52 +584,11 @@ export class GetWorktypesResponse {
active?: number; active?: number;
} }
export class CreateWorktypesRequest {
@ApiProperty({ minLength: 1, maxLength: 255, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })
@MaxLength(255)
@IsOptional()
description?: string;
}
export class CreateWorktypeResponse {} export class CreateWorktypeResponse {}
export class UpdateWorktypesRequest {
@ApiProperty({ minLength: 1, description: 'WorktypeID' })
@MinLength(1)
@MaxLength(255)
@IsRecorderAllowed()
worktypeId: string;
@ApiProperty({ description: 'Worktypeの説明', required: false })
@MaxLength(255)
@IsOptional()
description?: string;
}
export class UpdateWorktypeResponse {} export class UpdateWorktypeResponse {}
export class PostWorktypeOptionItem {
@ApiProperty({ maxLength: 16 })
@MaxLength(16)
@IsRecorderAllowed()
itemLabel: string;
@ApiProperty({
maxLength: 20,
description: `${Object.values(OPTION_ITEM_VALUE_TYPE).join(' / ')}`,
})
@MaxLength(20)
@IsIn(Object.values(OPTION_ITEM_VALUE_TYPE))
defaultValueType: string;
@ApiProperty({ maxLength: 20 })
@MaxLength(20)
@IsRecorderAllowed()
@IsInitialValue()
initialValue: string;
}
export class GetWorktypeOptionItem extends PostWorktypeOptionItem { export class GetWorktypeOptionItem extends PostWorktypeOptionItem {
@ApiProperty() @ApiProperty()
id: number; id: number;
@ -459,82 +603,12 @@ export class GetOptionItemsResponse {
optionItems: GetWorktypeOptionItem[]; optionItems: GetWorktypeOptionItem[];
} }
export class GetOptionItemsRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(0)
id: number;
}
export class UpdateOptionItemsRequest {
@ApiProperty({
maxItems: 10,
minItems: 10,
type: [PostWorktypeOptionItem],
})
@IsArray()
@ValidateNested({ each: true })
@Type(() => PostWorktypeOptionItem)
@ArrayMinSize(10)
@ArrayMaxSize(10)
optionItems: PostWorktypeOptionItem[];
}
export class UpdateOptionItemsResponse {} export class UpdateOptionItemsResponse {}
export class UpdateOptionItemsRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(0)
id: number;
}
export class UpdateWorktypeRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(0)
id: number;
}
export class DeleteWorktypeRequestParam {
@ApiProperty({ description: 'Worktypeの内部ID' })
@Type(() => Number)
@IsInt()
@Min(0)
id: number;
}
export class DeleteWorktypeResponse {} export class DeleteWorktypeResponse {}
export class PostActiveWorktypeRequest {
@ApiProperty({
required: false,
description: 'Active WorkTypeIDにするWorktypeの内部ID',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(0)
id?: number;
}
export class PostActiveWorktypeResponse {} export class PostActiveWorktypeResponse {}
export class GetPartnersRequest {
@ApiProperty({ description: '取得件数' })
@IsInt()
@Min(0)
@Type(() => Number)
limit: number;
@ApiProperty({ description: '開始位置' })
@IsInt()
@Min(0)
@Type(() => Number)
offset: number;
}
export class Partner { export class Partner {
@ApiProperty({ description: '会社名' }) @ApiProperty({ description: '会社名' })
@ -560,6 +634,42 @@ export class GetPartnersResponse {
partners: Partner[]; partners: Partner[];
} }
export class UpdateAccountInfoResponse {}
export class DeleteAccountResponse {}
export class GetAccountInfoMinimalAccessResponse {
@ApiProperty({ description: '階層' })
tier: number;
}
// ==============================
// Request/Response外の型
// TODO: Request/Response/その他の型を別ファイルに分ける
// ==============================
export class LicenseSummaryInfo {
totalLicense: number;
allocatedLicense: number;
reusableLicense: number;
freeLicense: number;
expiringSoonLicense: number;
issueRequesting: number;
numberOfRequesting: number;
allocatableLicenseWithMargin: number;
}
// 第五階層のshortage算出用
export class PartnerLicenseInfoForShortage {
expiringSoonLicense?: number;
allocatableLicenseWithMargin?: number;
}
// RepositoryからPartnerLicenseInfoに関する情報を取得する際の型
export type PartnerLicenseInfoForRepository = Omit<
PartnerLicenseInfo & PartnerLicenseInfoForShortage,
'shortage'
>;
// RepositoryからPartnerLicenseInfoに関する情報を取得する際の型 // RepositoryからPartnerLicenseInfoに関する情報を取得する際の型
export type PartnerInfoFromDb = { export type PartnerInfoFromDb = {
name: string; name: string;
@ -569,45 +679,3 @@ export type PartnerInfoFromDb = {
primaryAccountExternalId: string; primaryAccountExternalId: string;
dealerManagement: boolean; dealerManagement: boolean;
}; };
export class UpdateAccountInfoRequest {
@ApiProperty({ description: '親アカウントのID', required: false })
@IsOptional()
parentAccountId?: number;
@ApiProperty({ description: '代行操作許可' })
delegationPermission: boolean;
@ApiProperty({ description: 'プライマリ管理者ID' })
primaryAdminUserId: number;
@ApiProperty({ description: 'セカンダリ管理者ID', required: false })
@IsOptional()
secondryAdminUserId?: number;
}
export class UpdateAccountInfoResponse {}
export class DeleteAccountRequest {
@ApiProperty({ description: 'アカウントID' })
accountId: number;
}
export class DeleteAccountResponse {}
export class GetAccountInfoMinimalAccessRequest {
@ApiProperty({ description: 'idトークン' })
idToken: string;
}
export class GetAccountInfoMinimalAccessResponse {
@ApiProperty({ description: '階層' })
tier: number;
}
export class GetCompanyNameRequest {
@ApiProperty()
@IsInt()
@Type(() => Number)
accountId: number;
}
export class GetCompanyNameResponse {
@ApiProperty()
companyName: string;
}

View File

@ -3,6 +3,7 @@ import {
Controller, Controller,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Post, Post,
Req, Req,
UseGuards, UseGuards,
@ -25,8 +26,7 @@ import {
DelegationAccessTokenResponse, DelegationAccessTokenResponse,
} from './types/types'; } from './types/types';
import { retrieveAuthorizationToken } from '../../common/http/helper'; import { retrieveAuthorizationToken } from '../../common/http/helper';
import { makeContext } from '../../common/log'; import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { v4 as uuidv4 } from 'uuid';
import { Request } from 'express'; import { Request } from 'express';
import { AuthGuard } from '../../common/guards/auth/authguards'; import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards'; import { RoleGuard } from '../../common/guards/role/roleguards';
@ -39,6 +39,7 @@ import { RedisService } from '../../gateways/redis/redis.service';
@ApiTags('auth') @ApiTags('auth')
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
private readonly logger = new Logger(AuthController.name);
constructor( constructor(
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly redisService: RedisService, private readonly redisService: RedisService,
@ -65,8 +66,29 @@ export class AuthController {
'AzureADB2Cでのサインイン後に払いだされるIDトークンを元に認証用のアクセストークンとリフレッシュトークンを生成します', 'AzureADB2Cでのサインイン後に払いだされるIDトークンを元に認証用のアクセストークンとリフレッシュトークンを生成します',
operationId: 'token', operationId: 'token',
}) })
async token(@Body() body: TokenRequest): Promise<TokenResponse> { async token(
const context = makeContext(uuidv4()); @Body() body: TokenRequest,
@Req() req: Request,
): Promise<TokenResponse> {
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const idToken = await this.authService.getVerifiedIdToken( const idToken = await this.authService.getVerifiedIdToken(
context, context,
body.idToken, body.idToken,
@ -145,7 +167,6 @@ export class AuthController {
}) })
async accessToken(@Req() req: Request): Promise<AccessTokenResponse> { async accessToken(@Req() req: Request): Promise<AccessTokenResponse> {
const refreshToken = retrieveAuthorizationToken(req); const refreshToken = retrieveAuthorizationToken(req);
if (!refreshToken) { if (!refreshToken) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000107'), makeErrorResponse('E000107'),
@ -153,7 +174,24 @@ export class AuthController {
); );
} }
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const accessToken = await this.authService.generateAccessToken( const accessToken = await this.authService.generateAccessToken(
context, context,
@ -202,13 +240,29 @@ export class AuthController {
): Promise<DelegationTokenResponse> { ): Promise<DelegationTokenResponse> {
const { delegatedAccountId } = body; const { delegatedAccountId } = body;
const token = retrieveAuthorizationToken(req); const token = retrieveAuthorizationToken(req);
if (!token) { if (!token) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000107'), makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(token, { json: true }); const decodedAccessToken = jwt.decode(token, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -218,7 +272,9 @@ export class AuthController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const refreshToken = await this.authService.generateDelegationRefreshToken( const refreshToken = await this.authService.generateDelegationRefreshToken(
context, context,
userId, userId,
@ -257,13 +313,29 @@ export class AuthController {
@Req() req: Request, @Req() req: Request,
): Promise<DelegationAccessTokenResponse> { ): Promise<DelegationAccessTokenResponse> {
const refreshToken = retrieveAuthorizationToken(req); const refreshToken = retrieveAuthorizationToken(req);
if (!refreshToken) { if (!refreshToken) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000107'), makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedRefreshToken = jwt.decode(refreshToken, { json: true }); const decodedRefreshToken = jwt.decode(refreshToken, { json: true });
if (!decodedRefreshToken) { if (!decodedRefreshToken) {
throw new HttpException( throw new HttpException(
@ -273,7 +345,9 @@ export class AuthController {
} }
const { userId, delegateUserId } = decodedRefreshToken as RefreshToken; const { userId, delegateUserId } = decodedRefreshToken as RefreshToken;
const context = makeContext(userId); const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const accessToken = await this.authService.updateDelegationAccessToken( const accessToken = await this.authService.updateDelegationAccessToken(
context, context,
delegateUserId, delegateUserId,

View File

@ -31,7 +31,7 @@ describe('AuthService', () => {
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';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
expect(await service.getVerifiedIdToken(context, token)).toEqual( expect(await service.getVerifiedIdToken(context, token)).toEqual(
idTokenPayload, idTokenPayload,
); );
@ -43,7 +43,7 @@ describe('AuthService', () => {
const service = await makeAuthServiceMock(adb2cParam, configMockValue); const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = 'invalid.id.token'; const token = 'invalid.id.token';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED),
); );
@ -58,7 +58,7 @@ describe('AuthService', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED),
); );
@ -73,7 +73,7 @@ describe('AuthService', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED),
); );
@ -86,7 +86,7 @@ describe('AuthService', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED),
); );
@ -101,7 +101,7 @@ describe('AuthService', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaW52bGlkX2lzc3VlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.0bp3e1mDG78PX3lo8zgOLXGenIqG_Vi6kw7CbwauAQM-cnUZ_aVCoJ_dAv_QmPElOQKcCkRrAvAZ91FwuHDlBGuuDqx8OwqN0VaD-4NPouoAswj-9HNvBm8gUn-pGaXkvWt_72UdCJavZJjDj_RHur8y8kFt5Qeab3mUP2x-uNcV2Q2x3M_IIfcRiIZkRZm_azKfiVIy7tzoUFLDss97y938aR8imMVxazoSQvj7RWIWylgeRr9yVt7qYl18cnEVL0IGtslFbqhfNsiEmRCMsttm5kXs7E9B0bhhUe_xbJW9VumQ6G7dgMrswevp_jRgbpWJoZsgErtqIRl9Tc9ikA'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaW52bGlkX2lzc3VlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.0bp3e1mDG78PX3lo8zgOLXGenIqG_Vi6kw7CbwauAQM-cnUZ_aVCoJ_dAv_QmPElOQKcCkRrAvAZ91FwuHDlBGuuDqx8OwqN0VaD-4NPouoAswj-9HNvBm8gUn-pGaXkvWt_72UdCJavZJjDj_RHur8y8kFt5Qeab3mUP2x-uNcV2Q2x3M_IIfcRiIZkRZm_azKfiVIy7tzoUFLDss97y938aR8imMVxazoSQvj7RWIWylgeRr9yVt7qYl18cnEVL0IGtslFbqhfNsiEmRCMsttm5kXs7E9B0bhhUe_xbJW9VumQ6G7dgMrswevp_jRgbpWJoZsgErtqIRl9Tc9ikA';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED), new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED),
); );
@ -115,7 +115,7 @@ describe('AuthService', () => {
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';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -131,7 +131,7 @@ describe('AuthService', () => {
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';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -150,7 +150,7 @@ describe('AuthService', () => {
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';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual( await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -186,7 +186,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -210,7 +210,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -234,7 +234,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -258,7 +258,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -282,7 +282,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -306,7 +306,7 @@ describe('checkIsAcceptedLatestVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const idToken = { const idToken = {
emails: [], emails: [],
@ -361,7 +361,7 @@ describe('generateDelegationRefreshToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
const delegationRefreshToken = await service.generateDelegationRefreshToken( const delegationRefreshToken = await service.generateDelegationRefreshToken(
context, context,
@ -399,7 +399,7 @@ describe('generateDelegationRefreshToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
try { try {
await service.generateDelegationRefreshToken( await service.generateDelegationRefreshToken(
@ -437,7 +437,7 @@ describe('generateDelegationRefreshToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
try { try {
await service.generateDelegationRefreshToken( await service.generateDelegationRefreshToken(
@ -495,7 +495,7 @@ describe('generateDelegationAccessToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
const delegationRefreshToken = await service.generateDelegationRefreshToken( const delegationRefreshToken = await service.generateDelegationRefreshToken(
context, context,
@ -540,7 +540,7 @@ describe('generateDelegationAccessToken', () => {
tier: 4, tier: 4,
}); });
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
try { try {
await service.generateDelegationAccessToken(context, 'invalid token'); await service.generateDelegationAccessToken(context, 'invalid token');
@ -595,7 +595,7 @@ describe('updateDelegationAccessToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
const delegationRefreshToken = await service.generateDelegationRefreshToken( const delegationRefreshToken = await service.generateDelegationRefreshToken(
context, context,
@ -653,7 +653,7 @@ describe('updateDelegationAccessToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
const delegationRefreshToken = await service.generateDelegationRefreshToken( const delegationRefreshToken = await service.generateDelegationRefreshToken(
context, context,
@ -719,7 +719,7 @@ describe('updateDelegationAccessToken', () => {
{ role: USER_ROLES.NONE }, { role: USER_ROLES.NONE },
); );
const context = makeContext(parentAdmin.external_id); const context = makeContext(parentAdmin.external_id, 'requestId');
const delegationRefreshToken = await service.generateDelegationRefreshToken( const delegationRefreshToken = await service.generateDelegationRefreshToken(
context, context,

View File

@ -61,7 +61,10 @@ export class AuthService {
); );
try { try {
// IDトークンのユーザーがDBに登録されていてメール認証が完了しているユーザーか検証 // IDトークンのユーザーがDBに登録されていてメール認証が完了しているユーザーか検証
const user = await this.usersRepository.findVerifiedUser(idToken.sub); const user = await this.usersRepository.findVerifiedUser(
context,
idToken.sub,
);
return user !== undefined; return user !== undefined;
} catch (e) { } catch (e) {
@ -96,7 +99,10 @@ export class AuthService {
// ユーザー情報とユーザーが属しているアカウント情報を取得 // ユーザー情報とユーザーが属しているアカウント情報を取得
try { try {
const user = await this.usersRepository.findUserByExternalId(idToken.sub); const user = await this.usersRepository.findUserByExternalId(
context,
idToken.sub,
);
if (!user.account) { if (!user.account) {
throw new Error('Account information not found'); throw new Error('Account information not found');
} }
@ -236,11 +242,13 @@ export class AuthService {
// ユーザー情報とユーザーが属しているアカウント情報を取得 // ユーザー情報とユーザーが属しているアカウント情報を取得
try { try {
const user = await this.usersRepository.findUserByExternalId( const user = await this.usersRepository.findUserByExternalId(
context,
delegateUserExternalId, delegateUserExternalId,
); );
// 代行操作対象アカウントの管理者ユーザーを取得 // 代行操作対象アカウントの管理者ユーザーを取得
const adminUser = await this.usersRepository.findDelegateUser( const adminUser = await this.usersRepository.findDelegateUser(
context,
user.account_id, user.account_id,
originAccountId, originAccountId,
); );
@ -382,6 +390,7 @@ export class AuthService {
} }
const user = await this.usersRepository.findUserByExternalId( const user = await this.usersRepository.findUserByExternalId(
context,
delegateUserExternalId, delegateUserExternalId,
); );
@ -406,6 +415,7 @@ export class AuthService {
// 代行操作対象アカウントの管理者ユーザーが存在して、アカウントに対して代行操作権限があるか確認 // 代行操作対象アカウントの管理者ユーザーが存在して、アカウントに対して代行操作権限があるか確認
const delegationPermission = const delegationPermission =
await this.usersRepository.isAllowDelegationPermission( await this.usersRepository.isAllowDelegationPermission(
context,
user.account_id, user.account_id,
userId, userId,
); );
@ -694,7 +704,10 @@ export class AuthService {
acceptedDpaVersion, acceptedDpaVersion,
latestDpaVersion, latestDpaVersion,
tier, tier,
} = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub); } = await this.usersRepository.getAcceptedAndLatestVersion(
context,
idToken.sub,
);
// 第五階層はEULAとPrivacyNoticeのみ判定 // 第五階層はEULAとPrivacyNoticeのみ判定
if (tier === TIERS.TIER5) { if (tier === TIERS.TIER5) {

View File

@ -1,10 +1,14 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsInt } from 'class-validator'; import { IsIn, IsInt, IsNotEmpty, IsString } from 'class-validator';
export class TokenRequest { export class TokenRequest {
@ApiProperty() @ApiProperty()
@IsString()
@IsNotEmpty()
idToken: string; idToken: string;
@ApiProperty({ description: 'web or mobile or desktop' }) @ApiProperty({ description: 'web or mobile or desktop' })
@IsIn(['web', 'mobile', 'desktop'], { message: 'invalid type' })
@IsNotEmpty()
type: string; type: string;
} }
export class TokenResponse { export class TokenResponse {

View File

@ -4,6 +4,7 @@ import {
Get, Get,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Post, Post,
Query, Query,
Req, Req,
@ -37,12 +38,13 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, USER_ROLES } from '../../constants'; 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, retrieveRequestId, retrieveIp } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('files') @ApiTags('files')
@Controller('files') @Controller('files')
export class FilesController { export class FilesController {
private readonly logger = new Logger(FilesController.name);
constructor(private readonly filesService: FilesService) {} constructor(private readonly filesService: FilesService) {}
@ApiResponse({ @ApiResponse({
@ -84,6 +86,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -93,7 +111,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const { const {
url, url,
@ -176,6 +195,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -185,7 +220,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishUploadSas(context, userId); const url = await this.filesService.publishUploadSas(context, userId);
return { url }; return { url };
@ -237,6 +273,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -246,7 +298,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishAudioFileDownloadSas( const url = await this.filesService.publishAudioFileDownloadSas(
context, context,
@ -285,9 +338,7 @@ export class FilesController {
}) })
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@UseGuards( @UseGuards(RoleGuard.requireds({ roles: [USER_ROLES.TYPIST] }))
RoleGuard.requireds({ roles: [USER_ROLES.AUTHOR, USER_ROLES.TYPIST] }),
)
async downloadTemplateLocation( async downloadTemplateLocation(
@Req() req: Request, @Req() req: Request,
@Query() body: TemplateDownloadLocationRequest, @Query() body: TemplateDownloadLocationRequest,
@ -301,6 +352,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -310,7 +377,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishTemplateFileDownloadSas( const url = await this.filesService.publishTemplateFileDownloadSas(
context, context,
@ -357,6 +425,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -366,7 +450,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const url = await this.filesService.publishTemplateFileUploadSas( const url = await this.filesService.publishTemplateFileUploadSas(
context, context,
@ -418,6 +503,22 @@ export class FilesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -427,7 +528,8 @@ export class FilesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.filesService.templateUploadFinished(context, userId, url, name); await this.filesService.templateUploadFinished(context, userId, url, name);
return {}; return {};
} }

View File

@ -34,7 +34,12 @@ import { TasksRepositoryService } from '../../repositories/tasks/tasks.repositor
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { getCheckoutPermissions, getTask } from '../tasks/test/utility'; import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
import { DateWithZeroTime } from '../licenses/types/types'; import { DateWithZeroTime } from '../licenses/types/types';
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants'; import {
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
TASK_STATUS,
USER_ROLES,
} from '../../constants';
describe('publishUploadSas', () => { describe('publishUploadSas', () => {
let source: DataSource | null = null; let source: DataSource | null = null;
@ -85,7 +90,7 @@ describe('publishUploadSas', () => {
null, null,
null, null,
); );
const context = makeContext(externalId); const context = makeContext(externalId, 'requestId');
const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/${userId}`; const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/${userId}`;
//SASトークンを返却する //SASトークンを返却する
@ -107,7 +112,7 @@ describe('publishUploadSas', () => {
// 第四階層のアカウント作成 // 第四階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 4 }); const { admin } = await makeTestAccount(source, { tier: 4 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//Blobコンテナ存在チェックに失敗するようにする //Blobコンテナ存在チェックに失敗するようにする
overrideBlobstorageService(service, { overrideBlobstorageService(service, {
@ -135,7 +140,7 @@ describe('publishUploadSas', () => {
// 第四階層のアカウント作成 // 第四階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 4 }); const { admin } = await makeTestAccount(source, { tier: 4 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//BlobのSASトークン生成に失敗するようにする //BlobのSASトークン生成に失敗するようにする
overrideBlobstorageService(service, { overrideBlobstorageService(service, {
@ -164,7 +169,7 @@ describe('publishUploadSas', () => {
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5, locked: true }); const { admin } = await makeTestAccount(source, { tier: 5, locked: true });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
try { try {
await service.publishUploadSas(context, admin.external_id); await service.publishUploadSas(context, admin.external_id);
@ -209,7 +214,10 @@ describe('publishUploadSas', () => {
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
service.publishUploadSas(makeContext('trackingId'), externalId), service.publishUploadSas(
makeContext('trackingId', 'requestId'),
externalId,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
); );
@ -267,7 +275,10 @@ describe('publishUploadSas', () => {
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( await expect(
service.publishUploadSas(makeContext('trackingId'), externalId), service.publishUploadSas(
makeContext('trackingId', 'requestId'),
externalId,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
); );
@ -348,7 +359,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, authorExternalId,
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', authorAuthorId ?? '',
@ -368,7 +379,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(result).toEqual({ jobNumber: '00000001' }); expect(result).toEqual({ jobNumber: '00000001' });
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
authorId: 'AUTHOR_ID', authorId: 'AUTHOR_ID',
@ -449,7 +460,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, authorExternalId,
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', authorAuthorId ?? '',
@ -469,7 +480,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(result).toEqual({ jobNumber: '00000002' }); expect(result).toEqual({ jobNumber: '00000002' });
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
authorId: 'AUTHOR_ID', authorId: 'AUTHOR_ID',
@ -572,7 +583,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
myExternalId, // API実行者のユーザーIDを設定 myExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
@ -592,7 +603,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(result).toEqual({ jobNumber: '00000001' }); expect(result).toEqual({ jobNumber: '00000001' });
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
authorId: 'AUTHOR_ID', authorId: 'AUTHOR_ID',
@ -694,7 +705,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
NotificationhubService, NotificationhubService,
); );
const result = await service.uploadFinished( const result = await service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
myExternalId, // API実行者のユーザーIDを設定 myExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip', 'http://blob/url/file.zip',
'XXXXXXXXXX', // 音声ファイルの情報には、録音者のAuthorIDが入る 'XXXXXXXXXX', // 音声ファイルの情報には、録音者のAuthorIDが入る
@ -714,7 +725,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(result).toEqual({ jobNumber: '00000001' }); expect(result).toEqual({ jobNumber: '00000001' });
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
authorId: 'XXXXXXXXXX', authorId: 'XXXXXXXXXX',
@ -763,7 +774,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
const result = await service.uploadFinished( const result = await service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, // API実行者のユーザーIDを設定 authorExternalId, // API実行者のユーザーIDを設定
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
@ -819,7 +830,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
await expect( await expect(
service.uploadFinished( service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, authorExternalId,
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', authorAuthorId ?? '',
@ -866,7 +877,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
await expect( await expect(
service.uploadFinished( service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, authorExternalId,
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', authorAuthorId ?? '',
@ -907,7 +918,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
await expect( await expect(
service.uploadFinished( service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
'authorExternalId', 'authorExternalId',
'http://blob/url/file.zip', 'http://blob/url/file.zip',
'authorAuthorId', 'authorAuthorId',
@ -958,7 +969,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
await expect( await expect(
service.uploadFinished( service.uploadFinished(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
authorExternalId, authorExternalId,
'http://blob/url/file.zip', 'http://blob/url/file.zip',
authorAuthorId ?? '', authorAuthorId ?? '',
@ -1043,7 +1054,7 @@ describe('音声ファイルダウンロードURL取得', () => {
expect( expect(
await service.publishAudioFileDownloadSas( await service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1113,7 +1124,7 @@ describe('音声ファイルダウンロードURL取得', () => {
expect( expect(
await service.publishAudioFileDownloadSas( await service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1160,7 +1171,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1214,7 +1225,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishTemplateFileDownloadSas( service.publishTemplateFileDownloadSas(
makeContext('tracking'), makeContext('tracking', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1259,7 +1270,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1291,7 +1302,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
1, 1,
), ),
@ -1340,7 +1351,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1395,7 +1406,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1467,7 +1478,7 @@ describe('音声ファイルダウンロードURL取得', () => {
await expect( await expect(
service.publishAudioFileDownloadSas( service.publishAudioFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
@ -1499,15 +1510,11 @@ describe('テンプレートファイルダウンロードURL取得', () => {
it('ダウンロードSASトークンが乗っているURLを取得できる', async () => { it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail(); 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, id: userId } = await makeTestUser(source, {
source,
{
account_id: accountId, account_id: accountId,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID', });
},
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`; const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
const { audioFileId } = await createTask( const { audioFileId } = await createTask(
@ -1515,9 +1522,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId, accountId,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
undefined, userId,
authorId ?? '', 'AUTHOR_ID',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1533,13 +1540,12 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
expect( const resultUrl = await service.publishTemplateFileDownloadSas(
await service.publishTemplateFileDownloadSas( makeContext('tracking', 'requestId'),
makeContext('tracking'),
externalId, externalId,
audioFileId, audioFileId,
), );
).toEqual(`${url}?sas-token`); expect(resultUrl).toBe(`${url}?sas-token`);
}); });
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => { it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail(); if (!source) fail();
@ -1551,15 +1557,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id, parent_account_id: tier4Accounts[0].account.id,
tier: 5, tier: 5,
}); });
const { const { external_id: externalId, id: userId } = await makeTestUser(source, {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id, account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID',
}); });
// 本日の日付を作成 // 本日の日付を作成
let yesterday = new Date(); let yesterday = new Date();
@ -1585,9 +1586,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id, tier5Accounts.account.id,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
undefined, userId,
authorId ?? '', 'AUTHOR_ID',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1603,22 +1604,20 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
expect( const resultUrl = await service.publishTemplateFileDownloadSas(
await service.publishTemplateFileDownloadSas( makeContext('trackingId', 'requestId'),
makeContext('trackingId'),
externalId, externalId,
audioFileId, audioFileId,
), );
).toEqual(`${url}?sas-token`); expect(resultUrl).toBe(`${url}?sas-token`);
}); });
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => { it('タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail(); 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,
external_id: 'typist-user-external-id', external_id: 'typist-user-external-id',
role: 'typist', role: USER_ROLES.TYPIST,
author_id: undefined,
}); });
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`; const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
@ -1627,7 +1626,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId, accountId,
url, url,
'test.zip', 'test.zip',
'Finished', TASK_STATUS.FINISHED,
userId, userId,
); );
@ -1644,31 +1643,35 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('tracking'), makeContext('tracking', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
); );
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
}); });
it('Typistの場合、自身が担当するタスクでない場合エラー', async () => { it('自身が担当するタスクでない場合エラー', async () => {
if (!source) fail(); 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,
external_id: 'typist-user-external-id', external_id: 'typist-user-external-id',
role: 'typist', role: USER_ROLES.TYPIST,
author_id: undefined,
}); });
const { id: otherId } = await makeTestUser(source, { const { id: otherId } = await makeTestUser(source, {
account_id: accountId, account_id: accountId,
external_id: 'other-typist-user-external-id', external_id: 'other-typist-user-external-id',
role: 'typist', role: USER_ROLES.TYPIST,
author_id: undefined,
}); });
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`; const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
@ -1677,7 +1680,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId, accountId,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
otherId, otherId,
); );
@ -1694,60 +1697,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('tracking'), makeContext('tracking', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
);
});
it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
const { audioFileId } = await createTask(
source,
accountId,
url,
'test.zip',
'InProgress',
undefined,
'OTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = true;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
makeContext('tracking'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
); );
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
}); });
it('Taskが存在しない場合はエラーとなる', async () => { it('Taskが存在しない場合はエラーとなる', async () => {
@ -1755,9 +1719,8 @@ describe('テンプレートファイルダウンロードURL取得', () => {
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,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID',
}); });
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1771,29 +1734,31 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('tracking'), makeContext('tracking', 'requestId'),
externalId, externalId,
1, 1,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
); );
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
} else {
fail();
}
}
}); });
it('blobストレージにファイルが存在しない場合はエラーとなる', async () => { it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
if (!source) fail(); 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, id: userId } = await makeTestUser(source, {
source,
{
account_id: accountId, account_id: accountId,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID', });
},
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`; const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;
const { audioFileId } = await createTask( const { audioFileId } = await createTask(
@ -1801,9 +1766,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
accountId, accountId,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
undefined, userId,
authorId ?? '', 'AUTHOR_ID',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1819,15 +1784,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('tracking'), makeContext('tracking', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
); );
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
} else {
fail();
}
}
}); });
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => { it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail(); if (!source) fail();
@ -1839,15 +1810,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id, parent_account_id: tier4Accounts[0].account.id,
tier: 5, tier: 5,
}); });
const { const { external_id: externalId, id: userId } = await makeTestUser(source, {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id, account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID',
}); });
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`; const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
@ -1856,9 +1822,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id, tier5Accounts.account.id,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
undefined, undefined,
authorId ?? '', 'AUTHOR_ID',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1874,15 +1840,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
); );
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
} else {
fail();
}
}
}); });
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => { it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail(); if (!source) fail();
@ -1894,15 +1866,10 @@ describe('テンプレートファイルダウンロードURL取得', () => {
parent_account_id: tier4Accounts[0].account.id, parent_account_id: tier4Accounts[0].account.id,
tier: 5, tier: 5,
}); });
const { const { external_id: externalId, id: userId } = await makeTestUser(source, {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id, account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id', external_id: 'typist-user-external-id',
role: 'author', role: USER_ROLES.TYPIST,
author_id: 'AUTHOR_ID',
}); });
// 昨日の日付を作成 // 昨日の日付を作成
let yesterday = new Date(); let yesterday = new Date();
@ -1928,9 +1895,9 @@ describe('テンプレートファイルダウンロードURL取得', () => {
tier5Accounts.account.id, tier5Accounts.account.id,
url, url,
'test.zip', 'test.zip',
'InProgress', TASK_STATUS.IN_PROGRESS,
undefined, undefined,
authorId ?? '', 'AUTHOR_ID',
); );
const blobParam = makeBlobstorageServiceMockValue(); const blobParam = makeBlobstorageServiceMockValue();
@ -1946,15 +1913,21 @@ describe('テンプレートファイルダウンロードURL取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<FilesService>(FilesService); const service = module.get<FilesService>(FilesService);
await expect( try {
service.publishTemplateFileDownloadSas( await service.publishTemplateFileDownloadSas(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
audioFileId, audioFileId,
), ),
).rejects.toEqual( fail();
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST), } catch (e) {
); if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
}); });
}); });
@ -1985,7 +1958,7 @@ describe('publishTemplateFileUploadSas', () => {
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/Templates`; const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/Templates`;
//SASトークンを返却する //SASトークンを返却する
@ -2010,7 +1983,7 @@ describe('publishTemplateFileUploadSas', () => {
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//Blobコンテナ存在チェックに失敗するようにする //Blobコンテナ存在チェックに失敗するようにする
overrideBlobstorageService(service, { overrideBlobstorageService(service, {
@ -2038,7 +2011,7 @@ describe('publishTemplateFileUploadSas', () => {
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//BlobのSASトークン生成に失敗するようにする //BlobのSASトークン生成に失敗するようにする
overrideBlobstorageService(service, { overrideBlobstorageService(service, {
@ -2087,7 +2060,7 @@ describe('templateUploadFinished', () => {
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 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const fileName = 'test.docs'; const fileName = 'test.docs';
const url = `https://blob.url/account-${account.id}/Templates`; const url = `https://blob.url/account-${account.id}/Templates`;
@ -2121,7 +2094,7 @@ describe('templateUploadFinished', () => {
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 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const fileName = 'test.docs'; const fileName = 'test.docs';
const url = `https://blob.url/account-${account.id}/Templates`; const url = `https://blob.url/account-${account.id}/Templates`;
@ -2161,7 +2134,7 @@ describe('templateUploadFinished', () => {
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 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const fileName = 'test.docs'; const fileName = 'test.docs';
const url = `https://blob.url/account-${account.id}/Templates`; const url = `https://blob.url/account-${account.id}/Templates`;

View File

@ -166,7 +166,7 @@ export class FilesService {
let user: User; let user: User;
try { try {
// ユーザー取得 // ユーザー取得
user = await this.usersRepository.findUserByExternalId(userId); user = await this.usersRepository.findUserByExternalId(context, userId);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log( this.logger.log(
@ -190,6 +190,7 @@ export class FilesService {
// 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加) // 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加)
// 追加時に末尾のJOBナンバーにインクリメントする // 追加時に末尾のJOBナンバーにインクリメントする
task = await this.tasksRepositoryService.create( task = await this.tasksRepositoryService.create(
context,
user.account_id, user.account_id,
user.id, user.id,
priority, priority,
@ -218,6 +219,7 @@ export class FilesService {
// ルーティング設定に従い、チェックアウト権限を付与する // ルーティング設定に従い、チェックアウト権限を付与する
const { typistGroupIds, typistIds } = const { typistGroupIds, typistIds } =
await this.tasksRepositoryService.autoRouting( await this.tasksRepositoryService.autoRouting(
context,
task.audio_file_id, task.audio_file_id,
user.account_id, user.account_id,
user.author_id ?? undefined, user.author_id ?? undefined,
@ -225,6 +227,7 @@ export class FilesService {
const groupMembers = const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
context,
typistGroupIds, typistGroupIds,
); );
@ -284,7 +287,10 @@ export class FilesService {
//DBから国情報とアカウントIDを取得する //DBから国情報とアカウントIDを取得する
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); const user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
if (!user.account) { if (!user.account) {
throw new AccountNotFoundError('account not found.'); throw new AccountNotFoundError('account not found.');
} }
@ -299,6 +305,7 @@ export class FilesService {
// ライセンスが有効でない場合、エラー // ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState( const { state } = await this.licensesRepository.getLicenseState(
context,
user.id, user.id,
); );
if (state === 'expired') { if (state === 'expired') {
@ -378,7 +385,10 @@ export class FilesService {
let authorId: string | undefined; let authorId: string | undefined;
let isAdmin: boolean; let isAdmin: boolean;
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); const user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
if (!user.account) { if (!user.account) {
throw new AccountNotFoundError('account not found.'); throw new AccountNotFoundError('account not found.');
} }
@ -386,6 +396,7 @@ export class FilesService {
if (user.account.tier === TIERS.TIER5) { if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー // ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState( const { state } = await this.licensesRepository.getLicenseState(
context,
user.id, user.id,
); );
if (state === 'expired') { if (state === 'expired') {
@ -437,6 +448,7 @@ export class FilesService {
: Object.values(TASK_STATUS); : Object.values(TASK_STATUS);
const task = await this.tasksRepository.getTaskAndAudioFile( const task = await this.tasksRepository.getTaskAndAudioFile(
context,
audioFileId, audioFileId,
accountId, accountId,
status, status,
@ -462,7 +474,7 @@ export class FilesService {
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー // ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
if (isTypist && task.typist_user_id !== userId) { if (isTypist && task.typist_user_id !== userId) {
throw new AuthorUserNotMatchError( throw new TypistUserNotFoundError(
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`, `task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
); );
} }
@ -551,10 +563,11 @@ export class FilesService {
let accountId: number; let accountId: number;
let userId: number; let userId: number;
let country: string; let country: string;
let isTypist: boolean;
let authorId: string | undefined;
try { try {
const user = await this.usersRepository.findUserByExternalId(externalId); const user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
if (!user.account) { if (!user.account) {
throw new AccountNotFoundError('account not found.'); throw new AccountNotFoundError('account not found.');
} }
@ -562,6 +575,7 @@ export class FilesService {
if (user.account.tier === TIERS.TIER5) { if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー // ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState( const { state } = await this.licensesRepository.getLicenseState(
context,
user.id, user.id,
); );
if (state === 'expired') { if (state === 'expired') {
@ -574,8 +588,6 @@ export class FilesService {
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;
authorId = user.author_id ?? undefined;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log( this.logger.log(
@ -603,26 +615,13 @@ export class FilesService {
} }
try { try {
const status = isTypist
? [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING]
: Object.values(TASK_STATUS);
const task = await this.tasksRepository.getTaskAndAudioFile( const task = await this.tasksRepository.getTaskAndAudioFile(
context,
audioFileId, audioFileId,
accountId, accountId,
status, [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING],
); );
const { file } = task; const { template_file } = task;
// タスクに紐づく音声ファイルだけが消される場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う
if (!file) {
throw new AudioFileNotFoundError(
`Audio file is not exists in DB. audio_file_id:${audioFileId}`,
);
}
const template_file = task.template_file;
// タスクに紐づくテンプレートファイルがない場合がある。 // タスクに紐づくテンプレートファイルがない場合がある。
// その場合はダウンロード不可なので不在エラーとして扱う // その場合はダウンロード不可なので不在エラーとして扱う
@ -632,16 +631,9 @@ export class FilesService {
); );
} }
// ユーザーがAuthorの場合、自身が追加したタスクでない場合はエラー // ユーザー自身が担当したタスクでない場合はエラー
if (!isTypist && file.author_id !== authorId) { if (task.typist_user_id !== userId) {
throw new AuthorUserNotMatchError( throw new TypistUserNotFoundError(
`task author is not match. audio_file_id:${audioFileId}, task.file.author_id:${file.author_id}, authorId:${authorId}`,
);
}
// ユーザーがTypistの場合、自身が担当したタスクでない場合はエラー
if (isTypist && task.typist_user_id !== userId) {
throw new AuthorUserNotMatchError(
`task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`, `task typist is not match. audio_file_id:${audioFileId}, task.typist_user_id:${task.typist_user_id}, userId:${userId}`,
); );
} }
@ -676,7 +668,6 @@ export class FilesService {
case TasksNotFoundError: case TasksNotFoundError:
case AccountNotMatchError: case AccountNotMatchError:
case StatusNotMatchError: case StatusNotMatchError:
case AuthorUserNotMatchError:
case TypistUserNotFoundError: case TypistUserNotFoundError:
throw new HttpException( throw new HttpException(
makeErrorResponse('E010603'), makeErrorResponse('E010603'),
@ -726,6 +717,7 @@ export class FilesService {
); );
try { try {
const { account } = await this.usersRepository.findUserByExternalId( const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
if (!account) { if (!account) {
@ -788,7 +780,7 @@ export class FilesService {
try { try {
// ユーザー取得 // ユーザー取得
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// URLにSASトークンがついている場合は取り除く; // URLにSASトークンがついている場合は取り除く;
const urlObj = new URL(url); const urlObj = new URL(url);
@ -800,6 +792,7 @@ export class FilesService {
// テンプレートファイル情報をDBに登録 // テンプレートファイル情報をDBに登録
await this.templateFilesRepository.upsertTemplateFile( await this.templateFilesRepository.upsertTemplateFile(
context,
accountId, accountId,
fileName, fileName,
fileUrl, fileUrl,

View File

@ -1,4 +1,17 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
ArrayMaxSize,
ArrayMinSize,
IsArray,
IsIn,
IsInt,
IsNotEmpty,
IsNumberString,
MaxLength,
Min,
MinLength,
} from 'class-validator';
export class AudioUploadLocationRequest {} export class AudioUploadLocationRequest {}
@ -11,6 +24,9 @@ export class AudioUploadLocationResponse {
export class AudioDownloadLocationRequest { export class AudioDownloadLocationRequest {
@ApiProperty({ description: 'ODMSCloud上で管理する音声ファイルのID' }) @ApiProperty({ description: 'ODMSCloud上で管理する音声ファイルのID' })
@Type(() => Number)
@Min(1)
@IsInt()
audioFileId: number; audioFileId: number;
} }
@ -23,6 +39,9 @@ export class AudioDownloadLocationResponse {
export class TemplateDownloadLocationRequest { export class TemplateDownloadLocationRequest {
@ApiProperty({ description: '文字起こし対象の音声ファイルID' }) @ApiProperty({ description: '文字起こし対象の音声ファイルID' })
@Type(() => Number)
@Min(1)
@IsInt()
audioFileId: number; audioFileId: number;
} }
@ -38,40 +57,58 @@ export class TemplateUploadLocationResponse {
export class AudioOptionItem { export class AudioOptionItem {
@ApiProperty({ minLength: 1, maxLength: 16 }) @ApiProperty({ minLength: 1, maxLength: 16 })
@MinLength(1)
@MaxLength(16)
optionItemLabel: string; optionItemLabel: string;
@ApiProperty({ minLength: 1, maxLength: 20 }) @ApiProperty({ minLength: 1, maxLength: 20 })
@MinLength(1)
@MaxLength(20)
optionItemValue: string; optionItemValue: string;
} }
export class AudioUploadFinishedRequest { export class AudioUploadFinishedRequest {
@ApiProperty({ description: 'アップロード先Blob Storage(ファイル名含む)' }) @ApiProperty({ description: 'アップロード先Blob Storage(ファイル名含む)' })
@IsNotEmpty()
url: string; url: string;
@ApiProperty({ description: '自分自身(ログイン認証)したAuthorID' }) @ApiProperty({ description: '自分自身(ログイン認証)したAuthorID' })
@IsNotEmpty()
authorId: string; authorId: string;
@ApiProperty({ description: '音声ファイル名' }) @ApiProperty({ description: '音声ファイル名' })
@IsNotEmpty()
fileName: string; fileName: string;
@ApiProperty({ @ApiProperty({
description: '音声ファイルの録音時間(ミリ秒の整数値)', description: '音声ファイルの録音時間(ミリ秒の整数値)',
}) })
@IsNumberString()
@IsNotEmpty()
duration: string; duration: string;
@ApiProperty({ @ApiProperty({
description: description:
'音声ファイルの録音作成日時(開始日時)yyyy-mm-ddThh:mm:ss.sss', '音声ファイルの録音作成日時(開始日時)yyyy-mm-ddThh:mm:ss.sss',
}) })
@IsNotEmpty()
createdDate: string; createdDate: string;
@ApiProperty({ @ApiProperty({
description: '音声ファイルの録音作成終了日時yyyy-mm-ddThh:mm:ss.sss', description: '音声ファイルの録音作成終了日時yyyy-mm-ddThh:mm:ss.sss',
}) })
@IsNotEmpty()
finishedDate: string; finishedDate: string;
@ApiProperty({ @ApiProperty({
description: '音声ファイルのアップロード日時yyyy-mm-ddThh:mm:ss.sss', description: '音声ファイルのアップロード日時yyyy-mm-ddThh:mm:ss.sss',
}) })
@IsNotEmpty()
uploadedDate: string; uploadedDate: string;
@ApiProperty({ description: '音声ファイルのファイルサイズByte' }) @ApiProperty({ description: '音声ファイルのファイルサイズByte' })
@Type(() => Number)
@IsInt()
@IsNotEmpty()
fileSize: number; fileSize: number;
@ApiProperty({ description: '優先度 "00":Normal / "01":High' }) @ApiProperty({ description: '優先度 "00":Normal / "01":High' })
@IsIn(['00', '01'], { message: 'invalid priority' })
@IsNotEmpty()
priority: string; priority: string;
@ApiProperty({ description: '録音形式: DSS/DS2(SP)/DS2(QP)' }) @ApiProperty({ description: '録音形式: DSS/DS2(SP)/DS2(QP)' })
@IsNotEmpty()
audioFormat: string; audioFormat: string;
@ApiProperty() @ApiProperty()
comment: string; comment: string;
@ -83,6 +120,9 @@ export class AudioUploadFinishedRequest {
minItems: 10, minItems: 10,
description: '音声ファイルに紐づくOption Itemの一覧10個固定', description: '音声ファイルに紐づくOption Itemの一覧10個固定',
}) })
@IsArray()
@ArrayMinSize(10)
@ArrayMaxSize(10)
optionItemList: AudioOptionItem[]; optionItemList: AudioOptionItem[];
@ApiProperty() @ApiProperty()
isEncrypted: boolean; isEncrypted: boolean;

View File

@ -4,6 +4,7 @@ import {
Get, Get,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Post, Post,
Req, Req,
UseGuards, UseGuards,
@ -34,12 +35,13 @@ import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards'; 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, retrieveRequestId, retrieveIp } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('licenses') @ApiTags('licenses')
@Controller('licenses') @Controller('licenses')
export class LicensesController { export class LicensesController {
private readonly logger = new Logger(LicensesController.name);
constructor(private readonly licensesService: LicensesService) {} constructor(private readonly licensesService: LicensesService) {}
@ApiResponse({ @ApiResponse({
status: HttpStatus.OK, status: HttpStatus.OK,
@ -83,6 +85,22 @@ export class LicensesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -91,7 +109,9 @@ export class LicensesController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
// ライセンス注文処理 // ライセンス注文処理
await this.licensesService.licenseOrders( await this.licensesService.licenseOrders(
@ -136,6 +156,22 @@ export class LicensesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -144,7 +180,9 @@ export class LicensesController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys( const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
context, context,
@ -198,6 +236,22 @@ export class LicensesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -206,7 +260,9 @@ export class LicensesController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.licensesService.activateCardLicenseKey( await this.licensesService.activateCardLicenseKey(
context, context,
@ -257,6 +313,22 @@ export class LicensesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -266,7 +338,8 @@ export class LicensesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const allocatableLicenses = const allocatableLicenses =
await this.licensesService.getAllocatableLicenses(context, userId); await this.licensesService.getAllocatableLicenses(context, userId);
@ -319,6 +392,22 @@ export class LicensesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -328,7 +417,8 @@ export class LicensesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.licensesService.cancelOrder(context, userId, body.poNumber); await this.licensesService.cancelOrder(context, userId, body.poNumber);
return {}; return {};

View File

@ -4,12 +4,16 @@ import { LicensesService } from './licenses.service';
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module'; import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module'; import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
@Module({ @Module({
imports: [ imports: [
UsersRepositoryModule, UsersRepositoryModule,
AccountsRepositoryModule, AccountsRepositoryModule,
LicensesRepositoryModule, LicensesRepositoryModule,
AdB2cModule,
SendGridModule,
], ],
controllers: [LicensesController], controllers: [LicensesController],
providers: [LicensesService], providers: [LicensesService],

View File

@ -1,24 +1,6 @@
import { AccessToken } from '../../common/token'; import { NewAllocatedLicenseExpirationDate } from './types/types';
import {
CreateOrdersRequest,
IssueCardLicensesRequest,
IssueCardLicensesResponse,
ActivateCardLicensesRequest,
NewAllocatedLicenseExpirationDate,
} from './types/types';
import {
makeDefaultAccountsRepositoryMockValue,
makeDefaultLicensesRepositoryMockValue,
makeDefaultUsersRepositoryMockValue,
makeLicensesServiceMock,
} from './test/liscense.service.mock';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { HttpException, HttpStatus } from '@nestjs/common'; import { HttpException, HttpStatus } from '@nestjs/common';
import {
PoNumberAlreadyExistError,
LicenseKeyAlreadyActivatedError,
LicenseNotExistError,
} from '../../repositories/licenses/errors/types';
import { LicensesService } from './licenses.service'; import { LicensesService } from './licenses.service';
import { makeTestingModule } from '../../common/test/modules'; import { makeTestingModule } from '../../common/test/modules';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
@ -42,273 +24,171 @@ import {
makeTestSimpleAccount, makeTestSimpleAccount,
makeTestUser, makeTestUser,
} from '../../common/test/utility'; } from '../../common/test/utility';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { overrideSendgridService } from '../../common/test/overrides';
describe('ライセンス注文', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
describe('LicensesService', () => {
it('ライセンス注文が完了する', async () => { it('ライセンス注文が完了する', async () => {
const lisencesRepositoryMockValue = if (!source) fail();
makeDefaultLicensesRepositoryMockValue(); const module = await makeTestingModule(source);
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); if (!module) fail();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue(); const { id: accountId, parent_account_id: parentAccountId } =
const service = await makeLicensesServiceMock( await makeTestSimpleAccount(source, {
lisencesRepositoryMockValue, parent_account_id: 2,
usersRepositoryMockValue, });
accountsRepositoryMockValue, const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
});
const poNumber = 'test1';
const orderCount = 100;
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
await service.licenseOrders(context, externalId, poNumber, orderCount);
const dbSelectResult = await selectOrderLicense(
source,
accountId,
poNumber,
); );
const body = new CreateOrdersRequest();
const userId = '0001'; expect(dbSelectResult.orderLicense?.po_number).toEqual(poNumber);
body.orderCount = 1000; expect(dbSelectResult.orderLicense?.quantity).toEqual(orderCount);
body.poNumber = '1'; expect(dbSelectResult.orderLicense?.from_account_id).toEqual(accountId);
const context = makeContext(`uuidv4`); expect(dbSelectResult.orderLicense?.to_account_id).toEqual(parentAccountId);
expect( expect(dbSelectResult.orderLicense?.status).toEqual('Issue Requesting');
});
it('POナンバー重複時、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source, {
parent_account_id: 2,
});
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
});
const poNumber = 'test1';
const orderCount = 100;
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
try {
await service.licenseOrders(context, externalId, poNumber, orderCount);
await service.licenseOrders(context, externalId, poNumber, orderCount);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010401'));
} else {
fail();
}
}
});
it('ユーザID取得できなかった場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source, {
parent_account_id: 2,
});
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'useId',
role: 'admin',
});
const poNumber = 'test1';
const orderCount = 100;
// 存在しないのユーザーIDを作成
const notExistUserId = 'notExistId';
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
try {
await service.licenseOrders( await service.licenseOrders(
context, context,
userId, notExistUserId,
body.poNumber, poNumber,
body.orderCount, orderCount,
), );
).toEqual(undefined); } catch (e) {
}); if (e instanceof HttpException) {
it('ユーザID取得できなかった場合、エラーとなる', async () => { expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
const lisencesRepositoryMockValue = expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
makeDefaultLicensesRepositoryMockValue(); } else {
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); fail();
usersRepositoryMockValue.findUserByExternalId = new Error( }
'User not Found Error.', }
);
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const userId = '';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
}); });
it('親ユーザID取得できなかった場合、エラーとなる', async () => { it('親ユーザID取得できなかった場合、エラーとなる', async () => {
const lisencesRepositoryMockValue = if (!source) fail();
makeDefaultLicensesRepositoryMockValue(); const module = await makeTestingModule(source);
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); if (!module) fail();
usersRepositoryMockValue.findUserByExternalId = new Error(
'Account not Found Error.', const { id: accountId } = await makeTestSimpleAccount(source, {
); parent_account_id: undefined,
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
}); });
it('POナンバー重複時、エラーとなる', async () => { const { external_id: externalId } = await makeTestUser(source, {
const lisencesRepositoryMockValue = account_id: accountId,
makeDefaultLicensesRepositoryMockValue(); external_id: 'userId',
lisencesRepositoryMockValue.order = new PoNumberAlreadyExistError( role: 'admin',
`This PoNumber already used`,
);
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new CreateOrdersRequest();
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E010401'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
}); });
it('カードライセンス発行が完了する', async () => {
const lisencesRepositoryMockValue = const poNumber = 'test1';
makeDefaultLicensesRepositoryMockValue(); const orderCount = 100;
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue = const service = module.get<LicensesService>(LicensesService);
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock( const context = makeContext(`uuidv4`, 'requestId');
lisencesRepositoryMockValue, try {
usersRepositoryMockValue, await service.licenseOrders(context, externalId, poNumber, orderCount);
accountsRepositoryMockValue, } catch (e) {
); if (e instanceof HttpException) {
const body = new IssueCardLicensesRequest(); expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
const userId = '0001'; expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
body.createCount = 10; } else {
const issueCardLicensesResponse: IssueCardLicensesResponse = { fail();
cardLicenseKeys: [ }
'WZCETXC0Z9PQZ9GKRGGY', }
'F0JD7EZEDBH4PQRQ83YF',
'H0HXBP5K9RW7T7JSVDJV',
'HKIWX54EESYL4X132223',
'363E81JR460UBHXGFXFI',
'70IKAPV9K6YMEVLTOXBY',
'1RJY1TRRYYTGF1LL9WLU',
'BXM0HKFO7IULTL0A1B36',
'XYLEWNY2LR6Q657CZE41',
'AEJWRFFSWRQYQQJ6WVLV',
],
};
const context = makeContext(`uuidv4`);
expect(
await service.issueCardLicenseKeys(context, userId, body.createCount),
).toEqual(issueCardLicensesResponse);
});
it('カードライセンス発行に失敗した場合、エラーになる', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.createCardLicenses = new Error('DB failed');
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new IssueCardLicensesRequest();
const userId = '0001';
body.createCount = 1000;
const context = makeContext(`uuidv4`);
await expect(
service.issueCardLicenseKeys(context, userId, body.createCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('カードライセンス取り込みが完了する', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
expect(
await service.activateCardLicenseKey(
context,
userId,
body.cardLicenseKey,
),
).toEqual(undefined);
});
it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense = new Error('DB failed');
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが存在しないエラー)', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense = new LicenseNotExistError(
`License not exist`,
);
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
);
});
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが既に取り込まれているエラー)', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense =
new LicenseKeyAlreadyActivatedError(`License already activated`);
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
);
}); });
}); });
describe('DBテスト', () => { describe('カードライセンス発行', () => {
let source: DataSource | null = null; let source: DataSource | null = null;
beforeEach(async () => { beforeEach(async () => {
source = new DataSource({ source = new DataSource({
@ -342,12 +222,68 @@ describe('DBテスト', () => {
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
const issueCount = 500; const issueCount = 500;
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await service.issueCardLicenseKeys(context, externalId, issueCount); await service.issueCardLicenseKeys(context, externalId, issueCount);
const dbSelectResult = await selectCardLicensesCount(source); const dbSelectResult = await selectCardLicensesCount(source);
expect(dbSelectResult.count).toEqual(issueCount); expect(dbSelectResult.count).toEqual(issueCount);
}); });
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
author_id: undefined,
});
const service = module.get<LicensesService>(LicensesService);
const issueCount = 500;
const context = makeContext(`uuidv4`, 'requestId');
//DBアクセスに失敗するようにする
const licensesService = module.get<LicensesRepositoryService>(
LicensesRepositoryService,
);
licensesService.createCardLicenses = jest
.fn()
.mockRejectedValue('DB failed');
try {
await service.issueCardLicenseKeys(context, externalId, issueCount);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});
describe('カードライセンスを取り込む', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('カードライセンス取り込みが完了する', async () => { it('カードライセンス取り込みが完了する', async () => {
if (!source) fail(); if (!source) fail();
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
@ -382,7 +318,7 @@ describe('DBテスト', () => {
await createCardLicenseIssue(source, issueId); await createCardLicenseIssue(source, issueId);
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await service.activateCardLicenseKey(context, externalId, cardLicenseKey); await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
const dbSelectResultFromCardLicense = await selectCardLicense( const dbSelectResultFromCardLicense = await selectCardLicense(
@ -529,7 +465,7 @@ describe('DBテスト', () => {
null, null,
); );
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
const context = makeContext('userId'); const context = makeContext('userId', 'requestId');
const response = await service.getAllocatableLicenses(context, externalId); const response = await service.getAllocatableLicenses(context, externalId);
// 対象外のデータは取得していないことを確認する // 対象外のデータは取得していないことを確認する
expect(response.allocatableLicenses.length).toBe(5); expect(response.allocatableLicenses.length).toBe(5);
@ -541,6 +477,126 @@ describe('DBテスト', () => {
expect(response.allocatableLicenses[3].licenseId).toBe(1); expect(response.allocatableLicenses[3].licenseId).toBe(1);
expect(response.allocatableLicenses[4].licenseId).toBe(3); expect(response.allocatableLicenses[4].licenseId).toBe(3);
}); });
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが存在しないエラー)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
author_id: undefined,
});
//存在しないライセンスキーを作成
const notExistCardLicenseKey = 'XXCETXC0Z9PQZ9GKRGGY';
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
try {
await service.activateCardLicenseKey(
context,
externalId,
notExistCardLicenseKey,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010801'));
} else {
fail();
}
}
});
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが既に取り込まれているエラー)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
author_id: undefined,
});
const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const defaultAccountId = 150;
const license_id = 50;
const issueId = 100;
await createLicense(
source,
license_id,
null,
defaultAccountId,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null,
);
await createCardLicense(source, license_id, issueId, cardLicenseKey);
await createCardLicenseIssue(source, issueId);
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
try {
await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010802'));
} else {
fail();
}
}
});
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { external_id: externalId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'userId',
role: 'admin',
author_id: undefined,
});
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`, 'requestId');
const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
//DBアクセスに失敗するようにする
const licensesService = module.get<LicensesRepositoryService>(
LicensesRepositoryService,
);
licensesService.activateCardLicense = jest
.fn()
.mockRejectedValue('DB failed');
try {
await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
}); });
describe('ライセンス割り当て', () => { describe('ライセンス割り当て', () => {
@ -596,10 +652,15 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
const expiry_date = new NewAllocatedLicenseExpirationDate(); const expiry_date = new NewAllocatedLicenseExpirationDate();
await service.allocateLicense(makeContext('trackingId'), userId, 1); await service.allocateLicense(
makeContext('trackingId', 'requestId'),
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(
@ -663,8 +724,13 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
await service.allocateLicense(makeContext('trackingId'), userId, 1); await service.allocateLicense(
makeContext('trackingId', 'requestId'),
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);
@ -736,10 +802,14 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
const expiry_date = new NewAllocatedLicenseExpirationDate(); const expiry_date = new NewAllocatedLicenseExpirationDate();
await service.allocateLicense(makeContext('trackingId'), userId, 2); await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
2,
);
// もともと割り当てられていたライセンスの状態確認 // もともと割り当てられていたライセンスの状態確認
const result1 = await selectLicense(source, 1); const result1 = await selectLicense(source, 1);
@ -838,7 +908,12 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2); overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
2,
);
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
source, source,
@ -898,7 +973,12 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2); overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
2,
);
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
source, source,
@ -958,7 +1038,12 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.allocateLicense(makeContext('trackingId'), userId, 2); overrideSendgridService(service, {});
await service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
2,
);
const licenseAllocationHistory = await selectLicenseAllocationHistory( const licenseAllocationHistory = await selectLicenseAllocationHistory(
source, source,
@ -998,9 +1083,14 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
await expect( await expect(
service.allocateLicense(makeContext('trackingId'), userId, 1), service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
1,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
); );
@ -1046,14 +1136,23 @@ describe('ライセンス割り当て', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
await expect( await expect(
service.allocateLicense(makeContext('trackingId'), userId, 1), service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
1,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
); );
await expect( await expect(
service.allocateLicense(makeContext('trackingId'), userId, 2), service.allocateLicense(
makeContext('trackingId', 'requestId'),
userId,
2,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST),
); );
@ -1115,7 +1214,11 @@ describe('ライセンス割り当て解除', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.deallocateLicense(makeContext('trackingId'), userId); overrideSendgridService(service, {});
await service.deallocateLicense(
makeContext('trackingId', 'requestId'),
userId,
);
// 割り当て解除したライセンスの状態確認 // 割り当て解除したライセンスの状態確認
const deallocatedLicense = await selectLicense(source, 1); const deallocatedLicense = await selectLicense(source, 1);
@ -1202,8 +1305,9 @@ describe('ライセンス割り当て解除', () => {
); );
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
await expect( await expect(
service.deallocateLicense(makeContext('trackingId'), userId), service.deallocateLicense(makeContext('trackingId', 'requestId'), userId),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST),
); );
@ -1258,8 +1362,9 @@ describe('ライセンス注文キャンセル', () => {
); );
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {});
await service.cancelOrder( await service.cancelOrder(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
tier2Accounts[0].users[0].external_id, tier2Accounts[0].users[0].external_id,
poNumber, poNumber,
); );
@ -1293,9 +1398,10 @@ describe('ライセンス注文キャンセル', () => {
); );
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {});
await expect( await expect(
service.cancelOrder( service.cancelOrder(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
tier2Accounts[0].users[0].external_id, tier2Accounts[0].users[0].external_id,
poNumber, poNumber,
), ),
@ -1324,9 +1430,10 @@ describe('ライセンス注文キャンセル', () => {
); );
const service = module.get<LicensesService>(LicensesService); const service = module.get<LicensesService>(LicensesService);
overrideSendgridService(service, {});
await expect( await expect(
service.cancelOrder( service.cancelOrder(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
tier2Accounts[0].users[0].external_id, tier2Accounts[0].users[0].external_id,
poNumber, poNumber,
), ),

View File

@ -16,6 +16,10 @@ import {
IssueCardLicensesResponse, IssueCardLicensesResponse,
} from './types/types'; } from './types/types';
import { Context } from '../../common/log'; import { Context } from '../../common/log';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { LICENSE_ISSUE_STATUS } from '../../constants';
@Injectable() @Injectable()
export class LicensesService { export class LicensesService {
@ -23,6 +27,8 @@ export class LicensesService {
private readonly usersRepository: UsersRepositoryService, private readonly usersRepository: UsersRepositoryService,
private readonly accountsRepository: AccountsRepositoryService, private readonly accountsRepository: AccountsRepositoryService,
private readonly licensesRepository: LicensesRepositoryService, private readonly licensesRepository: LicensesRepositoryService,
private readonly adb2cService: AdB2cService,
private readonly sendgridService: SendGridService,
) {} ) {}
private readonly logger = new Logger(LicensesService.name); private readonly logger = new Logger(LicensesService.name);
@ -49,7 +55,7 @@ export class LicensesService {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
try { try {
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(externalId) await this.usersRepository.findUserByExternalId(context, externalId)
).account_id; ).account_id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -70,7 +76,7 @@ export class LicensesService {
// 親アカウントIDを取得 // 親アカウントIDを取得
try { try {
parentAccountId = parentAccountId =
(await this.accountsRepository.findAccountById(myAccountId)) (await this.accountsRepository.findAccountById(context, myAccountId))
.parent_account_id ?? undefined; .parent_account_id ?? undefined;
// 親アカウントIDが取得できない場合はエラー // 親アカウントIDが取得できない場合はエラー
if (parentAccountId === undefined) { if (parentAccountId === undefined) {
@ -94,11 +100,34 @@ export class LicensesService {
try { try {
await this.licensesRepository.order( await this.licensesRepository.order(
context,
poNumber, poNumber,
myAccountId, myAccountId,
parentAccountId, parentAccountId,
orderCount, orderCount,
); );
// メール送信処理
try {
const customer = await this.getAccountInformation(context, myAccountId);
const dealer = await this.getAccountInformation(
context,
parentAccountId,
);
this.sendgridService.sendMailWithU105(
context,
customer.adminEmails,
customer.companyName,
orderCount,
poNumber,
dealer.adminEmails,
dealer.companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
switch (e.constructor) { switch (e.constructor) {
case PoNumberAlreadyExistError: case PoNumberAlreadyExistError:
@ -134,7 +163,7 @@ export class LicensesService {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
try { try {
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(externalId) await this.usersRepository.findUserByExternalId(context, externalId)
).account_id; ).account_id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -153,6 +182,7 @@ export class LicensesService {
} }
try { try {
const licenseKeys = await this.licensesRepository.createCardLicenses( const licenseKeys = await this.licensesRepository.createCardLicenses(
context,
myAccountId, myAccountId,
createCount, createCount,
); );
@ -194,7 +224,7 @@ export class LicensesService {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
try { try {
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(externalId) await this.usersRepository.findUserByExternalId(context, externalId)
).account_id; ).account_id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -215,6 +245,7 @@ export class LicensesService {
// カードライセンスを取り込む // カードライセンスを取り込む
try { try {
await this.licensesRepository.activateCardLicense( await this.licensesRepository.activateCardLicense(
context,
myAccountId, myAccountId,
cardLicenseKey, cardLicenseKey,
); );
@ -266,11 +297,14 @@ export class LicensesService {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
try { try {
const myAccountId = ( const myAccountId = (
await this.usersRepository.findUserByExternalId(userId) await this.usersRepository.findUserByExternalId(context, userId)
).account_id; ).account_id;
// 割り当て可能なライセンスを取得する // 割り当て可能なライセンスを取得する
const allocatableLicenses = const allocatableLicenses =
await this.licensesRepository.getAllocatableLicenses(myAccountId); await this.licensesRepository.getAllocatableLicenses(
context,
myAccountId,
);
return { return {
allocatableLicenses, allocatableLicenses,
@ -316,10 +350,78 @@ export class LicensesService {
try { try {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
myAccountId = ( myAccountId = (
await this.usersRepository.findUserByExternalId(externalId) await this.usersRepository.findUserByExternalId(context, externalId)
).account_id; ).account_id;
// 注文キャンセル処理 // 注文キャンセル処理
await this.licensesRepository.cancelOrder(myAccountId, poNumber); const { canceledOrderId } = await this.licensesRepository.cancelOrder(
context,
myAccountId,
poNumber,
);
// メール送信処理
try {
// 注文キャンセルを実行したカスタマー企業の管理者情報を取得する
const customerAccountId = myAccountId;
// カスタマー企業の企業名と管理者情報を取得する
const {
companyName: customerCompanyName,
adminEmails: customerAdminEmails,
} = await this.getAccountInformation(context, customerAccountId);
// ディーラー企業を特定する
const { parent_account_id: dealerAccountId } =
await this.accountsRepository.findAccountById(
context,
customerAccountId,
);
// ライセンス発行が行われているので、ディーラーは必ず存在するはず
if (dealerAccountId == null) {
throw new Error('dealerAccountId is null');
}
// ディーラー企業の企業名と管理者情報を取得する
const {
companyName: dealerCompanyName,
adminEmails: dealerAdminEmails,
} = await this.getAccountInformation(context, dealerAccountId);
// キャンセル済みの注文をID指定して取得する
const order = await this.licensesRepository.getLicenseOrder(
context,
customerAccountId,
poNumber,
canceledOrderId,
);
if (order == null) {
throw new Error(
`cancel target order not found. fromAccountId: ${customerAccountId}, poNumber:${poNumber}`,
);
}
if (order.status !== LICENSE_ISSUE_STATUS.CANCELED) {
throw new Error(
`target order is not canceled. fromAccountId: ${order.from_account_id}, poNumber:${order.po_number}, status:${order.status}, id:${order.id}`,
);
}
const { quantity } = order;
// 注文キャンセルが成功した旨をメール送信する
await this.sendgridService.sendMailWithU106(
context,
customerAdminEmails,
customerCompanyName,
quantity,
poNumber,
dealerAdminEmails,
dealerCompanyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) { switch (e.constructor) {
@ -346,4 +448,51 @@ export class LicensesService {
} }
return; return;
} }
/**
* IDを指定して
* @param context
* @param accountId ID
* @returns /
*/
private async getAccountInformation(
context: Context,
accountId: number,
): Promise<{
companyName: string;
adminEmails: string[];
}> {
// アカウントIDから企業名を取得する
const { company_name } = await this.accountsRepository.findAccountById(
context,
accountId,
);
// 管理者一覧を取得
const admins = await this.usersRepository.findAdminUsers(
context,
accountId,
);
const adminExternalIDs = admins.map((x) => x.external_id);
// ADB2Cから管理者IDを元にメールアドレスを取得する
const usersInfo = await this.adb2cService.getUsers(
context,
adminExternalIDs,
);
// 生のAzure AD B2Cのユーザー情報からメールアドレスを抽出する
const adminEmails = usersInfo.map((x) => {
const { emailAddress } = getUserNameAndMailAddress(x);
if (emailAddress == null) {
throw new Error('dealer admin email-address is not found');
}
return emailAddress;
});
return {
companyName: company_name,
adminEmails: adminEmails,
};
}
} }

View File

@ -6,6 +6,7 @@ import {
LICENSE_EXPIRATION_TIME_WITH_TIMEZONE, LICENSE_EXPIRATION_TIME_WITH_TIMEZONE,
TRIAL_LICENSE_EXPIRATION_DAYS, TRIAL_LICENSE_EXPIRATION_DAYS,
} from '../../../constants'; } from '../../../constants';
import { Type } from 'class-transformer';
export class CreateOrdersRequest { export class CreateOrdersRequest {
@ApiProperty() @ApiProperty()
@ -13,6 +14,7 @@ export class CreateOrdersRequest {
poNumber: string; poNumber: string;
@ApiProperty() @ApiProperty()
@Type(() => Number)
@IsInt() @IsInt()
@Min(1) @Min(1)
@Max(9999) @Max(9999)
@ -23,6 +25,7 @@ export class CreateOrdersResponse {}
export class IssueCardLicensesRequest { export class IssueCardLicensesRequest {
@ApiProperty() @ApiProperty()
@Type(() => Number)
@IsInt() @IsInt()
@Min(1) @Min(1)
@Max(9999) @Max(9999)

View File

@ -3,6 +3,7 @@ import {
Controller, Controller,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Post, Post,
Req, Req,
UseGuards, UseGuards,
@ -21,12 +22,13 @@ import { AuthGuard } from '../../common/guards/auth/authguards';
import { retrieveAuthorizationToken } from '../../common/http/helper'; import { retrieveAuthorizationToken } from '../../common/http/helper';
import { AccessToken } from '../../common/token'; import { AccessToken } from '../../common/token';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { makeContext } from '../../common/log'; import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('notification') @ApiTags('notification')
@Controller('notification') @Controller('notification')
export class NotificationController { export class NotificationController {
private readonly logger = new Logger(NotificationController.name);
constructor(private readonly notificationService: NotificationService) {} constructor(private readonly notificationService: NotificationService) {}
@Post('register') @Post('register')
@ApiResponse({ @ApiResponse({
@ -65,6 +67,22 @@ export class NotificationController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -74,7 +92,8 @@ export class NotificationController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.notificationService.register(context, userId, pns, handler); await this.notificationService.register(context, userId, pns, handler);
return {}; return {};

View File

@ -19,7 +19,7 @@ describe('NotificationService.register', () => {
expect( expect(
await service.register( await service.register(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
'external_id', 'external_id',
'apns', 'apns',
'handler', 'handler',
@ -38,7 +38,7 @@ describe('NotificationService.register', () => {
await expect( await expect(
service.register( service.register(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
'external_id', 'external_id',
'apns', 'apns',
'handler', 'handler',
@ -63,7 +63,7 @@ describe('NotificationService.register', () => {
await expect( await expect(
service.register( service.register(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
'external_id', 'external_id',
'apns', 'apns',
'handler', 'handler',

View File

@ -34,7 +34,9 @@ export class NotificationService {
// ユーザIDからアカウントIDを取得する // ユーザIDからアカウントIDを取得する
let userId: number; let userId: number;
try { try {
userId = (await this.usersRepository.findUserByExternalId(externalId)).id; userId = (
await this.usersRepository.findUserByExternalId(context, externalId)
).id;
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) { switch (e.constructor) {

View File

@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsIn } from 'class-validator'; import { IsIn, IsNotEmpty, IsString } from 'class-validator';
import { PNS } from '../../../constants'; import { PNS } from '../../../constants';
export class RegisterRequest { export class RegisterRequest {
@ -9,6 +9,8 @@ export class RegisterRequest {
}) })
pns: string; pns: string;
@ApiProperty({ description: 'wnsのチャネルURI or apnsのデバイストークン' }) @ApiProperty({ description: 'wnsのチャネルURI or apnsのデバイストークン' })
@IsString()
@IsNotEmpty()
handler: string; handler: string;
} }

View File

@ -2,9 +2,9 @@ import {
Body, Body,
Controller, Controller,
Get, Get,
Headers,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Param, Param,
ParseIntPipe, ParseIntPipe,
Post, Post,
@ -45,12 +45,13 @@ import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards'; 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, retrieveRequestId, retrieveIp } from '../../common/log';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('tasks') @ApiTags('tasks')
@Controller('tasks') @Controller('tasks')
export class TasksController { export class TasksController {
private readonly logger = new Logger(TasksController.name);
constructor(private readonly taskService: TasksService) {} constructor(private readonly taskService: TasksService) {}
@ApiResponse({ @ApiResponse({
@ -91,6 +92,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -102,7 +120,8 @@ export class TasksController {
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const { limit, offset, status } = body; const { limit, offset, status } = body;
const paramName = isTaskListSortableAttribute(body.paramName ?? '') const paramName = isTaskListSortableAttribute(body.paramName ?? '')
@ -164,13 +183,29 @@ export class TasksController {
): Promise<AudioNextResponse> { ): Promise<AudioNextResponse> {
const { endedFileId } = param; const { endedFileId } = param;
const accessToken = retrieveAuthorizationToken(req) as string; const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000107'), makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -179,7 +214,8 @@ export class TasksController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const nextFileId = await this.taskService.getNextTask( const nextFileId = await this.taskService.getNextTask(
context, context,
@ -241,6 +277,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -253,7 +306,8 @@ export class TasksController {
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.checkout(context, param.audioFileId, roles, userId); await this.taskService.checkout(context, param.audioFileId, roles, userId);
return {}; return {};
@ -311,6 +365,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -320,7 +391,8 @@ export class TasksController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.checkin(context, audioFileId, userId); await this.taskService.checkin(context, audioFileId, userId);
return {}; return {};
@ -378,6 +450,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -389,7 +478,8 @@ export class TasksController {
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.cancel(context, audioFileId, userId, roles); await this.taskService.cancel(context, audioFileId, userId, roles);
return {}; return {};
@ -447,6 +537,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -456,7 +563,8 @@ export class TasksController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.suspend(context, audioFileId, userId); await this.taskService.suspend(context, audioFileId, userId);
return {}; return {};
@ -513,6 +621,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -522,7 +647,8 @@ export class TasksController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.backup(context, audioFileId, userId); await this.taskService.backup(context, audioFileId, userId);
return {}; return {};
@ -585,6 +711,23 @@ export class TasksController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -596,7 +739,8 @@ export class TasksController {
// RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う // RoleGuardでroleの文字列に想定外の文字列や重複がないことは担保されているためここでは型変換のみ行う
const roles = role.split(' ') as Roles[]; const roles = role.split(' ') as Roles[];
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.taskService.changeCheckoutPermission( await this.taskService.changeCheckoutPermission(
context, context,

View File

@ -6,14 +6,18 @@ import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module'; import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module'; import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module'; import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
@Module({ @Module({
imports: [ imports: [
AccountsRepositoryModule,
UsersRepositoryModule, UsersRepositoryModule,
UserGroupsRepositoryModule, UserGroupsRepositoryModule,
TasksRepositoryModule, TasksRepositoryModule,
AdB2cModule, AdB2cModule,
NotificationhubModule, NotificationhubModule,
SendGridModule,
], ],
providers: [TasksService], providers: [TasksService],
controllers: [TasksController], controllers: [TasksController],

View File

@ -63,7 +63,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
expect( expect(
await service.tasksService.getTasks( await service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -138,7 +138,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -180,7 +180,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -266,7 +266,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -309,8 +309,9 @@ describe('TasksService', () => {
const status = ['Uploaded', 'Backup']; const status = ['Uploaded', 'Backup'];
const paramName = 'JOB_NUMBER'; const paramName = 'JOB_NUMBER';
const direction = 'ASC'; const direction = 'ASC';
const context = makeContext('trackingId', 'requestId');
const result = await service.tasksService.getTasks( const result = await service.tasksService.getTasks(
makeContext('trackingId'), context,
userId, userId,
[USER_ROLES.AUTHOR], [USER_ROLES.AUTHOR],
offset, offset,
@ -360,7 +361,7 @@ describe('TasksService', () => {
}); });
expect( expect(
service.taskRepoService.getTasksFromAuthorIdAndAccountId, service.taskRepoService.getTasksFromAuthorIdAndAccountId,
).toHaveBeenCalledWith('abcdef', 1, 0, 20, 'JOB_NUMBER', 'ASC', [ ).toHaveBeenCalledWith(context, 'abcdef', 1, 0, 20, 'JOB_NUMBER', 'ASC', [
'Uploaded', 'Uploaded',
'Backup', 'Backup',
]); ]);
@ -393,7 +394,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[USER_ROLES.AUTHOR], [USER_ROLES.AUTHOR],
offset, offset,
@ -437,8 +438,9 @@ describe('TasksService', () => {
const status = ['Uploaded', 'Backup']; const status = ['Uploaded', 'Backup'];
const paramName = 'JOB_NUMBER'; const paramName = 'JOB_NUMBER';
const direction = 'ASC'; const direction = 'ASC';
const context = makeContext('trackingId', 'requestId');
const result = await service.tasksService.getTasks( const result = await service.tasksService.getTasks(
makeContext('trackingId'), context,
userId, userId,
[USER_ROLES.TYPIST], [USER_ROLES.TYPIST],
offset, offset,
@ -488,7 +490,7 @@ describe('TasksService', () => {
}); });
expect( expect(
service.taskRepoService.getTasksFromTypistRelations, service.taskRepoService.getTasksFromTypistRelations,
).toHaveBeenCalledWith('userId', 0, 20, 'JOB_NUMBER', 'ASC', [ ).toHaveBeenCalledWith(context, 'userId', 0, 20, 'JOB_NUMBER', 'ASC', [
'Uploaded', 'Uploaded',
'Backup', 'Backup',
]); ]);
@ -521,7 +523,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[USER_ROLES.TYPIST], [USER_ROLES.TYPIST],
offset, offset,
@ -563,7 +565,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
await expect( await expect(
service.tasksService.getTasks( service.tasksService.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
userId, userId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -623,7 +625,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
const { tasks, total } = await service.getTasks( const { tasks, total } = await service.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
externalId, externalId,
[ADMIN_ROLES.ADMIN, USER_ROLES.NONE], [ADMIN_ROLES.ADMIN, USER_ROLES.NONE],
offset, offset,
@ -681,7 +683,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
const { tasks, total } = await service.getTasks( const { tasks, total } = await service.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
external_id, external_id,
[USER_ROLES.AUTHOR], [USER_ROLES.AUTHOR],
offset, offset,
@ -753,7 +755,7 @@ describe('TasksService', () => {
const direction = 'ASC'; const direction = 'ASC';
const { tasks, total } = await service.getTasks( const { tasks, total } = await service.getTasks(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
external_id, external_id,
[USER_ROLES.AUTHOR], [USER_ROLES.AUTHOR],
offset, offset,
@ -839,7 +841,7 @@ describe('changeCheckoutPermission', () => {
NotificationhubService, NotificationhubService,
); );
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }], [{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }],
'author-user-external-id', 'author-user-external-id',
@ -856,7 +858,7 @@ describe('changeCheckoutPermission', () => {
const resultTask = await getTask(source, taskId); const resultTask = await getTask(source, taskId);
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId_2}`], [`user_${typistUserId_2}`],
{ {
authorId: 'MY_AUTHOR_ID', authorId: 'MY_AUTHOR_ID',
@ -922,7 +924,7 @@ describe('changeCheckoutPermission', () => {
NotificationhubService, NotificationhubService,
); );
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'USER_GROUP_B', typistGroupId: userGroupId_2 }], [{ typistName: 'USER_GROUP_B', typistGroupId: userGroupId_2 }],
'author-user-external-id', 'author-user-external-id',
@ -940,7 +942,7 @@ describe('changeCheckoutPermission', () => {
const resultTask = await getTask(source, taskId); const resultTask = await getTask(source, taskId);
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId_2}`], [`user_${typistUserId_2}`],
{ {
authorId: 'MY_AUTHOR_ID', authorId: 'MY_AUTHOR_ID',
@ -992,7 +994,7 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId); await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[], [],
'author-user-external-id', 'author-user-external-id',
@ -1045,7 +1047,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'not-exist-user', typistUserId: 999 }], [{ typistName: 'not-exist-user', typistUserId: 999 }],
'author-user-external-id', 'author-user-external-id',
@ -1111,7 +1113,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'not-verified-user', typistUserId: typistUserId_2 }], [{ typistName: 'not-verified-user', typistUserId: typistUserId_2 }],
'author-user-external-id', 'author-user-external-id',
@ -1171,7 +1173,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'not-exist-user-group', typistGroupId: 999 }], [{ typistName: 'not-exist-user-group', typistGroupId: 999 }],
'author-user-external-id', 'author-user-external-id',
@ -1213,7 +1215,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'typist-user', typistUserId: typistUserId }], [{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id', 'author-user-external-id',
@ -1265,7 +1267,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'typist-user', typistUserId: typistUserId }], [{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id', 'author-user-external-id',
@ -1317,7 +1319,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'typist-user', typistUserId: typistUserId }], [{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id', 'author-user-external-id',
@ -1383,7 +1385,7 @@ describe('changeCheckoutPermission', () => {
try { try {
await service.changeCheckoutPermission( await service.changeCheckoutPermission(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
[{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }], [{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }],
'author-user-external-id', 'author-user-external-id',
@ -1460,7 +1462,7 @@ describe('checkout', () => {
const initTask = await getTask(source, taskId); const initTask = await getTask(source, taskId);
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1520,7 +1522,7 @@ describe('checkout', () => {
const initTask = await getTask(source, taskId); const initTask = await getTask(source, taskId);
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1573,7 +1575,7 @@ describe('checkout', () => {
const initTask = await getTask(source, taskId); const initTask = await getTask(source, taskId);
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1625,7 +1627,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1672,7 +1674,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1733,7 +1735,7 @@ describe('checkout', () => {
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
audioFileId, audioFileId,
['typist'], ['typist'],
'typist-user-external-id', 'typist-user-external-id',
@ -1798,7 +1800,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
2, 2,
['typist'], ['typist'],
'typist-user-external-id2', 'typist-user-external-id2',
@ -1839,7 +1841,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
expect( expect(
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['author'], ['author'],
'author-user-external-id', 'author-user-external-id',
@ -1873,7 +1875,7 @@ describe('checkout', () => {
expect( expect(
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['author'], ['author'],
'author-user-external-id', 'author-user-external-id',
@ -1896,7 +1898,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['author'], ['author'],
'author-user-external-id', 'author-user-external-id',
@ -1937,7 +1939,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['author'], ['author'],
'author-user-external-id', 'author-user-external-id',
@ -1968,7 +1970,7 @@ describe('checkout', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
try { try {
await service.checkout( await service.checkout(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
['none'], ['none'],
'none-user-external-id', 'none-user-external-id',
@ -2043,7 +2045,7 @@ describe('checkin', () => {
const initTask = await getTask(source, taskId); const initTask = await getTask(source, taskId);
await service.checkin( await service.checkin(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
); );
@ -2089,7 +2091,11 @@ describe('checkin', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.checkin(makeContext('trackingId'), 1, 'typist-user-external-id'), service.checkin(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2137,7 +2143,11 @@ describe('checkin', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.checkin(makeContext('trackingId'), 1, 'typist-user-external-id'), service.checkin(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2169,7 +2179,11 @@ describe('checkin', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.checkin(makeContext('trackingId'), 1, 'typist-user-external-id'), service.checkin(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND), new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND),
); );
@ -2231,7 +2245,7 @@ describe('suspend', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.suspend( await service.suspend(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
); );
@ -2276,7 +2290,11 @@ describe('suspend', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.suspend(makeContext('trackingId'), 1, 'typist-user-external-id'), service.suspend(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2324,7 +2342,11 @@ describe('suspend', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.checkin(makeContext('trackingId'), 1, 'typist-user-external-id'), service.checkin(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2356,7 +2378,11 @@ describe('suspend', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.checkin(makeContext('trackingId'), 1, 'typist-user-external-id'), service.checkin(
makeContext('trackingId', 'requestId'),
1,
'typist-user-external-id',
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND), new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND),
); );
@ -2419,7 +2445,7 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
['typist', 'standard'], ['typist', 'standard'],
@ -2468,7 +2494,7 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
['typist', 'standard'], ['typist', 'standard'],
@ -2520,7 +2546,7 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
['admin', 'author'], ['admin', 'author'],
@ -2571,7 +2597,7 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
['admin', 'author'], ['admin', 'author'],
@ -2620,10 +2646,12 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.cancel(makeContext('trackingId'), 1, 'typist-user-external-id', [ service.cancel(
'admin', makeContext('trackingId', 'requestId'),
'author', 1,
]), 'typist-user-external-id',
['admin', 'author'],
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2671,10 +2699,12 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.cancel(makeContext('trackingId'), 1, 'typist-user-external-id', [ service.cancel(
'typist', makeContext('trackingId', 'requestId'),
'standard', 1,
]), 'typist-user-external-id',
['typist', 'standard'],
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
); );
@ -2706,10 +2736,12 @@ describe('cancel', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await expect( await expect(
service.cancel(makeContext('trackingId'), 1, 'typist-user-external-id', [ service.cancel(
'typist', makeContext('trackingId', 'requestId'),
'standard', 1,
]), 'typist-user-external-id',
['typist', 'standard'],
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND), new HttpException(makeErrorResponse('E010603'), HttpStatus.NOT_FOUND),
); );
@ -2774,7 +2806,7 @@ describe('cancel', () => {
NotificationhubService, NotificationhubService,
); );
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
'typist-user-external-id', 'typist-user-external-id',
['typist', 'standard'], ['typist', 'standard'],
@ -2791,7 +2823,7 @@ describe('cancel', () => {
expect(permisions[0].user_id).toEqual(typistUserId); expect(permisions[0].user_id).toEqual(typistUserId);
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${typistUserId}`], [`user_${typistUserId}`],
{ {
authorId: 'AUTHOR_ID', authorId: 'AUTHOR_ID',
@ -2884,7 +2916,7 @@ describe('cancel', () => {
NotificationhubService, NotificationhubService,
); );
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
external_id, external_id,
role.split(' ') as Roles[], role.split(' ') as Roles[],
@ -2901,7 +2933,7 @@ describe('cancel', () => {
expect(permisions[0].user_id).toEqual(autoRoutingTypistUserId); expect(permisions[0].user_id).toEqual(autoRoutingTypistUserId);
// 通知処理が想定通りの引数で呼ばれているか確認 // 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith( expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
[`user_${autoRoutingTypistUserId}`], [`user_${autoRoutingTypistUserId}`],
{ {
authorId: 'AUTHOR_ID', authorId: 'AUTHOR_ID',
@ -2956,7 +2988,7 @@ describe('cancel', () => {
NotificationhubService, NotificationhubService,
); );
await service.cancel( await service.cancel(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
1, 1,
external_id, external_id,
role.split(' ') as Roles[], role.split(' ') as Roles[],
@ -3030,7 +3062,7 @@ describe('backup', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.backup( await service.backup(
makeContext(admin.external_id), makeContext(admin.external_id, 'requestId'),
audioFileId, audioFileId,
admin.external_id, admin.external_id,
); );
@ -3082,7 +3114,7 @@ describe('backup', () => {
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
await service.backup( await service.backup(
makeContext(admin.external_id), makeContext(admin.external_id, 'requestId'),
audioFileId, audioFileId,
admin.external_id, admin.external_id,
); );
@ -3135,7 +3167,7 @@ describe('backup', () => {
try { try {
await service.backup( await service.backup(
makeContext(admin.external_id), makeContext(admin.external_id, 'requestId'),
audioFileId, audioFileId,
admin.external_id, admin.external_id,
); );
@ -3190,7 +3222,7 @@ describe('backup', () => {
try { try {
await service.backup( await service.backup(
makeContext(admin.external_id), makeContext(admin.external_id, 'requestId'),
9999, // 存在しないタスクID 9999, // 存在しないタスクID
admin.external_id, admin.external_id,
); );
@ -3251,7 +3283,7 @@ describe('backup', () => {
try { try {
await service.backup( await service.backup(
makeContext(admin.external_id), makeContext(admin.external_id, 'requestId'),
audioFileId, audioFileId,
admin.external_id, admin.external_id,
); );
@ -3344,7 +3376,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId2, typistUserId); await createCheckoutPermissions(source, taskId2, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3416,7 +3448,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId2, typistUserId); await createCheckoutPermissions(source, taskId2, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3488,7 +3520,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId2, typistUserId); await createCheckoutPermissions(source, taskId2, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3560,7 +3592,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId2, typistUserId); await createCheckoutPermissions(source, taskId2, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3632,7 +3664,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId2, typistUserId); await createCheckoutPermissions(source, taskId2, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3680,7 +3712,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId1, typistUserId); await createCheckoutPermissions(source, taskId1, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const nextAudioFileId = await service.getNextTask( const nextAudioFileId = await service.getNextTask(
context, context,
@ -3727,7 +3759,7 @@ describe('getNextTask', () => {
await createCheckoutPermissions(source, taskId1, typistUserId); await createCheckoutPermissions(source, taskId1, typistUserId);
const service = module.get<TasksService>(TasksService); const service = module.get<TasksService>(TasksService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
// 実行結果が正しいか確認 // 実行結果が正しいか確認
try { try {

View File

@ -33,15 +33,20 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
import { Context } from '../../common/log'; import { Context } from '../../common/log';
import { User } from '../../repositories/users/entity/user.entity'; import { User } from '../../repositories/users/entity/user.entity';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
@Injectable() @Injectable()
export class TasksService { export class TasksService {
private readonly logger = new Logger(TasksService.name); private readonly logger = new Logger(TasksService.name);
constructor( constructor(
private readonly accountsRepository: AccountsRepositoryService,
private readonly taskRepository: TasksRepositoryService, private readonly taskRepository: TasksRepositoryService,
private readonly usersRepository: UsersRepositoryService, private readonly usersRepository: UsersRepositoryService,
private readonly userGroupsRepositoryService: UserGroupsRepositoryService, private readonly userGroupsRepositoryService: UserGroupsRepositoryService,
private readonly adB2cService: AdB2cService, private readonly adB2cService: AdB2cService,
private readonly sendgridService: SendGridService,
private readonly notificationhubService: NotificationhubService, private readonly notificationhubService: NotificationhubService,
) {} ) {}
@ -74,10 +79,11 @@ export class TasksService {
try { try {
const { account_id, author_id } = const { account_id, author_id } =
await this.usersRepository.findUserByExternalId(userId); await this.usersRepository.findUserByExternalId(context, userId);
if (roles.includes(ADMIN_ROLES.ADMIN)) { if (roles.includes(ADMIN_ROLES.ADMIN)) {
const result = await this.taskRepository.getTasksFromAccountId( const result = await this.taskRepository.getTasksFromAccountId(
context,
account_id, account_id,
offset, offset,
limit, limit,
@ -104,6 +110,7 @@ export class TasksService {
} }
const result = const result =
await this.taskRepository.getTasksFromAuthorIdAndAccountId( await this.taskRepository.getTasksFromAuthorIdAndAccountId(
context,
author_id, author_id,
account_id, account_id,
offset, offset,
@ -126,6 +133,7 @@ export class TasksService {
if (roles.includes(USER_ROLES.TYPIST)) { if (roles.includes(USER_ROLES.TYPIST)) {
const result = await this.taskRepository.getTasksFromTypistRelations( const result = await this.taskRepository.getTasksFromTypistRelations(
context,
userId, userId,
offset, offset,
limit, limit,
@ -187,10 +195,11 @@ export class TasksService {
try { try {
const { account_id: accountId, id } = const { account_id: accountId, id } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// タスク一覧を取得する // タスク一覧を取得する
const tasks = await this.taskRepository.getSortedTasks( const tasks = await this.taskRepository.getSortedTasks(
context,
accountId, accountId,
id, id,
fileId, fileId,
@ -268,7 +277,7 @@ export class TasksService {
); );
const { id, account_id, author_id } = const { id, account_id, author_id } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
if (roles.includes(USER_ROLES.AUTHOR)) { if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする // API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
@ -276,6 +285,7 @@ export class TasksService {
throw new Error('AuthorID not found'); throw new Error('AuthorID not found');
} }
await this.taskRepository.getTaskFromAudioFileId( await this.taskRepository.getTaskFromAudioFileId(
context,
audioFileId, audioFileId,
account_id, account_id,
author_id, author_id,
@ -284,11 +294,13 @@ export class TasksService {
} }
if (roles.includes(USER_ROLES.TYPIST)) { if (roles.includes(USER_ROLES.TYPIST)) {
return await this.taskRepository.checkout(audioFileId, account_id, id, [ return await this.taskRepository.checkout(
TASK_STATUS.UPLOADED, context,
TASK_STATUS.PENDING, audioFileId,
TASK_STATUS.IN_PROGRESS, account_id,
]); id,
[TASK_STATUS.UPLOADED, TASK_STATUS.PENDING, TASK_STATUS.IN_PROGRESS],
);
} }
throw new InvalidRoleError(`invalid roles: ${roles.join(',')}`); throw new InvalidRoleError(`invalid roles: ${roles.join(',')}`);
@ -350,15 +362,103 @@ export class TasksService {
this.checkin.name this.checkin.name
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`, } | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
); );
const { id } = await this.usersRepository.findUserByExternalId( const user = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
return await this.taskRepository.checkin( await this.taskRepository.checkin(
context,
audioFileId, audioFileId,
id, user.id,
TASK_STATUS.IN_PROGRESS, TASK_STATUS.IN_PROGRESS,
); );
// メール送信処理
try {
// タスク情報の取得
const task = await this.taskRepository.getTaskAndAudioFile(
context,
audioFileId,
user.account_id,
[TASK_STATUS.FINISHED],
);
if (!task) {
throw new Error(
`task not found. audioFileId: ${audioFileId}. account_id: ${user.account_id}`,
);
}
// author情報の取得
if (!task.file?.author_id) {
throw new Error(
`author_id not found. audioFileId: ${audioFileId}. account_id: ${user.account_id}`,
);
}
const { external_id: authorExternalId } =
await this.usersRepository.findUserByAuthorId(
context,
task.file.author_id,
user.account_id,
);
// プライマリ管理者を取得
const { external_id: primaryAdminExternalId } =
await this.getPrimaryAdminUser(context, user.account_id);
// ADB2C情報を取得する
const usersInfo = await this.adB2cService.getUsers(context, [
externalId,
authorExternalId,
primaryAdminExternalId,
]);
// メール送信に必要な情報を取得
const author = usersInfo.find((x) => x.id === authorExternalId);
if (!author) {
throw new Error(`author not found. id=${authorExternalId}`);
}
const { displayName: authorName, emailAddress: authorEmail } =
getUserNameAndMailAddress(author);
if (!authorEmail) {
throw new Error(`author email not found. id=${authorExternalId}`);
}
const typist = usersInfo.find((x) => x.id === externalId);
if (!typist) {
throw new Error(`typist not found. id=${externalId}`);
}
const { displayName: typistName, emailAddress: typistEmail } =
getUserNameAndMailAddress(typist);
if (!typistEmail) {
throw new Error(`typist email not found. id=${externalId}`);
}
const primaryAdmin = usersInfo.find(
(x) => x.id === primaryAdminExternalId,
);
if (!primaryAdmin) {
throw new Error(
`primary admin not found. id=${primaryAdminExternalId}`,
);
}
const { displayName: primaryAdminName } =
getUserNameAndMailAddress(primaryAdmin);
// メール送信
this.sendgridService.sendMailWithU117(
context,
authorEmail,
typistEmail,
authorName,
task.file.file_name.replace('.zip', ''),
typistName,
primaryAdminName,
);
} catch (e) {
// メール送信に関する例外はログだけ出して握りつぶす
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -391,6 +491,26 @@ export class TasksService {
); );
} }
} }
private async getPrimaryAdminUser(
context: Context,
accountId: number,
): Promise<User> {
const accountInfo = await this.accountsRepository.findAccountById(
context,
accountId,
);
if (!accountInfo || !accountInfo.primary_admin_user_id) {
throw new Error(`account or primary admin not found. id=${accountId}`);
}
const primaryAdmin = await this.usersRepository.findUserById(
context,
accountInfo.primary_admin_user_id,
);
return primaryAdmin;
}
/** /**
* *
* @param audioFileId * @param audioFileId
@ -412,7 +532,10 @@ export class TasksService {
let user: User; let user: User;
try { try {
// ユーザー取得 // ユーザー取得
user = await this.usersRepository.findUserByExternalId(externalId); user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.cancel.name}`); this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.cancel.name}`);
@ -425,6 +548,7 @@ export class TasksService {
try { try {
// roleにAdminが含まれていれば、文字起こし担当でなくてもキャンセルできるため、ユーザーIDは指定しない // roleにAdminが含まれていれば、文字起こし担当でなくてもキャンセルできるため、ユーザーIDは指定しない
await this.taskRepository.cancel( await this.taskRepository.cancel(
context,
audioFileId, audioFileId,
[TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING], [TASK_STATUS.IN_PROGRESS, TASK_STATUS.PENDING],
user.account_id, user.account_id,
@ -462,6 +586,7 @@ export class TasksService {
// キャンセルしたタスクに自動ルーティングを行う // キャンセルしたタスクに自動ルーティングを行う
const { typistGroupIds, typistIds } = const { typistGroupIds, typistIds } =
await this.taskRepository.autoRouting( await this.taskRepository.autoRouting(
context,
audioFileId, audioFileId,
user.account_id, user.account_id,
user.author_id ?? undefined, user.author_id ?? undefined,
@ -504,10 +629,12 @@ export class TasksService {
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`, } | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
); );
const { id } = await this.usersRepository.findUserByExternalId( const { id } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
return await this.taskRepository.suspend( return await this.taskRepository.suspend(
context,
audioFileId, audioFileId,
id, id,
TASK_STATUS.IN_PROGRESS, TASK_STATUS.IN_PROGRESS,
@ -564,9 +691,9 @@ export class TasksService {
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`, } | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
); );
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
await this.taskRepository.backup(accountId, audioFileId, [ await this.taskRepository.backup(context, accountId, audioFileId, [
TASK_STATUS.FINISHED, TASK_STATUS.FINISHED,
TASK_STATUS.BACKUP, TASK_STATUS.BACKUP,
]); ]);
@ -651,13 +778,14 @@ export class TasksService {
} | params: { audioFileId: ${audioFileId}, assignees: ${assignees}, externalId: ${externalId}, role: ${role} };`, } | params: { audioFileId: ${audioFileId}, assignees: ${assignees}, externalId: ${externalId}, role: ${role} };`,
); );
const { author_id, account_id } = const { author_id, account_id } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする // RoleがAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (role.includes(USER_ROLES.AUTHOR) && !author_id) { if (role.includes(USER_ROLES.AUTHOR) && !author_id) {
throw new Error('AuthorID not found'); throw new Error('AuthorID not found');
} }
await this.taskRepository.changeCheckoutPermission( await this.taskRepository.changeCheckoutPermission(
context,
audioFileId, audioFileId,
author_id ?? undefined, author_id ?? undefined,
account_id, account_id,
@ -737,6 +865,7 @@ export class TasksService {
); );
const groupMembers = const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
context,
typistGroupIds, typistGroupIds,
); );
@ -757,6 +886,7 @@ export class TasksService {
// 通知内容に含む音声ファイル情報を取得 // 通知内容に含む音声ファイル情報を取得
const { file } = await this.taskRepository.getTaskAndAudioFile( const { file } = await this.taskRepository.getTaskAndAudioFile(
context,
audioFileId, audioFileId,
accountId, accountId,
[TASK_STATUS.UPLOADED], [TASK_STATUS.UPLOADED],

View File

@ -15,6 +15,8 @@ import { Assignee } from '../types/types';
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity'; import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service'; import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service';
import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service'; import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service';
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
export type TasksRepositoryMockValue = { export type TasksRepositoryMockValue = {
getTasksFromAccountId: getTasksFromAccountId:
@ -84,6 +86,12 @@ export const makeTasksServiceMock = async (
return makeNotificationhubServiceMock( return makeNotificationhubServiceMock(
notificationhubServiceMockValue, notificationhubServiceMockValue,
); );
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
case AccountsRepositoryService:
return {};
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
case SendGridService:
return {};
} }
}) })
.compile(); .compile();

View File

@ -22,7 +22,6 @@ export class TasksRequest {
}) })
@IsInt() @IsInt()
@Min(0) @Min(0)
@Type(() => Number)
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
limit: number; limit: number;
@ -35,7 +34,6 @@ export class TasksRequest {
}) })
@IsInt() @IsInt()
@Min(0) @Min(0)
@Type(() => Number)
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
offset: number; offset: number;
@ -69,6 +67,7 @@ export class TasksRequest {
paramName?: string; paramName?: string;
} }
// TODO: RequestでもResponseでも使われているので、Requestに使用される箇所のみバリデータでチェックが行われる状態になっている
export class Assignee { export class Assignee {
@ApiProperty({ @ApiProperty({
required: false, required: false,
@ -192,6 +191,7 @@ export class AudioNextRequest {
@ApiProperty({ description: '文字起こし完了したタスクの音声ファイルID' }) @ApiProperty({ description: '文字起こし完了したタスクの音声ファイルID' })
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(1)
endedFileId: number; endedFileId: number;
} }

View File

@ -3,6 +3,7 @@ import {
Get, Get,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Req, Req,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
@ -21,13 +22,14 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES } from '../../constants'; import { ADMIN_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, retrieveRequestId, retrieveIp } from '../../common/log';
import { TemplatesService } from './templates.service'; import { TemplatesService } from './templates.service';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('templates') @ApiTags('templates')
@Controller('templates') @Controller('templates')
export class TemplatesController { export class TemplatesController {
private readonly logger = new Logger(TemplatesController.name);
constructor(private readonly templatesService: TemplatesService) {} constructor(private readonly templatesService: TemplatesService) {}
@ApiResponse({ @ApiResponse({
@ -63,6 +65,22 @@ export class TemplatesController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -72,7 +90,9 @@ export class TemplatesController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const templates = await this.templatesService.getTemplates(context, userId); const templates = await this.templatesService.getTemplates(context, userId);
return { templates }; return { templates };

View File

@ -35,7 +35,7 @@ describe('getTemplates', () => {
const service = module.get<TemplatesService>(TemplatesService); const service = module.get<TemplatesService>(TemplatesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const template1 = await createTemplateFile( const template1 = await createTemplateFile(
source, source,
@ -76,7 +76,7 @@ describe('getTemplates', () => {
const service = module.get<TemplatesService>(TemplatesService); const service = module.get<TemplatesService>(TemplatesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
const templates = await service.getTemplates(context, admin.external_id); const templates = await service.getTemplates(context, admin.external_id);
@ -94,7 +94,7 @@ describe('getTemplates', () => {
const service = module.get<TemplatesService>(TemplatesService); const service = module.get<TemplatesService>(TemplatesService);
// 第五階層のアカウント作成 // 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//DBアクセスに失敗するようにする //DBアクセスに失敗するようにする
const typistGroupService = module.get<TemplateFilesRepositoryService>( const typistGroupService = module.get<TemplateFilesRepositoryService>(

View File

@ -31,10 +31,10 @@ export class TemplatesService {
try { try {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
const templateFileRecords = const templateFileRecords =
await this.templateFilesRepository.getTemplateFiles(accountId); await this.templateFilesRepository.getTemplateFiles(context, accountId);
// DBから取得したテンプレートファイルのレコードをレスポンス用に整形する // DBから取得したテンプレートファイルのレコードをレスポンス用に整形する
const resTemplates = templateFileRecords.map((templateFile) => ({ const resTemplates = templateFileRecords.map((templateFile) => ({

View File

@ -1,14 +1,24 @@
import { Controller, HttpStatus, Get } from '@nestjs/common'; import {
Controller,
HttpStatus,
Get,
Logger,
HttpException,
Req,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { TermsService } from '../terms/terms.service'; import { TermsService } from '../terms/terms.service';
import { ErrorResponse } from '../../common/error/types/types'; import { ErrorResponse } from '../../common/error/types/types';
import { makeContext } from '../../common/log'; import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { GetTermsInfoResponse } from './types/types'; import { GetTermsInfoResponse } from './types/types';
import { v4 as uuidv4 } from 'uuid'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { Request } from 'express';
@ApiTags('terms') @ApiTags('terms')
@Controller('terms') @Controller('terms')
export class TermsController { export class TermsController {
private readonly logger = new Logger(TermsController.name);
constructor( constructor(
private readonly termsService: TermsService, //private readonly cryptoService: CryptoService, private readonly termsService: TermsService, //private readonly cryptoService: CryptoService,
) {} ) {}
@ -25,8 +35,24 @@ export class TermsController {
type: ErrorResponse, type: ErrorResponse,
}) })
@ApiOperation({ operationId: 'getTermsInfo' }) @ApiOperation({ operationId: 'getTermsInfo' })
async getTermsInfo(): Promise<GetTermsInfoResponse> { async getTermsInfo(@Req() req: Request): Promise<GetTermsInfoResponse> {
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const termsInfo = await this.termsService.getTermsInfo(context); const termsInfo = await this.termsService.getTermsInfo(context);

View File

@ -39,7 +39,7 @@ describe('利用規約取得', () => {
await createTermInfo(source, 'DPA', 'v1.0'); await createTermInfo(source, 'DPA', 'v1.0');
await createTermInfo(source, 'DPA', 'v1.2'); await createTermInfo(source, 'DPA', 'v1.2');
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const result = await service.getTermsInfo(context); const result = await service.getTermsInfo(context);
expect(result[0].documentType).toBe('EULA'); expect(result[0].documentType).toBe('EULA');
@ -55,7 +55,7 @@ describe('利用規約取得', () => {
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail(); if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
await expect(service.getTermsInfo(context)).rejects.toEqual( await expect(service.getTermsInfo(context)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -70,7 +70,7 @@ describe('利用規約取得', () => {
if (!module) fail(); 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(), 'requestId');
await expect(service.getTermsInfo(context)).rejects.toEqual( await expect(service.getTermsInfo(context)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -85,7 +85,7 @@ describe('利用規約取得', () => {
if (!module) fail(); if (!module) fail();
const service = module.get<TermsService>(TermsService); const service = module.get<TermsService>(TermsService);
await createTermInfo(source, 'PrivacyNotice', 'v1.0'); await createTermInfo(source, 'PrivacyNotice', 'v1.0');
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
await expect(service.getTermsInfo(context)).rejects.toEqual( await expect(service.getTermsInfo(context)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -100,7 +100,7 @@ describe('利用規約取得', () => {
if (!module) fail(); 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(), 'requestId');
await expect(service.getTermsInfo(context)).rejects.toEqual( await expect(service.getTermsInfo(context)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),

View File

@ -20,7 +20,7 @@ export class TermsService {
); );
try { try {
const { eulaVersion, privacyNoticeVersion, dpaVersion } = const { eulaVersion, privacyNoticeVersion, dpaVersion } =
await this.termsRepository.getLatestTermsInfo(); await this.termsRepository.getLatestTermsInfo(context);
return [ return [
{ {
documentType: TERM_TYPE.EULA, documentType: TERM_TYPE.EULA,

View File

@ -18,6 +18,7 @@ import {
} from '../../../common/types/sort'; } from '../../../common/types/sort';
import { AdB2cUser } from '../../../gateways/adb2c/types/types'; import { AdB2cUser } from '../../../gateways/adb2c/types/types';
import { ADB2C_SIGN_IN_TYPE } from '../../../constants'; import { ADB2C_SIGN_IN_TYPE } from '../../../constants';
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
export type SortCriteriaRepositoryMockValue = { export type SortCriteriaRepositoryMockValue = {
updateSortCriteria: SortCriteria | Error; updateSortCriteria: SortCriteria | Error;
@ -47,15 +48,11 @@ export type AdB2cMockValue = {
}; };
export type SendGridMockValue = { export type SendGridMockValue = {
createMailContentFromEmailConfirm: {
subject: string;
text: string;
html: string;
};
createMailContentFromEmailConfirmForNormalUser: createMailContentFromEmailConfirmForNormalUser:
| { subject: string; text: string; html: string } | { subject: string; text: string; html: string }
| Error; | Error;
sendMail: undefined | Error; sendMail: undefined | Error;
sendMailWithU113: undefined | Error;
}; };
export type ConfigMockValue = { export type ConfigMockValue = {
@ -80,6 +77,8 @@ export const makeUsersServiceMock = async (
}) })
.useMocker((token) => { .useMocker((token) => {
switch (token) { switch (token) {
case AccountsRepositoryService:
return {};
case UsersRepositoryService: case UsersRepositoryService:
return makeUsersRepositoryMock(usersRepositoryMockValue); return makeUsersRepositoryMock(usersRepositoryMockValue);
case LicensesRepositoryService: case LicensesRepositoryService:
@ -131,16 +130,8 @@ export const makeSortCriteriaRepositoryMock = (
}; };
export const makeSendGridServiceMock = (value: SendGridMockValue) => { export const makeSendGridServiceMock = (value: SendGridMockValue) => {
const { createMailContentFromEmailConfirm, sendMail } = value; const { sendMail } = value;
return { return {
createMailContentFromEmailConfirm:
createMailContentFromEmailConfirm instanceof Error
? jest
.fn<Promise<void>, []>()
.mockRejectedValue(createMailContentFromEmailConfirm)
: jest
.fn<Promise<{ subject: string; text: string; html: string }>, []>()
.mockResolvedValue(createMailContentFromEmailConfirm),
sendMail: sendMail:
sendMail instanceof Error sendMail instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(sendMail) ? jest.fn<Promise<void>, []>().mockRejectedValue(sendMail)
@ -287,12 +278,12 @@ export const makeConfigMock = (value: ConfigMockValue) => {
export const makeDefaultSendGridlValue = (): SendGridMockValue => { export const makeDefaultSendGridlValue = (): SendGridMockValue => {
return { return {
sendMail: undefined, sendMail: undefined,
createMailContentFromEmailConfirm: { subject: '', text: '', html: '' },
createMailContentFromEmailConfirmForNormalUser: { createMailContentFromEmailConfirmForNormalUser: {
subject: 'test', subject: 'test',
text: 'test', text: 'test',
html: 'test', html: 'test',
}, },
sendMailWithU113: undefined,
}; };
}; };

View File

@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsIn } from 'class-validator'; import { IsBoolean, IsEmail, IsIn, IsInt, IsOptional, MaxLength } from 'class-validator';
import { import {
TASK_LIST_SORTABLE_ATTRIBUTES, TASK_LIST_SORTABLE_ATTRIBUTES,
USER_LICENSE_STATUS, USER_LICENSE_STATUS,
@ -10,6 +10,7 @@ import {
IsPasswordvalid, IsPasswordvalid,
} from '../../../common/validators/encryptionPassword.validator'; } from '../../../common/validators/encryptionPassword.validator';
import { IsRoleAuthorDataValid } from '../../../common/validators/roleAuthor.validator'; import { IsRoleAuthorDataValid } from '../../../common/validators/roleAuthor.validator';
import { Type } from 'class-transformer';
export class ConfirmRequest { export class ConfirmRequest {
@ApiProperty() @ApiProperty()
@ -88,15 +89,22 @@ export class SignupRequest {
authorId?: string; authorId?: string;
@ApiProperty() @ApiProperty()
@IsEmail({ blacklisted_chars: '*' })
email: string; email: string;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
autoRenew: boolean; autoRenew: boolean;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
licenseAlert: boolean; licenseAlert: boolean;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
notification: boolean; notification: boolean;
@ApiProperty({ required: false }) @ApiProperty({ required: false })
@ -208,6 +216,8 @@ export class GetSortCriteriaResponse {
export class PostUpdateUserRequest { export class PostUpdateUserRequest {
@ApiProperty() @ApiProperty()
@Type(() => Number)
@IsInt()
id: number; id: number;
@ApiProperty({ description: 'none/author/typist' }) @ApiProperty({ description: 'none/author/typist' })
@ -219,12 +229,18 @@ export class PostUpdateUserRequest {
authorId?: string; authorId?: string;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
autoRenew: boolean; autoRenew: boolean;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
licenseAlart: boolean; licenseAlart: boolean;
@ApiProperty() @ApiProperty()
@Type(() => Boolean)
@IsBoolean()
notification: boolean; notification: boolean;
@ApiProperty({ required: false }) @ApiProperty({ required: false })
@ -244,8 +260,12 @@ export class PostUpdateUserResponse {}
export class AllocateLicenseRequest { export class AllocateLicenseRequest {
@ApiProperty({ description: 'ユーザーID' }) @ApiProperty({ description: 'ユーザーID' })
@Type(() => Number)
@IsInt()
userId: number; userId: number;
@ApiProperty({ description: '割り当てるライセンスのID' }) @ApiProperty({ description: '割り当てるライセンスのID' })
@Type(() => Number)
@IsInt()
newLicenseId: number; newLicenseId: number;
} }
@ -253,6 +273,8 @@ export class AllocateLicenseResponse {}
export class DeallocateLicenseRequest { export class DeallocateLicenseRequest {
@ApiProperty({ description: 'ユーザーID' }) @ApiProperty({ description: 'ユーザーID' })
@Type(() => Number)
@IsInt()
userId: number; userId: number;
} }
@ -262,10 +284,14 @@ export class UpdateAcceptedVersionRequest {
@ApiProperty({ description: 'IDトークン' }) @ApiProperty({ description: 'IDトークン' })
idToken: string; idToken: string;
@ApiProperty({ description: '更新バージョンEULA' }) @ApiProperty({ description: '更新バージョンEULA' })
@MaxLength(255)
acceptedEULAVersion: string; acceptedEULAVersion: string;
@ApiProperty({ description: '更新バージョンPrivacyNotice' }) @ApiProperty({ description: '更新バージョンPrivacyNotice' })
@MaxLength(255)
acceptedPrivacyNoticeVersion: string; acceptedPrivacyNoticeVersion: string;
@ApiProperty({ description: '更新バージョンDPA', required: false }) @ApiProperty({ description: '更新バージョンDPA', required: false })
@MaxLength(255)
@IsOptional()
acceptedDPAVersion?: string; acceptedDPAVersion?: string;
} }

View File

@ -5,6 +5,7 @@ import {
HttpException, HttpException,
HttpStatus, HttpStatus,
Ip, Ip,
Logger,
Post, Post,
Query, Query,
Req, Req,
@ -52,13 +53,13 @@ import {
} from '../../common/types/sort'; } from '../../common/types/sort';
import { ADMIN_ROLES, TIERS } from '../../constants'; import { ADMIN_ROLES, TIERS } from '../../constants';
import { RoleGuard } from '../../common/guards/role/roleguards'; import { RoleGuard } from '../../common/guards/role/roleguards';
import { makeContext } from '../../common/log'; import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
import { UserRoles } from '../../common/types/role'; import { UserRoles } from '../../common/types/role';
import { v4 as uuidv4 } from 'uuid';
@ApiTags('users') @ApiTags('users')
@Controller('users') @Controller('users')
export class UsersController { export class UsersController {
private readonly logger = new Logger(UsersController.name);
constructor( constructor(
private readonly usersService: UsersService, private readonly usersService: UsersService,
private readonly authService: AuthService, private readonly authService: AuthService,
@ -81,8 +82,27 @@ export class UsersController {
}) })
@ApiOperation({ operationId: 'confirmUser' }) @ApiOperation({ operationId: 'confirmUser' })
@Post('confirm') @Post('confirm')
async confirmUser(@Body() body: ConfirmRequest): Promise<ConfirmResponse> { async confirmUser(
const context = makeContext(uuidv4()); @Body() body: ConfirmRequest,
@Req() req: Request,
): Promise<ConfirmResponse> {
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.usersService.confirmUser(context, body.token); await this.usersService.confirmUser(context, body.token);
return {}; return {};
@ -107,8 +127,25 @@ export class UsersController {
@Post('confirm/initpassword') @Post('confirm/initpassword')
async confirmUserAndInitPassword( async confirmUserAndInitPassword(
@Body() body: ConfirmRequest, @Body() body: ConfirmRequest,
@Req() req: Request,
): Promise<ConfirmResponse> { ): Promise<ConfirmResponse> {
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.usersService.confirmUserAndInitPassword(context, body.token); await this.usersService.confirmUserAndInitPassword(context, body.token);
return {}; return {};
} }
@ -144,6 +181,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -152,7 +206,8 @@ export class UsersController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const users = await this.usersService.getUsers(context, userId); const users = await this.usersService.getUsers(context, userId);
return { users }; return { users };
@ -209,6 +264,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -218,7 +290,8 @@ export class UsersController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
//ユーザ作成処理 //ユーザ作成処理
await this.usersService.createUser( await this.usersService.createUser(
@ -268,6 +341,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -277,7 +367,8 @@ export class UsersController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
return await this.usersService.getRelations(context, userId); return await this.usersService.getRelations(context, userId);
} }
@ -322,6 +413,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -330,7 +438,8 @@ export class UsersController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
//型チェック //型チェック
if ( if (
@ -386,6 +495,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -394,7 +520,8 @@ export class UsersController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const { direction, paramName } = await this.usersService.getSortCriteria( const { direction, paramName } = await this.usersService.getSortCriteria(
context, context,
@ -456,6 +583,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -465,7 +609,8 @@ export class UsersController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.usersService.updateUser( await this.usersService.updateUser(
context, context,
@ -528,6 +673,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -537,7 +699,8 @@ export class UsersController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.usersService.allocateLicense( await this.usersService.allocateLicense(
context, context,
body.userId, body.userId,
@ -591,6 +754,23 @@ export class UsersController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -600,7 +780,8 @@ export class UsersController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.usersService.deallocateLicense(context, body.userId); await this.usersService.deallocateLicense(context, body.userId);
return {}; return {};
@ -628,6 +809,7 @@ export class UsersController {
@Post('/accepted-version') @Post('/accepted-version')
async updateAcceptedVersion( async updateAcceptedVersion(
@Body() body: UpdateAcceptedVersionRequest, @Body() body: UpdateAcceptedVersionRequest,
@Req() req: Request,
): Promise<UpdateAcceptedVersionResponse> { ): Promise<UpdateAcceptedVersionResponse> {
const { const {
idToken, idToken,
@ -636,7 +818,23 @@ export class UsersController {
acceptedDPAVersion, acceptedDPAVersion,
} = body; } = body;
const context = makeContext(uuidv4()); const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const context = makeContext('anonymous', requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const verifiedIdToken = await this.authService.getVerifiedIdToken( const verifiedIdToken = await this.authService.getVerifiedIdToken(
context, context,
@ -685,13 +883,30 @@ export class UsersController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('me') @Get('me')
async getMyUser(@Req() req: Request): Promise<GetMyUserResponse> { async getMyUser(@Req() req: Request): Promise<GetMyUserResponse> {
const accessToken = retrieveAuthorizationToken(req) as string; const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) { if (!accessToken) {
throw new HttpException( throw new HttpException(
makeErrorResponse('E000107'), makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -700,7 +915,8 @@ export class UsersController {
); );
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const userName = await this.usersService.getUserName(context, userId); const userName = await this.usersService.getUserName(context, userId);
return { userName }; return { userName };
} }

View File

@ -8,9 +8,11 @@ import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.r
import { UsersController } from './users.controller'; import { UsersController } from './users.controller';
import { UsersService } from './users.service'; import { UsersService } from './users.service';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
@Module({ @Module({
imports: [ imports: [
AccountsRepositoryModule,
UsersRepositoryModule, UsersRepositoryModule,
LicensesRepositoryModule, LicensesRepositoryModule,
SortCriteriaRepositoryModule, SortCriteriaRepositoryModule,

View File

@ -94,10 +94,12 @@ describe('UsersService.confirmUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
// account id:1, user id: 2のトークン // account id:1, user id: 2のトークン
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await service.confirmUser(context, token); await service.confirmUser(context, token);
//result //result
const resultUser = await getUser(source, userId); const resultUser = await getUser(source, userId);
@ -141,7 +143,8 @@ describe('UsersService.confirmUser', () => {
if (!module) fail(); 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);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
await expect(service.confirmUser(context, token)).rejects.toEqual( await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST),
); );
@ -175,9 +178,10 @@ describe('UsersService.confirmUser', () => {
email_verified: false, email_verified: false,
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.confirmUser(context, token)).rejects.toEqual( await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST),
); );
@ -187,9 +191,10 @@ describe('UsersService.confirmUser', () => {
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail(); if (!module) fail();
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
overrideSendgridService(service, {});
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect(service.confirmUser(context, token)).rejects.toEqual( await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -246,7 +251,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
expect( expect(
await service.confirmUserAndInitPassword( await service.confirmUserAndInitPassword(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
token, token,
), ),
).toEqual(undefined); ).toEqual(undefined);
@ -295,7 +300,10 @@ describe('UsersService.confirmUserAndInitPassword', () => {
); );
const token = 'invalid.id.token'; const token = 'invalid.id.token';
await expect( await expect(
service.confirmUserAndInitPassword(makeContext('trackingId'), token), service.confirmUserAndInitPassword(
makeContext('trackingId', 'requestId'),
token,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST),
); );
@ -348,7 +356,10 @@ describe('UsersService.confirmUserAndInitPassword', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
await expect( await expect(
service.confirmUserAndInitPassword(makeContext('trackingId'), token), service.confirmUserAndInitPassword(
makeContext('trackingId', 'requestId'),
token,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException(makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST), new HttpException(makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST),
); );
@ -398,7 +409,10 @@ describe('UsersService.confirmUserAndInitPassword', () => {
const token = const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw'; 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
await expect( await expect(
service.confirmUserAndInitPassword(makeContext('trackingId'), token), service.confirmUserAndInitPassword(
makeContext('trackingId', 'requestId'),
token,
),
).rejects.toEqual( ).rejects.toEqual(
new HttpException( new HttpException(
makeErrorResponse('E009999'), makeErrorResponse('E009999'),
@ -434,19 +448,12 @@ describe('UsersService.createUser', () => {
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 } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -470,6 +477,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -482,7 +495,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -518,19 +531,12 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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 } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user2'; const name = 'test_user2';
const role = USER_ROLES.AUTHOR; const role = USER_ROLES.AUTHOR;
@ -558,6 +564,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -570,7 +582,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -610,19 +622,12 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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 } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user2'; const name = 'test_user2';
const role = USER_ROLES.AUTHOR; const role = USER_ROLES.AUTHOR;
@ -649,6 +654,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -661,7 +672,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -701,19 +712,12 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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 } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user3'; const name = 'test_user3';
const role = USER_ROLES.TYPIST; const role = USER_ROLES.TYPIST;
@ -737,6 +741,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -749,7 +759,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -786,19 +796,7 @@ describe('UsersService.createUser', () => {
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';
const { account, admin } = await makeTestAccount( await makeTestAccount(source, {}, { external_id: adminExternalId });
source,
{},
{ external_id: adminExternalId },
);
const { tier } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -823,6 +821,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
deleteUser: jest.fn(), deleteUser: jest.fn(),
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -842,7 +846,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -862,7 +866,7 @@ describe('UsersService.createUser', () => {
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認 // ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
expect(b2cService.deleteUser).toBeCalledWith( expect(b2cService.deleteUser).toBeCalledWith(
externalId, externalId,
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
); );
}); });
@ -873,19 +877,12 @@ describe('UsersService.createUser', () => {
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';
const { account, admin } = await makeTestAccount( const { account } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -910,6 +907,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId }; return { sub: externalId };
}, },
deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C error')), deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C error')),
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -929,7 +932,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -954,7 +957,7 @@ describe('UsersService.createUser', () => {
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認 // ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
expect(b2cService.deleteUser).toBeCalledWith( expect(b2cService.deleteUser).toBeCalledWith(
externalId, externalId,
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
); );
}); });
@ -964,19 +967,7 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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( await makeTestAccount(source, {}, { external_id: adminExternalId });
source,
{},
{ external_id: adminExternalId },
);
const { tier } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -1010,7 +1001,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1039,19 +1030,7 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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( await makeTestAccount(source, {}, { external_id: adminExternalId });
source,
{},
{ external_id: adminExternalId },
);
const { tier } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -1089,7 +1068,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1118,19 +1097,7 @@ describe('UsersService.createUser', () => {
if (!module) fail(); 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( await makeTestAccount(source, {}, { external_id: adminExternalId });
source,
{},
{ external_id: adminExternalId },
);
const { tier } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user2'; const name = 'test_user2';
const role = USER_ROLES.AUTHOR; const role = USER_ROLES.AUTHOR;
@ -1158,6 +1125,12 @@ describe('UsersService.createUser', () => {
return { sub: externalId_1 }; return { sub: externalId_1 };
}, },
getUser: async () => {
return {
id: adminExternalId,
displayName: 'admin',
};
},
}); });
overrideSendgridService(service, { overrideSendgridService(service, {
sendMail: async () => { sendMail: async () => {
@ -1170,7 +1143,7 @@ describe('UsersService.createUser', () => {
expect( expect(
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1211,7 +1184,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1247,19 +1220,12 @@ describe('UsersService.createUser', () => {
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';
const { account, admin } = await makeTestAccount( const { account } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user2'; const name = 'test_user2';
const role = USER_ROLES.AUTHOR; const role = USER_ROLES.AUTHOR;
@ -1307,7 +1273,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1335,7 +1301,7 @@ describe('UsersService.createUser', () => {
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認 // ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
expect(b2cService.deleteUser).toBeCalledWith( expect(b2cService.deleteUser).toBeCalledWith(
externalId, externalId,
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
); );
}); });
@ -1347,19 +1313,12 @@ describe('UsersService.createUser', () => {
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( const { account } = await makeTestAccount(
source, source,
{}, {},
{ external_id: adminExternalId }, { external_id: adminExternalId },
); );
const { id: accountId, tier } = account; const { id: accountId } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -1396,7 +1355,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1422,7 +1381,7 @@ describe('UsersService.createUser', () => {
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認 // ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
expect(b2cService.deleteUser).toBeCalledWith( expect(b2cService.deleteUser).toBeCalledWith(
externalId, externalId,
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
); );
}); });
@ -1434,19 +1393,7 @@ describe('UsersService.createUser', () => {
const b2cService = module.get<AdB2cService>(AdB2cService); const b2cService = module.get<AdB2cService>(AdB2cService);
const adminExternalId = 'ADMIN0001'; const adminExternalId = 'ADMIN0001';
const { account, admin } = await makeTestAccount( await makeTestAccount(source, {}, { external_id: adminExternalId });
source,
{},
{ external_id: adminExternalId },
);
const { tier } = account;
const { role: adminRole } = admin;
const token: AccessToken = {
userId: adminExternalId,
role: adminRole,
tier: tier,
};
const name = 'test_user1'; const name = 'test_user1';
const role = USER_ROLES.NONE; const role = USER_ROLES.NONE;
@ -1488,7 +1435,7 @@ describe('UsersService.createUser', () => {
try { try {
await service.createUser( await service.createUser(
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
adminExternalId, adminExternalId,
name, name,
role, role,
@ -1512,7 +1459,7 @@ describe('UsersService.createUser', () => {
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認 // ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
expect(b2cService.deleteUser).toBeCalledWith( expect(b2cService.deleteUser).toBeCalledWith(
externalId, externalId,
makeContext('trackingId'), makeContext('trackingId', 'requestId'),
); );
}); });
}); });
@ -1635,7 +1582,7 @@ describe('UsersService.getUsers', () => {
}, },
]; ];
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
expect(await service.getUsers(context, externalId_author)).toEqual( expect(await service.getUsers(context, externalId_author)).toEqual(
expectedUsers, expectedUsers,
); );
@ -1754,7 +1701,7 @@ describe('UsersService.getUsers', () => {
}, },
]; ];
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
expect(await service.getUsers(context, external_id1)).toEqual( expect(await service.getUsers(context, external_id1)).toEqual(
expectedUsers, expectedUsers,
); );
@ -1778,7 +1725,7 @@ describe('UsersService.getUsers', () => {
prompt: false, prompt: false,
}); });
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await expect( await expect(
service.getUsers(context, 'externalId_failed'), service.getUsers(context, 'externalId_failed'),
@ -1806,7 +1753,7 @@ describe('UsersService.getUsers', () => {
prompt: false, prompt: false,
}); });
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await expect(service.getUsers(context, externalId_author)).rejects.toEqual( await expect(service.getUsers(context, externalId_author)).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND), new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
@ -1831,7 +1778,7 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateSortCriteria( await service.updateSortCriteria(
@ -1862,7 +1809,7 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'), service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'),
@ -1894,7 +1841,7 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'), service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'),
@ -1924,7 +1871,7 @@ describe('UsersService.getSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
expect(await service.getSortCriteria(context, 'external_id')).toEqual({ expect(await service.getSortCriteria(context, 'external_id')).toEqual({
direction: 'ASC', direction: 'ASC',
@ -1953,7 +1900,7 @@ describe('UsersService.getSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.getSortCriteria(context, 'external_id'), service.getSortCriteria(context, 'external_id'),
@ -1988,7 +1935,7 @@ describe('UsersService.getSortCriteria', () => {
configMockValue, configMockValue,
sortCriteriaRepositoryMockValue, sortCriteriaRepositoryMockValue,
); );
const context = makeContext(`uuidv4`); const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.getSortCriteria(context, 'external_id'), service.getSortCriteria(context, 'external_id'),
@ -2048,7 +1995,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2107,7 +2055,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2166,7 +2115,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2225,7 +2175,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2284,7 +2235,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2343,7 +2295,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.updateUser( service.updateUser(
@ -2392,7 +2345,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2451,7 +2405,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
expect( expect(
await service.updateUser( await service.updateUser(
@ -2510,7 +2465,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.updateUser( service.updateUser(
@ -2570,7 +2526,8 @@ describe('UsersService.updateUser', () => {
}); });
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`); overrideSendgridService(service, {});
const context = makeContext(`uuidv4`, 'requestId');
await expect( await expect(
service.updateUser( service.updateUser(
@ -2618,7 +2575,7 @@ describe('UsersService.updateAcceptedVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 5, tier: 5,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.updateAcceptedVersion( await service.updateAcceptedVersion(
@ -2639,7 +2596,7 @@ describe('UsersService.updateAcceptedVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.updateAcceptedVersion( await service.updateAcceptedVersion(
@ -2662,7 +2619,7 @@ describe('UsersService.updateAcceptedVersion', () => {
const { admin } = await makeTestAccount(source, { const { admin } = await makeTestAccount(source, {
tier: 4, tier: 4,
}); });
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await expect( await expect(
@ -2705,7 +2662,7 @@ describe('UsersService.getUserName', () => {
try { try {
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail(); if (!module) fail();
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.getUserName(context, 'external_id'); await service.getUserName(context, 'external_id');
@ -2800,7 +2757,7 @@ describe('UsersService.getRelations', () => {
expect(workflows[3].author_id).toBe(user2); expect(workflows[3].author_id).toBe(user2);
} }
const context = makeContext(external_id); const context = makeContext(external_id, 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const relations = await service.getRelations(context, external_id); const relations = await service.getRelations(context, external_id);
@ -2863,7 +2820,7 @@ describe('UsersService.getRelations', () => {
expect(workflows[0].author_id).toBe(user2); expect(workflows[0].author_id).toBe(user2);
} }
const context = makeContext(external_id); const context = makeContext(external_id, 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
const relations = await service.getRelations(context, external_id); const relations = await service.getRelations(context, external_id);
@ -2889,7 +2846,7 @@ describe('UsersService.getRelations', () => {
try { try {
const module = await makeTestingModule(source); const module = await makeTestingModule(source);
if (!module) fail(); if (!module) fail();
const context = makeContext(uuidv4()); const context = makeContext(uuidv4(), 'requestId');
const service = module.get<UsersService>(UsersService); const service = module.get<UsersService>(UsersService);
await service.getRelations(context, 'external_id'); await service.getRelations(context, 'external_id');

View File

@ -33,7 +33,6 @@ import {
UserNotFoundError, UserNotFoundError,
} from '../../repositories/users/errors/types'; } from '../../repositories/users/errors/types';
import { import {
ADB2C_SIGN_IN_TYPE,
LICENSE_EXPIRATION_THRESHOLD_DAYS, LICENSE_EXPIRATION_THRESHOLD_DAYS,
MANUAL_RECOVERY_REQUIRED, MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER, OPTION_ITEM_VALUE_TYPE_NUMBER,
@ -50,6 +49,9 @@ import {
LicenseUnavailableError, LicenseUnavailableError,
} from '../../repositories/licenses/errors/types'; } from '../../repositories/licenses/errors/types';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { Account } from '../../repositories/accounts/entity/account.entity';
@Injectable() @Injectable()
export class UsersService { export class UsersService {
@ -57,6 +59,7 @@ export class UsersService {
private readonly mailFrom: string; private readonly mailFrom: string;
private readonly appDomain: string; private readonly appDomain: string;
constructor( constructor(
private readonly accountsRepository: AccountsRepositoryService,
private readonly usersRepository: UsersRepositoryService, private readonly usersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService, private readonly licensesRepository: LicensesRepositoryService,
private readonly sortCriteriaRepository: SortCriteriaRepositoryService, private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
@ -94,8 +97,27 @@ export class UsersService {
// トランザクションで取得と更新をまとめる // トランザクションで取得と更新をまとめる
const userId = decodedToken.userId; const userId = decodedToken.userId;
await this.usersRepository.updateUserVerifiedAndCreateTrialLicense( await this.usersRepository.updateUserVerifiedAndCreateTrialLicense(
context,
userId, userId,
); );
try {
const { company_name: companyName } =
await this.accountsRepository.findAccountById(
context,
decodedToken.accountId,
);
// アカウント認証が完了した旨をメール送信する
await this.sendgridService.sendMailWithU101(
context,
decodedToken.email,
companyName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -166,8 +188,12 @@ export class UsersService {
); );
//DBよりアクセス者の所属するアカウントIDを取得する //DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser; let adminUser: EntityUser;
let account: Account | null;
try { try {
adminUser = await this.usersRepository.findUserByExternalId(externalId); adminUser = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException( throw new HttpException(
@ -177,12 +203,14 @@ export class UsersService {
} }
const accountId = adminUser.account_id; const accountId = adminUser.account_id;
account = adminUser.account;
//authorIdが重複していないかチェックする //authorIdが重複していないかチェックする
if (authorId) { if (authorId) {
let isAuthorIdDuplicated = false; let isAuthorIdDuplicated = false;
try { try {
isAuthorIdDuplicated = await this.usersRepository.existsAuthorId( isAuthorIdDuplicated = await this.usersRepository.existsAuthorId(
context,
accountId, accountId,
authorId, authorId,
); );
@ -252,7 +280,10 @@ export class UsersService {
prompt, prompt,
); );
// ユーザ作成 // ユーザ作成
newUser = await this.usersRepository.createNormalUser(newUserInfo); newUser = await this.usersRepository.createNormalUser(
context,
newUserInfo,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}]create user failed`); this.logger.error(`[${context.getTrackingId()}]create user failed`);
@ -277,23 +308,32 @@ export class UsersService {
//Email送信用のコンテンツを作成する //Email送信用のコンテンツを作成する
try { try {
// メールの内容を構成 if (account === null) {
const { subject, text, html } = throw new Error(`account is null. account_id=${accountId}`);
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( }
const { primary_admin_user_id: primaryAdminUserId } = account;
if (primaryAdminUserId === null) {
throw new Error(
`primary_admin_user_id is null. account_id=${accountId}`,
);
}
const { external_id: extarnalId } =
await this.usersRepository.findUserById(context, primaryAdminUserId);
const primaryAdmimAdb2cUser = await this.adB2cService.getUser(
context,
extarnalId,
);
const { displayName: primaryAdminUserName } = getUserNameAndMailAddress(
primaryAdmimAdb2cUser,
);
await this.sendgridService.sendMailWithU114(
context, context,
accountId, accountId,
newUser.id, newUser.id,
email, email,
); primaryAdminUserName,
//SendGridAPIを呼び出してメールを送信する
await this.sendgridService.sendMail(
context,
email,
this.mailFrom,
subject,
text,
html,
); );
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -347,7 +387,7 @@ export class UsersService {
} | params: { userId: ${userId} }`, } | params: { userId: ${userId} }`,
); );
try { try {
await this.usersRepository.deleteNormalUser(userId); await this.usersRepository.deleteNormalUser(context, userId);
this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`); this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`);
} catch (error) { } catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`); this.logger.error(`[${context.getTrackingId()}] error=${error}`);
@ -475,11 +515,11 @@ export class UsersService {
// ランダムなパスワードを生成する // ランダムなパスワードを生成する
const ramdomPassword = makePassword(); const ramdomPassword = makePassword();
const { userId, email } = decodedToken; const { accountId, userId, email } = decodedToken;
try { try {
// ユーザー情報からAzure AD B2CのIDを特定する // ユーザー情報からAzure AD B2CのIDを特定する
const user = await this.usersRepository.findUserById(userId); const user = await this.usersRepository.findUserById(context, userId);
const extarnalId = user.external_id; const extarnalId = user.external_id;
// パスワードを変更する // パスワードを変更する
await this.adB2cService.changePassword( await this.adB2cService.changePassword(
@ -488,21 +528,31 @@ export class UsersService {
ramdomPassword, ramdomPassword,
); );
// ユーザを認証済みにする // ユーザを認証済みにする
await this.usersRepository.updateUserVerified(userId); await this.usersRepository.updateUserVerified(context, userId);
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
const subject = 'A temporary password has been issued.';
const text = 'temporary password: ' + ramdomPassword;
const html = `<p>OMDS TOP PAGE URL.<p><a href="${this.appDomain}">${this.appDomain}</a><br>temporary password: ${ramdomPassword}`;
// メールを送信 // メール送信処理
await this.sendgridService.sendMail( try {
const { external_id: primaryAdminUserExternalId } =
await this.getPrimaryAdminUser(context, accountId);
const adb2cUser = await this.adB2cService.getUser(
context,
primaryAdminUserExternalId,
);
const { displayName: primaryAdminName } =
getUserNameAndMailAddress(adb2cUser);
await this.sendgridService.sendMailWithU113(
context, context,
email, email,
this.mailFrom, primaryAdminName,
subject, ramdomPassword,
text,
html,
); );
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -545,12 +595,7 @@ export class UsersService {
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する // DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
const externalIds = dbUsers.map((x) => x.external_id); const externalIds = dbUsers.map((x) => x.external_id);
const trackingId = new Context(context.trackingId); const adb2cUsers = await this.adB2cService.getUsers(context, externalIds);
const adb2cUsers = await this.adB2cService.getUsers(
// TODO: 外部連携以外のログ強化時に、ContollerからContextを取得するように修正する
trackingId,
externalIds,
);
// DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出 // DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出
const users = dbUsers.map((dbUser): User => { const users = dbUsers.map((dbUser): User => {
@ -567,10 +612,12 @@ export class UsersService {
(user) => user.id === dbUser.external_id, (user) => user.id === dbUser.external_id,
); );
if (adb2cUser == null) {
throw new Error('mail not found.'); // TODO: リファクタ時に挙動を変更しないようエラー文面をmail not foundのまま据え置き。影響がない事が確認できたらエラー文面を変更する。
}
// メールアドレスを取得する // メールアドレスを取得する
const mail = adb2cUser?.identities?.find( const { emailAddress: mail } = getUserNameAndMailAddress(adb2cUser);
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
)?.issuerAssignedId;
//メールアドレスが取得できない場合はエラー //メールアドレスが取得できない場合はエラー
if (!mail) { if (!mail) {
@ -668,7 +715,10 @@ export class UsersService {
let user: EntityUser; let user: EntityUser;
try { try {
// ユーザー情報を取得 // ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId); user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -681,6 +731,7 @@ export class UsersService {
try { try {
// ユーザーのソート条件を更新 // ユーザーのソート条件を更新
await this.sortCriteriaRepository.updateSortCriteria( await this.sortCriteriaRepository.updateSortCriteria(
context,
user.id, user.id,
paramName, paramName,
direction, direction,
@ -717,7 +768,10 @@ export class UsersService {
let user: EntityUser; let user: EntityUser;
try { try {
// ユーザー情報を取得 // ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId); user = await this.usersRepository.findUserByExternalId(
context,
externalId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
@ -730,6 +784,7 @@ export class UsersService {
try { try {
// ユーザーのソート条件を取得 // ユーザーのソート条件を取得
const sortCriteria = await this.sortCriteriaRepository.getSortCriteria( const sortCriteria = await this.sortCriteriaRepository.getSortCriteria(
context,
user.id, user.id,
); );
const { direction, parameter } = sortCriteria; const { direction, parameter } = sortCriteria;
@ -769,11 +824,14 @@ export class UsersService {
} | params: { userId: ${userId} };`, } | params: { userId: ${userId} };`,
); );
try { try {
const { id } = await this.usersRepository.findUserByExternalId(userId); const { id } = await this.usersRepository.findUserByExternalId(
context,
userId,
);
// ユーザー関連情報を取得 // ユーザー関連情報を取得
const { user, authors, worktypes, activeWorktype } = const { user, authors, worktypes, activeWorktype } =
await this.usersRepository.getUserRelations(id); await this.usersRepository.getUserRelations(context, id);
// AuthorIDのリストを作成 // AuthorIDのリストを作成
const authorIds = authors.flatMap((author) => const authorIds = authors.flatMap((author) =>
@ -888,11 +946,12 @@ export class UsersService {
// 実行ユーザーのアカウントIDを取得 // 実行ユーザーのアカウントIDを取得
const accountId = ( const accountId = (
await this.usersRepository.findUserByExternalId(extarnalId) await this.usersRepository.findUserByExternalId(context, extarnalId)
).account_id; ).account_id;
// ユーザー情報を更新 // ユーザー情報を更新
await this.usersRepository.update( await this.usersRepository.update(
context,
accountId, accountId,
id, id,
role, role,
@ -904,6 +963,51 @@ export class UsersService {
encryptionPassword, encryptionPassword,
prompt, prompt,
); );
// メール送信処理
try {
const { adminEmails } = await this.getAccountInformation(
context,
accountId,
);
// 変更ユーザー情報を取得
const { external_id: userExtarnalId } =
await this.usersRepository.findUserById(context, id);
const adb2cUser = await this.adB2cService.getUser(
context,
userExtarnalId,
);
const { displayName: userName, emailAddress: userEmail } =
getUserNameAndMailAddress(adb2cUser);
if (userEmail === undefined) {
throw new Error(`userEmail is null. externalId=${extarnalId}`);
}
// プライマリ管理者を取得
const { external_id: adminExternalId } = await this.getPrimaryAdminUser(
context,
accountId,
);
const adb2cAdminUser = await this.adB2cService.getUser(
context,
adminExternalId,
);
const { displayName: primaryAdminName } =
getUserNameAndMailAddress(adb2cAdminUser);
await this.sendgridService.sendMailWithU115(
context,
userName,
userEmail,
primaryAdminName,
adminEmails,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -966,14 +1070,53 @@ export class UsersService {
); );
try { try {
const accountId = (await this.usersRepository.findUserById(userId)) const { external_id: externalId, account_id: accountId } =
.account_id; await this.usersRepository.findUserById(context, userId);
await this.licensesRepository.allocateLicense( await this.licensesRepository.allocateLicense(
context,
userId, userId,
newLicenseId, newLicenseId,
accountId, accountId,
); );
// メール送信処理
try {
const { parent_account_id: dealerId } =
await this.accountsRepository.findAccountById(context, accountId);
if (dealerId == null) {
throw new Error(`dealer is null. account_id=${accountId}`);
}
const { company_name: dealerName } =
await this.accountsRepository.findAccountById(context, dealerId);
const { companyName, adminEmails } = await this.getAccountInformation(
context,
accountId,
);
const adb2cUser = await this.adB2cService.getUser(context, externalId);
const { displayName, emailAddress } =
getUserNameAndMailAddress(adb2cUser);
if (emailAddress == null) {
throw new Error(`emailAddress is null. externalId=${externalId}`);
}
await this.sendgridService.sendMailWithU108(
context,
displayName,
emailAddress,
adminEmails,
companyName,
dealerName,
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -1015,10 +1158,15 @@ export class UsersService {
); );
try { try {
const accountId = (await this.usersRepository.findUserById(userId)) const accountId = (
.account_id; await this.usersRepository.findUserById(context, userId)
).account_id;
await this.licensesRepository.deallocateLicense(userId, accountId); await this.licensesRepository.deallocateLicense(
context,
userId,
accountId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {
@ -1069,6 +1217,7 @@ export class UsersService {
try { try {
await this.usersRepository.updateAcceptedTermsVersion( await this.usersRepository.updateAcceptedTermsVersion(
context,
externalId, externalId,
eulaVersion, eulaVersion,
privacyNoticeVersion, privacyNoticeVersion,
@ -1120,7 +1269,7 @@ export class UsersService {
try { try {
// extarnalIdの存在チェックを行う // extarnalIdの存在チェックを行う
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// ADB2Cからユーザー名を取得する // ADB2Cからユーザー名を取得する
const adb2cUser = await this.adB2cService.getUser(context, externalId); const adb2cUser = await this.adB2cService.getUser(context, externalId);
return adb2cUser.displayName; return adb2cUser.displayName;
@ -1150,4 +1299,76 @@ export class UsersService {
); );
} }
} }
/**
* IDを指定して
* @param context
* @param accountId ID
* @returns /
*/
private async getAccountInformation(
context: Context,
accountId: number,
): Promise<{
companyName: string;
adminEmails: string[];
}> {
// アカウントIDから企業名を取得する
const { company_name } = await this.accountsRepository.findAccountById(
context,
accountId,
);
// 管理者一覧を取得
const admins = await this.usersRepository.findAdminUsers(
context,
accountId,
);
const adminExternalIDs = admins.map((x) => x.external_id);
// ADB2Cから管理者IDを元にメールアドレスを取得する
const usersInfo = await this.adB2cService.getUsers(
context,
adminExternalIDs,
);
// 生のAzure AD B2Cのユーザー情報からメールアドレスを抽出する
const adminEmails = usersInfo.map((x) => {
const { emailAddress } = getUserNameAndMailAddress(x);
if (emailAddress == null) {
throw new Error('dealer admin email-address is not found');
}
return emailAddress;
});
return {
companyName: company_name,
adminEmails: adminEmails,
};
}
/**
*
* @param context
* @param accountId
* @returns primary admin user
*/
private async getPrimaryAdminUser(
context: Context,
accountId: number,
): Promise<EntityUser> {
const accountInfo = await this.accountsRepository.findAccountById(
context,
accountId,
);
if (!accountInfo || !accountInfo.primary_admin_user_id) {
throw new Error(`account or primary admin not found. id=${accountId}`);
}
const primaryAdmin = await this.usersRepository.findUserById(
context,
accountInfo.primary_admin_user_id,
);
return primaryAdmin;
}
} }

View File

@ -44,8 +44,14 @@ export class GetWorkflowsResponse {
export class WorkflowTypist { export class WorkflowTypist {
@ApiProperty({ description: 'タイピストユーザーの内部ID', required: false }) @ApiProperty({ description: 'タイピストユーザーの内部ID', required: false })
@IsOptional()
@IsInt()
@Type(() => Number)
typistId?: number; typistId?: number;
@ApiProperty({ description: 'タイピストグループの内部ID', required: false }) @ApiProperty({ description: 'タイピストグループの内部ID', required: false })
@IsOptional()
@IsInt()
@Type(() => Number)
typistGroupId?: number; typistGroupId?: number;
} }
@ -53,19 +59,19 @@ export class CreateWorkflowsRequest {
@ApiProperty({ description: 'Authorの内部ID' }) @ApiProperty({ description: 'Authorの内部ID' })
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
authorId: number; authorId: number;
@ApiProperty({ description: 'Worktypeの内部ID', required: false }) @ApiProperty({ description: 'Worktypeの内部ID', required: false })
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
worktypeId?: number; worktypeId?: number;
@ApiProperty({ description: 'テンプレートの内部ID', required: false }) @ApiProperty({ description: 'テンプレートの内部ID', required: false })
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
templateId?: number; templateId?: number;
@ApiProperty({ @ApiProperty({
description: 'ルーティング候補のタイピストユーザー/タイピストグループ', description: 'ルーティング候補のタイピストユーザー/タイピストグループ',
@ -84,7 +90,7 @@ export class UpdateWorkflowRequestParam {
@ApiProperty({ description: 'ワークフローの内部ID' }) @ApiProperty({ description: 'ワークフローの内部ID' })
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
workflowId: number; workflowId: number;
} }
@ -92,19 +98,19 @@ export class UpdateWorkflowRequest {
@ApiProperty({ description: 'Authorの内部ID' }) @ApiProperty({ description: 'Authorの内部ID' })
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
authorId: number; authorId: number;
@ApiProperty({ description: 'Worktypeの内部ID', required: false }) @ApiProperty({ description: 'Worktypeの内部ID', required: false })
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
worktypeId?: number; worktypeId?: number;
@ApiProperty({ description: 'テンプレートの内部ID', required: false }) @ApiProperty({ description: 'テンプレートの内部ID', required: false })
@IsOptional() @IsOptional()
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
templateId?: number; templateId?: number;
@ApiProperty({ @ApiProperty({
description: 'ルーティング候補のタイピストユーザー/タイピストグループ', description: 'ルーティング候補のタイピストユーザー/タイピストグループ',
@ -123,7 +129,7 @@ export class DeleteWorkflowRequestParam {
@ApiProperty({ description: 'ワークフローの内部ID' }) @ApiProperty({ description: 'ワークフローの内部ID' })
@Type(() => Number) @Type(() => Number)
@IsInt() @IsInt()
@Min(0) @Min(1)
workflowId: number; workflowId: number;
} }

View File

@ -4,6 +4,7 @@ import {
Get, Get,
HttpException, HttpException,
HttpStatus, HttpStatus,
Logger,
Param, Param,
Post, Post,
Req, Req,
@ -33,13 +34,14 @@ import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES } from '../../constants'; import { ADMIN_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, retrieveRequestId, retrieveIp } from '../../common/log';
import { WorkflowsService } from './workflows.service'; import { WorkflowsService } from './workflows.service';
import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('workflows') @ApiTags('workflows')
@Controller('workflows') @Controller('workflows')
export class WorkflowsController { export class WorkflowsController {
private readonly logger = new Logger(WorkflowsController.name);
constructor(private readonly workflowsService: WorkflowsService) {} constructor(private readonly workflowsService: WorkflowsService) {}
@ApiResponse({ @ApiResponse({
@ -75,6 +77,21 @@ export class WorkflowsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -84,7 +101,8 @@ export class WorkflowsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
const workflows = await this.workflowsService.getWorkflows(context, userId); const workflows = await this.workflowsService.getWorkflows(context, userId);
@ -134,6 +152,21 @@ export class WorkflowsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -143,7 +176,8 @@ export class WorkflowsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.workflowsService.createWorkflow( await this.workflowsService.createWorkflow(
context, context,
userId, userId,
@ -201,6 +235,21 @@ export class WorkflowsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -210,7 +259,8 @@ export class WorkflowsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.workflowsService.updateWorkflow( await this.workflowsService.updateWorkflow(
context, context,
userId, userId,
@ -267,6 +317,21 @@ export class WorkflowsController {
HttpStatus.UNAUTHORIZED, HttpStatus.UNAUTHORIZED,
); );
} }
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true }); const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) { if (!decodedAccessToken) {
throw new HttpException( throw new HttpException(
@ -276,7 +341,8 @@ export class WorkflowsController {
} }
const { userId } = decodedAccessToken as AccessToken; const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId); const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
await this.workflowsService.deleteWorkflow(context, userId, workflowId); await this.workflowsService.deleteWorkflow(context, userId, workflowId);
return {}; return {};
} }

View File

@ -118,7 +118,7 @@ describe('getWorkflows', () => {
await createWorkflowTypist(source, workflow3.id, undefined, userGroupId); await createWorkflowTypist(source, workflow3.id, undefined, userGroupId);
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//作成したデータを確認 //作成したデータを確認
{ {
@ -190,7 +190,7 @@ describe('getWorkflows', () => {
const { admin } = await makeTestAccount(source, { tier: 5 }); const { admin } = await makeTestAccount(source, { tier: 5 });
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
overrideAdB2cService(service, { overrideAdB2cService(service, {
getUsers: async () => [], getUsers: async () => [],
@ -212,7 +212,7 @@ describe('getWorkflows', () => {
const { account, admin } = await makeTestAccount(source, { tier: 5 }); const { account, admin } = await makeTestAccount(source, { tier: 5 });
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//DBアクセスに失敗するようにする //DBアクセスに失敗するようにする
const templatesService = module.get<WorkflowsRepositoryService>( const templatesService = module.get<WorkflowsRepositoryService>(
@ -292,7 +292,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.createWorkflow( await service.createWorkflow(
context, context,
@ -357,7 +357,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.createWorkflow( await service.createWorkflow(
context, context,
@ -421,7 +421,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.createWorkflow( await service.createWorkflow(
context, context,
@ -479,7 +479,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.createWorkflow( await service.createWorkflow(
context, context,
@ -543,7 +543,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
// 同一AuthorIDのワークフローを作成 // 同一AuthorIDのワークフローを作成
await service.createWorkflow( await service.createWorkflow(
@ -616,7 +616,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
await service.createWorkflow( await service.createWorkflow(
@ -673,7 +673,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -734,7 +734,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -794,7 +794,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -856,7 +856,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -924,7 +924,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -986,7 +986,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1057,7 +1057,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1124,7 +1124,7 @@ describe('createWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//DBアクセスに失敗するようにする //DBアクセスに失敗するようにする
const templatesService = module.get<WorkflowsRepositoryService>( const templatesService = module.get<WorkflowsRepositoryService>(
@ -1243,7 +1243,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.updateWorkflow( await service.updateWorkflow(
context, context,
@ -1333,7 +1333,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.updateWorkflow( await service.updateWorkflow(
context, context,
@ -1422,7 +1422,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.updateWorkflow( await service.updateWorkflow(
context, context,
@ -1505,7 +1505,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.updateWorkflow( await service.updateWorkflow(
context, context,
@ -1608,7 +1608,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.updateWorkflow( await service.updateWorkflow(
context, context,
@ -1687,7 +1687,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1730,7 +1730,7 @@ describe('updateWorkflow', () => {
}); });
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1804,7 +1804,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1873,7 +1873,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -1941,7 +1941,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2016,7 +2016,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2097,7 +2097,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2172,7 +2172,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2241,7 +2241,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2310,7 +2310,7 @@ describe('updateWorkflow', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//DBアクセスに失敗するようにする //DBアクセスに失敗するようにする
const workflowsRepositoryService = module.get<WorkflowsRepositoryService>( const workflowsRepositoryService = module.get<WorkflowsRepositoryService>(
@ -2401,7 +2401,7 @@ describe('deleteWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.deleteWorkflow(context, admin.external_id, workflow.id); await service.deleteWorkflow(context, admin.external_id, workflow.id);
@ -2452,7 +2452,7 @@ describe('deleteWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
await service.deleteWorkflow(context, admin.external_id, workflow1.id); await service.deleteWorkflow(context, admin.external_id, workflow1.id);
@ -2503,7 +2503,7 @@ describe('deleteWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2578,7 +2578,7 @@ describe('deleteWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//実行結果を確認 //実行結果を確認
try { try {
@ -2633,7 +2633,7 @@ describe('deleteWorkflows', () => {
} }
const service = module.get<WorkflowsService>(WorkflowsService); const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id); const context = makeContext(admin.external_id, 'requestId');
//DBアクセスに失敗するようにする //DBアクセスに失敗するようにする
const workflowsRepositoryService = module.get<WorkflowsRepositoryService>( const workflowsRepositoryService = module.get<WorkflowsRepositoryService>(

View File

@ -41,10 +41,11 @@ export class WorkflowsService {
); );
try { try {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
// DBからワークフロー一覧を取得 // DBからワークフロー一覧を取得
const workflowRecords = await this.workflowsRepository.getWorkflows( const workflowRecords = await this.workflowsRepository.getWorkflows(
context,
accountId, accountId,
); );
@ -165,9 +166,10 @@ export class WorkflowsService {
); );
try { try {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
await this.workflowsRepository.createtWorkflows( await this.workflowsRepository.createtWorkflows(
context,
accountId, accountId,
authorId, authorId,
typists, typists,
@ -253,9 +255,10 @@ export class WorkflowsService {
); );
try { try {
const { account_id: accountId } = const { account_id: accountId } =
await this.usersRepository.findUserByExternalId(externalId); await this.usersRepository.findUserByExternalId(context, externalId);
await this.workflowsRepository.updatetWorkflow( await this.workflowsRepository.updatetWorkflow(
context,
accountId, accountId,
workflowId, workflowId,
authorId, authorId,
@ -336,6 +339,7 @@ export class WorkflowsService {
); );
try { try {
const { account } = await this.usersRepository.findUserByExternalId( const { account } = await this.usersRepository.findUserByExternalId(
context,
externalId, externalId,
); );
@ -345,7 +349,11 @@ export class WorkflowsService {
); );
} }
await this.workflowsRepository.deleteWorkflow(account.id, workflowId); await this.workflowsRepository.deleteWorkflow(
context,
account.id,
workflowId,
);
} catch (e) { } catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) { if (e instanceof Error) {

View File

@ -1,3 +1,6 @@
import { ADB2C_SIGN_IN_TYPE } from '../../../constants';
import { AdB2cUser } from '../types/types';
export const isPromiseRejectedResult = ( export const isPromiseRejectedResult = (
data: unknown, data: unknown,
): data is PromiseRejectedResult => { ): data is PromiseRejectedResult => {
@ -8,3 +11,12 @@ export const isPromiseRejectedResult = (
'reason' in data 'reason' in data
); );
}; };
// 生のAdB2cUserのレスポンスから表示名とメールアドレスを取得する
export const getUserNameAndMailAddress = (user: AdB2cUser) => {
const { displayName, identities } = user;
const emailAddress = identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
)?.issuerAssignedId;
return { displayName, emailAddress };
};

View File

@ -4,64 +4,205 @@ import { sign } from '../../common/jwt';
import sendgrid from '@sendgrid/mail'; import sendgrid from '@sendgrid/mail';
import { getPrivateKey } from '../../common/jwt/jwt'; import { getPrivateKey } from '../../common/jwt/jwt';
import { Context } from '../../common/log'; import { Context } from '../../common/log';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import {
PRIMARY_ADMIN_NAME,
AUTHOR_NAME,
CUSTOMER_NAME,
DEALER_NAME,
FILE_NAME,
LICENSE_QUANTITY,
PO_NUMBER,
TOP_URL,
USER_EMAIL,
USER_NAME,
TYPIST_NAME,
VERIFY_LINK,
TEMPORARY_PASSWORD,
} from '../../templates/constants';
@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 emailConfirmLifetime: number;
private readonly appDomain: string; private readonly appDomain: string;
private readonly mailFrom: string;
private readonly templateEmailVerifyHtml: string;
private readonly templateEmailVerifyText: string;
private readonly templateU101Html: string;
private readonly templateU101Text: string;
private readonly templateU102Html: string;
private readonly templateU102Text: string;
private readonly templateU105Html: string;
private readonly templateU105Text: string;
private readonly templateU106Html: string;
private readonly templateU106Text: string;
private readonly templateU107Html: string;
private readonly templateU107Text: string;
private readonly templateU108Html: string;
private readonly templateU108Text: string;
private readonly templateU109Html: string;
private readonly templateU109Text: string;
private readonly templateU111Html: string;
private readonly templateU111Text: string;
private readonly templateU112Html: string;
private readonly templateU112Text: string;
// U-112のテンプレート差分親アカウントがない場合
private readonly templateU112NoParentHtml: string;
private readonly templateU112NoParentText: string;
private readonly templateU113Html: string;
private readonly templateU113Text: string;
private readonly templateU114Html: string;
private readonly templateU114Text: string;
private readonly templateU115Html: string;
private readonly templateU115Text: string;
private readonly templateU117Html: string;
private readonly templateU117Text: string;
constructor(private readonly configService: ConfigService) { constructor(private readonly configService: ConfigService) {
this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN'); this.appDomain = this.configService.getOrThrow<string>('APP_DOMAIN');
this.mailFrom = this.configService.getOrThrow<string>('MAIL_FROM');
this.emailConfirmLifetime = this.configService.getOrThrow<number>( this.emailConfirmLifetime = this.configService.getOrThrow<number>(
'EMAIL_CONFIRM_LIFETIME', '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);
}
/** // メールテンプレートを読み込む
* Email認証用のメールコンテンツを作成する
* @param accountId ID
* @param userId ID
* @param email
* @returns
*/
async createMailContentFromEmailConfirm(
context: Context,
accountId: number,
userId: number,
email: string,
): Promise<{ subject: string; text: string; html: string }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createMailContentFromEmailConfirm.name
} | params: { ` +
`accountId: ${accountId},` +
`userId: ${userId} };`,
);
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
{ {
accountId, this.templateEmailVerifyHtml = readFileSync(
userId, path.resolve(__dirname, `../../templates/template_email_verify.html`),
email, 'utf-8',
}, );
this.emailConfirmLifetime, this.templateEmailVerifyText = readFileSync(
privateKey, path.resolve(__dirname, `../../templates/template_email_verify.txt`),
'utf-8',
); );
const path = 'mail-confirm/';
this.logger.log( this.templateU101Html = readFileSync(
`[OUT] [${context.getTrackingId()}] ${ path.resolve(__dirname, `../../templates/template_U_101.html`),
this.createMailContentFromEmailConfirm.name 'utf-8',
}`,
); );
return { this.templateU101Text = readFileSync(
subject: 'Verify your new account', path.resolve(__dirname, `../../templates/template_U_101.txt`),
text: `The verification URL. ${this.appDomain}${path}?verify=${token}`, 'utf-8',
html: `<p>The verification URL.<p><a href="${this.appDomain}${path}?verify=${token}">${this.appDomain}${path}?verify=${token}</a>`, );
}; this.templateU102Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_102.html`),
'utf-8',
);
this.templateU102Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_102.txt`),
'utf-8',
);
this.templateU105Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_105.html`),
'utf-8',
);
this.templateU105Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_105.txt`),
'utf-8',
);
this.templateU106Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_106.html`),
'utf-8',
);
this.templateU106Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_106.txt`),
'utf-8',
);
this.templateU107Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_107.html`),
'utf-8',
);
this.templateU107Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_107.txt`),
'utf-8',
);
this.templateU108Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_108.html`),
'utf-8',
);
this.templateU108Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_108.txt`),
'utf-8',
);
this.templateU109Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_109.html`),
'utf-8',
);
this.templateU109Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_109.txt`),
'utf-8',
);
this.templateU111Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_111.html`),
'utf-8',
);
this.templateU111Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_111.txt`),
'utf-8',
);
this.templateU112Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_112.html`),
'utf-8',
);
this.templateU112Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_112.txt`),
'utf-8',
);
this.templateU112NoParentHtml = readFileSync(
path.resolve(
__dirname,
`../../templates/template_U_112_no_parent.html`,
),
'utf-8',
);
this.templateU112NoParentText = readFileSync(
path.resolve(__dirname, `../../templates/template_U_112_no_parent.txt`),
'utf-8',
);
this.templateU113Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_113.html`),
'utf-8',
);
this.templateU113Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_113.txt`),
'utf-8',
);
this.templateU114Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_114.html`),
'utf-8',
);
this.templateU114Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_114.txt`),
'utf-8',
);
this.templateU115Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_115.html`),
'utf-8',
);
this.templateU115Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_115.txt`),
'utf-8',
);
this.templateU117Html = readFileSync(
path.resolve(__dirname, `../../templates/template_U_117.html`),
'utf-8',
);
this.templateU117Text = readFileSync(
path.resolve(__dirname, `../../templates/template_U_117.txt`),
'utf-8',
);
}
} }
/** /**
@ -105,10 +246,704 @@ export class SendGridService {
}; };
} }
/**
* U-101使
* @param context
* @param customerMail
* @param customerAccountName
* @returns mail with u101
*/
async sendMailWithU101(
context: Context,
customerMail: string,
customerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
);
try {
const subject = 'Account Registered Notification [U-101]';
const html = this.templateU101Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(TOP_URL, this.appDomain);
const text = this.templateU101Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(TOP_URL, this.appDomain);
await this.sendMail(
context,
[customerMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU101.name}`,
);
}
}
/**
* U-102使
* @param context
* @param adminEmail (primary)
* @param accountId ID
* @param userId ID
* @returns mail with u102
*/
async sendMailWithU102(
context: Context,
adminEmail: string,
accountId: number,
userId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU102.name}`,
);
try {
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
{
accountId,
userId,
email: adminEmail,
},
this.emailConfirmLifetime,
privateKey,
);
const path = 'mail-confirm/';
const verifyUrl = `${this.appDomain}${path}?verify=${token}`;
const subject = 'User Registration Notification [U-102]';
const html = this.templateU102Html.replaceAll(VERIFY_LINK, verifyUrl);
const text = this.templateU102Text.replaceAll(VERIFY_LINK, verifyUrl);
await this.sendMail(
context,
[adminEmail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU102.name}`,
);
}
}
/**
* U-105使
* @param context
* @param customerMails (primary/secondary)
* @param customerAccountName
* @param lisenceCount
* @param poNumber PO番号
* @param dealerEmails (primary/secondary)
* @param dealerAccountName
* @returns mail with u105
*/
async sendMailWithU105(
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU105.name}`,
);
try {
const subject = 'License Requested Notification [U-105]';
// メールの本文を作成する
const html = this.templateU105Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
const text = this.templateU105Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
// メールを送信する
this.sendMail(
context,
customerMails,
dealerEmails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU105.name}`,
);
}
}
/**
* U-106使
* @param context
* @param cancelUserEmailAddress (primary/secondary)
* @param customerAccountName
* @param lisenceCount
* @param poNumber PO番号
* @param dealerEmails (primary/secondary)
* @param dealerAccountName
* @returns mail with u106
*/
async sendMailWithU106(
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU106.name}`,
);
try {
const subject = 'Cancelled License Order Notification [U-106]';
// メールの本文を作成する
const html = this.templateU106Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
const text = this.templateU106Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
// メールを送信する
this.sendMail(
context,
customerMails,
dealerEmails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU106.name}`,
);
}
}
/**
* U-107使
* @param context
* @param cancelUserEmailAddress (primary/secondary)
* @param customerAccountName
* @param lisenceCount
* @param poNumber PO番号
* @param dealerEmails (primary/secondary)
* @param dealerAccountName
* @returns mail with u107
*/
async sendMailWithU107(
context: Context,
customerMails: string[],
customerAccountName: string,
lisenceCount: number,
poNumber: string,
dealerEmails: string[],
dealerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU107.name}`,
);
try {
const subject = 'License Issued Notification [U-107]';
// メールの本文を作成する
const html = this.templateU107Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
const text = this.templateU107Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
// メールを送信する
this.sendMail(
context,
customerMails,
dealerEmails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU107.name}`,
);
}
}
/**
* U-109使
* @param context context
* @param dealerEmails (primary/secondary)
* @param dealerAccountName
* @param lisenceCount
* @param poNumber PO番号
* @param customerMails (primary/secondary)
* @param customerAccountName
* @returns
*/
async sendMailWithU109(
context: Context,
dealerEmails: string[],
dealerAccountName: string,
lisenceCount: number,
poNumber: string,
customerMails: string[],
customerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU109.name}`,
);
try {
const subject = 'License Returned Notification [U-109]';
// メールの本文を作成する
const html = this.templateU109Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
const text = this.templateU109Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PO_NUMBER, poNumber)
.replaceAll(LICENSE_QUANTITY, `${lisenceCount}`);
// メールを送信する
this.sendMail(
context,
dealerEmails,
customerMails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU109.name}`,
);
}
}
/**
* U-108使
* @param context
* @param userName
* @param userMail
* @param customerAdminMails (primary/secondary)
* @param customerAccountName
* @param dealerAccountName
* @returns mail with u108
*/
async sendMailWithU108(
context: Context,
userName: string,
userMail: string,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU108.name}`,
);
try {
const subject = 'License Assigned Notification [U-108]';
// メールの本文を作成する
const html = this.templateU108Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, this.appDomain);
const text = this.templateU108Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, this.appDomain);
const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail];
// メールを送信する
this.sendMail(
context,
customerAdminMails,
ccAddress,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU108.name}`,
);
}
}
/**
* U-111使
* @param context
* @param primaryAdminName (primary)
* @param primaryAdminMail (primary)
* @param customerAccountName
* @returns mail with u111
*/
async sendMailWithU111(
context: Context,
primaryAdminName: string,
primaryAdminMail: string,
customerAccountName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU111.name}`,
);
try {
const subject = 'Account Deleted Notification [U-111]';
// メールの本文を作成する
const html = this.templateU111Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
const text = this.templateU111Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
// メールを送信する
this.sendMail(
context,
[primaryAdminMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU111.name}`,
);
}
}
/**
* U-112使
* @param context
* @param primaryAdminName (primary)
* @param primaryAdminMail (primary)
* @param customerAccountName
* @param dealerAccountName
* @returns mail with u112
*/
async sendMailWithU112(
context: Context,
primaryAdminName: string,
primaryAdminMail: string,
customerAccountName: string,
dealerAccountName: string | null,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU112.name}`,
);
try {
const subject = 'Account Edit Notification [U-112]';
let html: string;
let text: string;
// 親アカウントがない場合は別のテンプレートを使用する
if (dealerAccountName === null) {
// メールの本文を作成する
html = this.templateU112NoParentHtml
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
text = this.templateU112NoParentText
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
} else {
html = this.templateU112Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
text = this.templateU112Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TOP_URL, this.appDomain);
}
// メールを送信する
this.sendMail(
context,
[primaryAdminMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU112.name}`,
);
}
}
/**
* U-113使
* @param context
* @param userMail
* @param primaryAdminName (primary)
* @param temporaryPassword
* @returns mail with u113
*/
async sendMailWithU113(
context: Context,
userMail: string,
primaryAdminName: string,
temporaryPassword: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
);
try {
const subject = 'Temporary password [U-113]';
// メールの本文を作成する
const html = this.templateU113Html
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TEMPORARY_PASSWORD, temporaryPassword);
const text = this.templateU113Text
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(TEMPORARY_PASSWORD, temporaryPassword);
// メールを送信する
this.sendMail(
context,
[userMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU113.name}`,
);
}
}
/**
* U-114使
* @param context
* @param accountId ID
* @param userId ID
* @param userMail
* @param primaryAdminName (primary)
* @returns mail with u114
*/
async sendMailWithU114(
context: Context,
accountId: number,
userId: number,
userMail: string,
primaryAdminName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU114.name}`,
);
try {
// ユーザー認証用のトークンを作成する
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
{
accountId,
userId,
email: userMail,
},
this.emailConfirmLifetime,
privateKey,
);
const path = 'mail-confirm/user/';
const verifyLink = `${this.appDomain}${path}?verify=${token}`;
const subject = 'User Registration Notification [U-114]';
// メールの本文を作成する
const html = this.templateU114Html
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(VERIFY_LINK, verifyLink);
const text = this.templateU114Text
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName)
.replaceAll(VERIFY_LINK, verifyLink);
// メールを送信する
this.sendMail(
context,
[userMail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU114.name}`,
);
}
}
/**
* U-115使
* @param context
* @param userName
* @param userMail
* @param primaryAdminName (primary)
* @param adminMails (primary/secondary)
* @returns mail with u115
*/
async sendMailWithU115(
context: Context,
userName: string,
userMail: string,
primaryAdminName: string,
adminMails: string[],
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU115.name}`,
);
try {
const subject = 'Edit User Notification [U-115]';
// メールの本文を作成する
const html = this.templateU115Html
.replaceAll(USER_NAME, userName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName);
const text = this.templateU115Text
.replaceAll(USER_NAME, userName)
.replaceAll(PRIMARY_ADMIN_NAME, primaryAdminName);
// 管理者ユーザーの情報を変更した場合にはTOに管理者のメールアドレスを設定するので、CCには管理者のメールアドレスを設定しない
const ccAdminMails = adminMails.filter((x) => x !== userMail);
// メールを送信する
this.sendMail(
context,
[userMail],
ccAdminMails,
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU115.name}`,
);
}
}
/**
* U-117使
* @param context
* @param authorEmail Authorのメールアドレス
* @param typistEmail Typistのメールアドレス
* @param authorName Authorの名前
* @param fileName
* @param typistName Typistの名前
* @param adminName
* @returns mail with u117
*/
async sendMailWithU117(
context: Context,
authorEmail: string,
typistEmail: string,
authorName: string,
fileName: string,
typistName: string,
adminName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
);
try {
const subject = 'Transcription Completion Notification [U-117]';
// メールの本文を作成する
const html = this.templateU117Html
.replaceAll(AUTHOR_NAME, authorName)
.replaceAll(FILE_NAME, fileName)
.replaceAll(TYPIST_NAME, typistName)
.replaceAll(PRIMARY_ADMIN_NAME, adminName);
const text = this.templateU117Text
.replaceAll(AUTHOR_NAME, authorName)
.replaceAll(FILE_NAME, fileName)
.replaceAll(TYPIST_NAME, typistName)
.replaceAll(PRIMARY_ADMIN_NAME, adminName);
// メールを送信する
this.sendMail(
context,
[authorEmail, typistEmail],
[],
this.mailFrom,
subject,
text,
html,
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMailWithU117.name}`,
);
}
}
/** /**
* *
* @param context * @param context
* @param to * @param to
* @param cc
* @param from * @param from
* @param subject * @param subject
* @param text * @param text
@ -117,7 +952,8 @@ export class SendGridService {
*/ */
async sendMail( async sendMail(
context: Context, context: Context,
to: string, to: string[],
cc: string[],
from: string, from: string,
subject: string, subject: string,
text: string, text: string,
@ -130,9 +966,8 @@ export class SendGridService {
from: { from: {
email: from, email: from,
}, },
to: { to: to.map((v) => ({ email: v })),
email: to, cc: cc.map((v) => ({ email: v })),
},
subject: subject, subject: subject,
text: text, text: text,
html: html, html: html,

View File

@ -31,11 +31,6 @@ import {
LICENSE_ISSUE_STATUS, LICENSE_ISSUE_STATUS,
TIERS, TIERS,
} from '../../constants'; } from '../../constants';
import {
LicenseSummaryInfo,
PartnerLicenseInfoForRepository,
PartnerInfoFromDb,
} from '../../features/accounts/types/types';
import { import {
AccountNotFoundError, AccountNotFoundError,
AdminUserNotFoundError, AdminUserNotFoundError,
@ -57,11 +52,20 @@ import { AudioOptionItem } from '../audio_option_items/entity/audio_option_item.
import { UserGroup } from '../user_groups/entity/user_group.entity'; import { UserGroup } from '../user_groups/entity/user_group.entity';
import { UserGroupMember } from '../user_groups/entity/user_group_member.entity'; import { UserGroupMember } from '../user_groups/entity/user_group_member.entity';
import { TemplateFile } from '../template_files/entity/template_file.entity'; import { TemplateFile } from '../template_files/entity/template_file.entity';
import {
insertEntity,
insertEntities,
updateEntity,
deleteEntity,
} from '../../common/repository';
import { Context } from '../../common/log';
import { LicenseSummaryInfo, PartnerInfoFromDb, PartnerLicenseInfoForRepository } from '../../features/accounts/types/types';
@Injectable() @Injectable()
export class AccountsRepositoryService { export class AccountsRepositoryService {
// クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
* *
* @param companyName * @param companyName
@ -71,6 +75,7 @@ export class AccountsRepositoryService {
* @returns create * @returns create
*/ */
async create( async create(
context: Context,
companyName: string, companyName: string,
country: string, country: string,
dealerAccountId: number | null, dealerAccountId: number | null,
@ -88,7 +93,13 @@ export class AccountsRepositoryService {
async (entityManager) => { async (entityManager) => {
const repo = entityManager.getRepository(Account); const repo = entityManager.getRepository(Account);
const newAccount = repo.create(account); const newAccount = repo.create(account);
const persisted = await repo.save(newAccount); const persisted = await insertEntity(
Account,
repo,
newAccount,
this.isCommentOut,
context,
);
return persisted; return persisted;
}, },
); );
@ -100,10 +111,16 @@ export class AccountsRepositoryService {
* @param account * @param account
* @returns update * @returns update
*/ */
async update(account: Account): Promise<UpdateResult> { async update(context: Context, account: Account): Promise<UpdateResult> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(Account); const repo = entityManager.getRepository(Account);
return await repo.update({ id: account.id }, account); return await updateEntity(
repo,
{ id: account.id },
account,
this.isCommentOut,
context,
);
}); });
} }
@ -120,6 +137,7 @@ export class AccountsRepositoryService {
* @returns account/admin user * @returns account/admin user
*/ */
async createAccount( async createAccount(
context: Context,
companyName: string, companyName: string,
country: string, country: string,
dealerAccountId: number | undefined, dealerAccountId: number | undefined,
@ -140,7 +158,13 @@ export class AccountsRepositoryService {
} }
const accountsRepo = entityManager.getRepository(Account); const accountsRepo = entityManager.getRepository(Account);
const newAccount = accountsRepo.create(account); const newAccount = accountsRepo.create(account);
const persistedAccount = await accountsRepo.save(newAccount); const persistedAccount = await insertEntity(
Account,
accountsRepo,
newAccount,
this.isCommentOut,
context,
);
// 作成されたAccountのIDを使用してユーザーを作成 // 作成されたAccountのIDを使用してユーザーを作成
const user = new User(); const user = new User();
@ -155,14 +179,23 @@ export class AccountsRepositoryService {
} }
const usersRepo = entityManager.getRepository(User); const usersRepo = entityManager.getRepository(User);
const newUser = usersRepo.create(user); const newUser = usersRepo.create(user);
const persistedUser = await usersRepo.save(newUser); const persistedUser = await insertEntity(
User,
usersRepo,
newUser,
this.isCommentOut,
context,
);
// アカウントに管理者を設定して更新 // アカウントに管理者を設定して更新
persistedAccount.primary_admin_user_id = persistedUser.id; persistedAccount.primary_admin_user_id = persistedUser.id;
const result = await accountsRepo.update( const result = await updateEntity(
accountsRepo,
{ id: persistedAccount.id }, { id: persistedAccount.id },
persistedAccount, persistedAccount,
this.isCommentOut,
context,
); );
// 想定外の更新が行われた場合はロールバックを行った上でエラー送出 // 想定外の更新が行われた場合はロールバックを行った上でエラー送出
@ -179,7 +212,13 @@ export class AccountsRepositoryService {
} }
const sortCriteriaRepo = entityManager.getRepository(SortCriteria); const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
const newSortCriteria = sortCriteriaRepo.create(sortCriteria); const newSortCriteria = sortCriteriaRepo.create(sortCriteria);
await sortCriteriaRepo.save(newSortCriteria); await insertEntity(
SortCriteria,
sortCriteriaRepo,
newSortCriteria,
this.isCommentOut,
context,
);
return { newAccount: persistedAccount, adminUser: persistedUser }; return { newAccount: persistedAccount, adminUser: persistedUser };
}); });
@ -190,19 +229,31 @@ export class AccountsRepositoryService {
* @param accountId * @param accountId
* @returns delete * @returns delete
*/ */
async deleteAccount(accountId: number, userId: number): Promise<void> { async deleteAccount(
context: Context,
accountId: number,
userId: number,
): Promise<void> {
await this.dataSource.transaction(async (entityManager) => { await this.dataSource.transaction(async (entityManager) => {
const accountsRepo = entityManager.getRepository(Account); const accountsRepo = entityManager.getRepository(Account);
const usersRepo = entityManager.getRepository(User); const usersRepo = entityManager.getRepository(User);
const sortCriteriaRepo = entityManager.getRepository(SortCriteria); const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
// ソート条件を削除 // ソート条件を削除
await sortCriteriaRepo.delete({ await deleteEntity(
user_id: userId, sortCriteriaRepo,
}); { user_id: userId },
this.isCommentOut,
context,
);
// プライマリ管理者を削除 // プライマリ管理者を削除
await usersRepo.delete({ id: userId }); await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context);
// アカウントを削除 // アカウントを削除
await accountsRepo.delete({ id: accountId }); await deleteEntity(
accountsRepo,
{ id: accountId },
this.isCommentOut,
context,
);
}); });
} }
@ -211,11 +262,12 @@ export class AccountsRepositoryService {
* @param id * @param id
* @returns account * @returns account
*/ */
async findAccountById(id: number): Promise<Account> { async findAccountById(context: Context, id: number): Promise<Account> {
const account = await this.dataSource.getRepository(Account).findOne({ const account = await this.dataSource.getRepository(Account).findOne({
where: { where: {
id: id, id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!account) { if (!account) {
@ -234,6 +286,7 @@ export class AccountsRepositoryService {
* @returns expiringSoonLicense * @returns expiringSoonLicense
*/ */
private async getExpiringSoonLicense( private async getExpiringSoonLicense(
context: Context,
entityManager: EntityManager, entityManager: EntityManager,
id: number, id: number,
currentDate: Date, currentDate: Date,
@ -248,6 +301,7 @@ export class AccountsRepositoryService {
expiry_date: Between(currentDate, expiringSoonDate), expiry_date: Between(currentDate, expiringSoonDate),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED), status: Not(LICENSE_ALLOCATED_STATUS.DELETED),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return expiringSoonLicense; return expiringSoonLicense;
@ -262,6 +316,7 @@ export class AccountsRepositoryService {
* @returns allocatableLicenseWithMargin * @returns allocatableLicenseWithMargin
*/ */
private async getAllocatableLicenseWithMargin( private async getAllocatableLicenseWithMargin(
context: Context,
entityManager: EntityManager, entityManager: EntityManager,
id: number, id: number,
expiringSoonDate: Date, expiringSoonDate: Date,
@ -288,6 +343,7 @@ export class AccountsRepositoryService {
expiry_date: IsNull(), expiry_date: IsNull(),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return allocatableLicenseWithMargin; return allocatableLicenseWithMargin;
@ -301,6 +357,7 @@ export class AccountsRepositoryService {
* @returns licenseSummary * @returns licenseSummary
*/ */
async getLicenseSummaryInfo( async getLicenseSummaryInfo(
context: Context,
id: number, id: number,
currentDate: Date, currentDate: Date,
expiringSoonDate: Date, expiringSoonDate: Date,
@ -318,14 +375,21 @@ export class AccountsRepositoryService {
{ {
account_id: id, account_id: id,
expiry_date: MoreThanOrEqual(currentDate), expiry_date: MoreThanOrEqual(currentDate),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED), status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
}, },
{ {
account_id: id, account_id: id,
expiry_date: IsNull(), expiry_date: IsNull(),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED), status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 有効な総ライセンス数のうち、ユーザーに割り当て済みのライセンス数を取得する // 有効な総ライセンス数のうち、ユーザーに割り当て済みのライセンス数を取得する
@ -344,6 +408,7 @@ export class AccountsRepositoryService {
status: LICENSE_ALLOCATED_STATUS.ALLOCATED, status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 総ライセンス数のうち、ユーザーに割り当てたことがあるが、現在は割り当て解除され誰にも割り当たっていないライセンス数を取得する // 総ライセンス数のうち、ユーザーに割り当てたことがあるが、現在は割り当て解除され誰にも割り当たっていないライセンス数を取得する
@ -360,6 +425,7 @@ export class AccountsRepositoryService {
status: LICENSE_ALLOCATED_STATUS.REUSABLE, status: LICENSE_ALLOCATED_STATUS.REUSABLE,
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 総ライセンス数のうち、一度もユーザーに割り当てたことのないライセンス数を取得する // 総ライセンス数のうち、一度もユーザーに割り当てたことのないライセンス数を取得する
@ -376,10 +442,12 @@ export class AccountsRepositoryService {
status: LICENSE_ALLOCATED_STATUS.UNALLOCATED, status: LICENSE_ALLOCATED_STATUS.UNALLOCATED,
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 有効期限が現在日付からしきい値以内のライセンス数を取得する // 有効期限が現在日付からしきい値以内のライセンス数を取得する
const expiringSoonLicense = await this.getExpiringSoonLicense( const expiringSoonLicense = await this.getExpiringSoonLicense(
context,
entityManager, entityManager,
id, id,
currentDate, currentDate,
@ -392,6 +460,7 @@ export class AccountsRepositoryService {
from_account_id: id, from_account_id: id,
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する // 未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する
@ -402,19 +471,22 @@ export class AccountsRepositoryService {
.andWhere('license_orders.status = :status', { .andWhere('license_orders.status = :status', {
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}) })
.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`)
.getRawOne(); .getRawOne();
const issueRequesting = parseInt(result.sum, 10) || 0; const issueRequesting = parseInt(result.sum, 10) || 0;
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う // 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
const allocatableLicenseWithMargin = const allocatableLicenseWithMargin =
await this.getAllocatableLicenseWithMargin( await this.getAllocatableLicenseWithMargin(
context,
entityManager, entityManager,
id, id,
expiringSoonDate, expiringSoonDate,
); );
// アカウントのロック状態を取得する // アカウントのロック状態を取得する
const isStorageAvailable = (await this.findAccountById(id)).locked; const isStorageAvailable = (await this.findAccountById(context, id))
.locked;
let licenseSummary = new LicenseSummaryInfo(); let licenseSummary = new LicenseSummaryInfo();
licenseSummary = { licenseSummary = {
@ -442,6 +514,7 @@ export class AccountsRepositoryService {
* @returns issueRequesting * @returns issueRequesting
*/ */
private async getAccountLicenseOrderStatus( private async getAccountLicenseOrderStatus(
context: Context,
id: number, id: number,
currentDate: Date, currentDate: Date,
entityManager: EntityManager, entityManager: EntityManager,
@ -459,14 +532,21 @@ export class AccountsRepositoryService {
{ {
account_id: id, account_id: id,
expiry_date: MoreThanOrEqual(currentDate), expiry_date: MoreThanOrEqual(currentDate),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED), status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
}, },
{ {
account_id: id, account_id: id,
expiry_date: IsNull(), expiry_date: IsNull(),
status: Not(LICENSE_ALLOCATED_STATUS.DELETED), status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 子アカウントからの、未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する // 子アカウントからの、未発行状態あるいは発行キャンセルされた注文の総ライセンス数を取得する
@ -477,6 +557,7 @@ export class AccountsRepositoryService {
.andWhere('license_orders.status = :status', { .andWhere('license_orders.status = :status', {
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}) })
.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`)
.getRawOne(); .getRawOne();
const issuedRequested = parseInt(issuedRequestedSqlResult.sum, 10) || 0; const issuedRequested = parseInt(issuedRequestedSqlResult.sum, 10) || 0;
@ -488,6 +569,7 @@ export class AccountsRepositoryService {
.andWhere('license_orders.status = :status', { .andWhere('license_orders.status = :status', {
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}) })
.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`)
.getRawOne(); .getRawOne();
const issuedRequesting = parseInt(issuedRequestingSqlResult.sum, 10) || 0; const issuedRequesting = parseInt(issuedRequestingSqlResult.sum, 10) || 0;
@ -508,6 +590,7 @@ export class AccountsRepositoryService {
* @returns childrenPartnerLicensesFromRepository: リポジトリから取得した子アカウントのライセンス情報 * @returns childrenPartnerLicensesFromRepository: リポジトリから取得した子アカウントのライセンス情報
*/ */
async getPartnerLicense( async getPartnerLicense(
context: Context,
id: number, id: number,
currentDate: Date, currentDate: Date,
expiringSoonDate: Date, expiringSoonDate: Date,
@ -526,6 +609,7 @@ export class AccountsRepositoryService {
where: { where: {
id: id, id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!ownAccount) { if (!ownAccount) {
throw new AccountNotFoundError(`Account is Not Found.`); throw new AccountNotFoundError(`Account is Not Found.`);
@ -533,6 +617,7 @@ export class AccountsRepositoryService {
// 自アカウントのライセンス注文状況を取得する // 自アカウントのライセンス注文状況を取得する
const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus( const ownLicenseOrderStatus = await this.getAccountLicenseOrderStatus(
context,
id, id,
currentDate, currentDate,
entityManager, entityManager,
@ -558,6 +643,7 @@ export class AccountsRepositoryService {
}, },
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 各子アカウントのライセンス注文状況を取得する // 各子アカウントのライセンス注文状況を取得する
@ -566,6 +652,7 @@ export class AccountsRepositoryService {
for (const childAccount of childAccounts) { for (const childAccount of childAccounts) {
// ライセンス注文状況を取得する // ライセンス注文状況を取得する
const childLicenseOrderStatus = await this.getAccountLicenseOrderStatus( const childLicenseOrderStatus = await this.getAccountLicenseOrderStatus(
context,
childAccount.id, childAccount.id,
currentDate, currentDate,
entityManager, entityManager,
@ -576,6 +663,7 @@ export class AccountsRepositoryService {
let allocatableLicenseWithMargin: number = 0; let allocatableLicenseWithMargin: number = 0;
if (childAccount.tier === TIERS.TIER5) { if (childAccount.tier === TIERS.TIER5) {
expiringSoonLicense = await this.getExpiringSoonLicense( expiringSoonLicense = await this.getExpiringSoonLicense(
context,
entityManager, entityManager,
childAccount.id, childAccount.id,
currentDate, currentDate,
@ -583,6 +671,7 @@ export class AccountsRepositoryService {
); );
allocatableLicenseWithMargin = allocatableLicenseWithMargin =
await this.getAllocatableLicenseWithMargin( await this.getAllocatableLicenseWithMargin(
context,
entityManager, entityManager,
childAccount.id, childAccount.id,
expiringSoonDate, expiringSoonDate,
@ -612,6 +701,7 @@ export class AccountsRepositoryService {
where: { where: {
parent_account_id: id, parent_account_id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return { return {
@ -626,11 +716,12 @@ export class AccountsRepositoryService {
* Dealer(Tier4) * Dealer(Tier4)
* @returns dealer accounts * @returns dealer accounts
*/ */
async findDealerAccounts(): Promise<Account[]> { async findDealerAccounts(context: Context): Promise<Account[]> {
const accounts = await this.dataSource.getRepository(Account).find({ const accounts = await this.dataSource.getRepository(Account).find({
where: { where: {
tier: TIERS.TIER4, tier: TIERS.TIER4,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return accounts; return accounts;
@ -642,7 +733,10 @@ export class AccountsRepositoryService {
* @param targetAccountId * @param targetAccountId
* @returns accountIds * @returns accountIds
*/ */
async getHierarchyParents(targetAccountId: number): Promise<number[]> { async getHierarchyParents(
context: Context,
targetAccountId: number,
): Promise<number[]> {
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;
@ -655,6 +749,7 @@ export class AccountsRepositoryService {
where: { where: {
id: currentAccountId, id: currentAccountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!account) { if (!account) {
break; break;
@ -675,9 +770,14 @@ export class AccountsRepositoryService {
* IDとPOナンバーに紐づくライセンス発行をキャンセルする * IDとPOナンバーに紐づくライセンス発行をキャンセルする
* @param orderedAccountId:キャンセルしたい発行の注文元アカウントID * @param orderedAccountId:キャンセルしたい発行の注文元アカウントID
* @param poNumber:POナンバー * @param poNumber:POナンバー
* @returns { canceledIssueLicenseOrderId } : ID
*/ */
async cancelIssue(orderedAccountId: number, poNumber: string): Promise<void> { async cancelIssue(
await this.dataSource.transaction(async (entityManager) => { context: Context,
orderedAccountId: number,
poNumber: string,
): Promise<{ canceledIssueLicenseOrderId: number }> {
return await this.dataSource.transaction(async (entityManager) => {
const orderRepo = entityManager.getRepository(LicenseOrder); const orderRepo = entityManager.getRepository(LicenseOrder);
// キャンセル対象の発行を取得 // キャンセル対象の発行を取得
@ -687,6 +787,7 @@ export class AccountsRepositoryService {
po_number: poNumber, po_number: poNumber,
status: LICENSE_ISSUE_STATUS.ISSUED, status: LICENSE_ISSUE_STATUS.ISSUED,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// キャンセル対象の発行が存在しない場合エラー // キャンセル対象の発行が存在しない場合エラー
@ -715,6 +816,7 @@ export class AccountsRepositoryService {
order_id: targetOrder.id, order_id: targetOrder.id,
status: Not(LICENSE_ALLOCATED_STATUS.UNALLOCATED), status: Not(LICENSE_ALLOCATED_STATUS.UNALLOCATED),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 存在した場合エラー // 存在した場合エラー
@ -730,18 +832,35 @@ export class AccountsRepositoryService {
// 注文を発行待ちに戻す // 注文を発行待ちに戻す
updatedOrder.issued_at = null; updatedOrder.issued_at = null;
updatedOrder.status = LICENSE_ISSUE_STATUS.ISSUE_REQUESTING; updatedOrder.status = LICENSE_ISSUE_STATUS.ISSUE_REQUESTING;
await orderRepo.save(updatedOrder); await updateEntity(
orderRepo,
{ id: targetOrder.id },
updatedOrder,
this.isCommentOut,
context,
);
// 発行時に削除したライセンスを未割当に戻す // 発行時に削除したライセンスを未割当に戻す
await licenseRepo.update( await updateEntity(
licenseRepo,
{ delete_order_id: targetOrder.id }, { delete_order_id: targetOrder.id },
{ {
status: LICENSE_ALLOCATED_STATUS.UNALLOCATED, status: LICENSE_ALLOCATED_STATUS.UNALLOCATED,
deleted_at: null, deleted_at: null,
delete_order_id: null, delete_order_id: null,
}, },
this.isCommentOut,
context,
); );
// 発行時に発行されたライセンスを削除する // 発行時に発行されたライセンスを削除する
await licenseRepo.delete({ order_id: targetOrder.id }); await deleteEntity(
licenseRepo,
{ order_id: targetOrder.id },
this.isCommentOut,
context,
);
return { canceledIssueLicenseOrderId: targetOrder.id };
}); });
} }
@ -754,6 +873,7 @@ export class AccountsRepositoryService {
* @returns partners: DBから取得できるパートナー一覧情報 * @returns partners: DBから取得できるパートナー一覧情報
*/ */
async getPartners( async getPartners(
context: Context,
id: number, id: number,
limit: number, limit: number,
offset: number, offset: number,
@ -769,6 +889,7 @@ export class AccountsRepositoryService {
where: { where: {
parent_account_id: id, parent_account_id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const partnerAccounts = await accountRepo.find({ const partnerAccounts = await accountRepo.find({
@ -780,6 +901,7 @@ export class AccountsRepositoryService {
}, },
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ADB2Cから情報を取得するための外部ユーザIDを取得する念のためプライマリ管理者IDが存在しない場合を考慮 // ADB2Cから情報を取得するための外部ユーザIDを取得する念のためプライマリ管理者IDが存在しない場合を考慮
@ -797,6 +919,7 @@ export class AccountsRepositoryService {
where: { where: {
id: In(primaryUserIds), id: In(primaryUserIds),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// アカウント情報とプライマリ管理者の外部ユーザIDをマージ // アカウント情報とプライマリ管理者の外部ユーザIDをマージ
@ -836,6 +959,7 @@ export class AccountsRepositoryService {
* @returns account: 一階層上のアカウント * @returns account: 一階層上のアカウント
*/ */
async getOneUpperTierAccount( async getOneUpperTierAccount(
context: Context,
accountId: number, accountId: number,
tier: number, tier: number,
): Promise<Account | null> { ): Promise<Account | null> {
@ -846,6 +970,7 @@ export class AccountsRepositoryService {
id: accountId, id: accountId,
tier: tier - 1, tier: tier - 1,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
}); });
} }
@ -860,6 +985,7 @@ export class AccountsRepositoryService {
* @param secondryAdminUserId * @param secondryAdminUserId
*/ */
async updateAccountInfo( async updateAccountInfo(
context: Context,
myAccountId: number, myAccountId: number,
tier: number, tier: number,
delegationPermission: boolean, delegationPermission: boolean,
@ -871,6 +997,7 @@ export class AccountsRepositoryService {
// ディーラーアカウントが指定されている場合、存在チェックを行う // ディーラーアカウントが指定されている場合、存在チェックを行う
if (parentAccountId) { if (parentAccountId) {
const dealerAccount = await this.getOneUpperTierAccount( const dealerAccount = await this.getOneUpperTierAccount(
context,
parentAccountId, parentAccountId,
tier, tier,
); );
@ -891,6 +1018,7 @@ export class AccountsRepositoryService {
account_id: myAccountId, account_id: myAccountId,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!primaryAdminUser) { if (!primaryAdminUser) {
throw new AdminUserNotFoundError( throw new AdminUserNotFoundError(
@ -907,6 +1035,7 @@ export class AccountsRepositoryService {
account_id: myAccountId, account_id: myAccountId,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!secondryAdminUser) { if (!secondryAdminUser) {
throw new AdminUserNotFoundError( throw new AdminUserNotFoundError(
@ -916,7 +1045,8 @@ export class AccountsRepositoryService {
} }
const accountRepo = entityManager.getRepository(Account); const accountRepo = entityManager.getRepository(Account);
// アカウント情報を更新 // アカウント情報を更新
await accountRepo.update( await updateEntity(
accountRepo,
{ id: myAccountId }, { id: myAccountId },
{ {
parent_account_id: parentAccountId ?? null, parent_account_id: parentAccountId ?? null,
@ -924,6 +1054,8 @@ export class AccountsRepositoryService {
primary_admin_user_id: primaryAdminUserId, primary_admin_user_id: primaryAdminUserId,
secondary_admin_user_id: secondryAdminUserId ?? null, secondary_admin_user_id: secondryAdminUserId ?? null,
}, },
this.isCommentOut,
context,
); );
}); });
} }
@ -935,6 +1067,7 @@ export class AccountsRepositoryService {
* @returns active worktype id * @returns active worktype id
*/ */
async updateActiveWorktypeId( async updateActiveWorktypeId(
context: Context,
accountId: number, accountId: number,
id?: number | undefined, id?: number | undefined,
): Promise<void> { ): Promise<void> {
@ -946,6 +1079,7 @@ export class AccountsRepositoryService {
// 自アカウント内に指定IDのワークタイプが存在するか確認 // 自アカウント内に指定IDのワークタイプが存在するか確認
const worktype = await worktypeRepo.findOne({ const worktype = await worktypeRepo.findOne({
where: { account_id: accountId, id: id }, where: { account_id: accountId, id: id },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ワークタイプが存在しない場合はエラー // ワークタイプが存在しない場合はエラー
@ -955,9 +1089,12 @@ export class AccountsRepositoryService {
} }
// アカウントのActiveWorktypeIDを更新 // アカウントのActiveWorktypeIDを更新
await accountRepo.update( await updateEntity(
accountRepo,
{ id: accountId }, { id: accountId },
{ active_worktype_id: id ?? null }, { active_worktype_id: id ?? null },
this.isCommentOut,
context,
); );
}); });
} }
@ -967,35 +1104,42 @@ export class AccountsRepositoryService {
* @param accountId * @param accountId
* @returns users * @returns users
*/ */
async deleteAccountAndInsertArchives(accountId: number): Promise<User[]> { async deleteAccountAndInsertArchives(
context: Context,
accountId: number,
): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
// 削除対象のユーザーを退避テーブルに退避 // 削除対象のユーザーを退避テーブルに退避
const users = await this.dataSource.getRepository(User).find({ const users = await this.dataSource.getRepository(User).find({
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const userArchiveRepo = entityManager.getRepository(UserArchive); const userArchiveRepo = entityManager.getRepository(UserArchive);
await userArchiveRepo await insertEntities(
.createQueryBuilder() UserArchive,
.insert() userArchiveRepo,
.into(UserArchive) users,
.values(users) this.isCommentOut,
.execute(); context,
);
// 削除対象のライセンスを退避テーブルに退避 // 削除対象のライセンスを退避テーブルに退避
const licenses = await this.dataSource.getRepository(License).find({ const licenses = await this.dataSource.getRepository(License).find({
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const licenseArchiveRepo = entityManager.getRepository(LicenseArchive); const licenseArchiveRepo = entityManager.getRepository(LicenseArchive);
await licenseArchiveRepo await insertEntities(
.createQueryBuilder() LicenseArchive,
.insert() licenseArchiveRepo,
.into(LicenseArchive) licenses,
.values(licenses) this.isCommentOut,
.execute(); context,
);
// 削除対象のライセンス割り当て履歴を退避テーブルに退避 // 削除対象のライセンス割り当て履歴を退避テーブルに退避
const licenseHistories = await this.dataSource const licenseHistories = await this.dataSource
@ -1004,57 +1148,85 @@ export class AccountsRepositoryService {
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const licenseHistoryArchiveRepo = entityManager.getRepository( const licenseHistoryArchiveRepo = entityManager.getRepository(
LicenseAllocationHistoryArchive, LicenseAllocationHistoryArchive,
); );
await licenseHistoryArchiveRepo await insertEntities(
.createQueryBuilder() LicenseAllocationHistoryArchive,
.insert() licenseHistoryArchiveRepo,
.into(LicenseAllocationHistoryArchive) licenseHistories,
.values(licenseHistories) this.isCommentOut,
.execute(); context,
);
// アカウントを削除 // アカウントを削除
const accountRepo = entityManager.getRepository(Account); const accountRepo = entityManager.getRepository(Account);
await accountRepo.delete({ id: accountId }); await deleteEntity(
accountRepo,
{ id: accountId },
this.isCommentOut,
context,
);
// ライセンス系(card_license_issue以外)のテーブルのレコードを削除する // ライセンス系(card_license_issue以外)のテーブルのレコードを削除する
const orderRepo = entityManager.getRepository(LicenseOrder); const orderRepo = entityManager.getRepository(LicenseOrder);
await orderRepo.delete({ await deleteEntity(
from_account_id: accountId, orderRepo,
}); { from_account_id: accountId },
this.isCommentOut,
context,
);
const licenseRepo = entityManager.getRepository(License); const licenseRepo = entityManager.getRepository(License);
const targetLicenses = await licenseRepo.find({ const targetLicenses = await licenseRepo.find({
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const cardLicenseRepo = entityManager.getRepository(CardLicense); const cardLicenseRepo = entityManager.getRepository(CardLicense);
await cardLicenseRepo.delete({ await deleteEntity(
license_id: In(targetLicenses.map((license) => license.id)), cardLicenseRepo,
}); { license_id: In(targetLicenses.map((license) => license.id)) },
await licenseRepo.delete({ this.isCommentOut,
account_id: accountId, context,
}); );
await deleteEntity(
licenseRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
const LicenseAllocationHistoryRepo = entityManager.getRepository( const LicenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory, LicenseAllocationHistory,
); );
await LicenseAllocationHistoryRepo.delete({ await deleteEntity(
account_id: accountId, LicenseAllocationHistoryRepo,
}); { account_id: accountId },
this.isCommentOut,
context,
);
// ワークタイプ系のテーブルのレコードを削除する // ワークタイプ系のテーブルのレコードを削除する
const worktypeRepo = entityManager.getRepository(Worktype); const worktypeRepo = entityManager.getRepository(Worktype);
const taggerWorktypes = await worktypeRepo.find({ const taggerWorktypes = await worktypeRepo.find({
where: { account_id: accountId }, where: { account_id: accountId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const optionItemRepo = entityManager.getRepository(OptionItem); const optionItemRepo = entityManager.getRepository(OptionItem);
await optionItemRepo.delete({ await deleteEntity(
worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)), optionItemRepo,
}); { worktype_id: In(taggerWorktypes.map((worktype) => worktype.id)) },
await worktypeRepo.delete({ account_id: accountId }); this.isCommentOut,
context,
);
await deleteEntity(
worktypeRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
// タスク系のテーブルのレコードを削除する // タスク系のテーブルのレコードを削除する
const taskRepo = entityManager.getRepository(Task); const taskRepo = entityManager.getRepository(Task);
@ -1062,15 +1234,22 @@ export class AccountsRepositoryService {
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const checkoutPermissionRepo = const checkoutPermissionRepo =
entityManager.getRepository(CheckoutPermission); entityManager.getRepository(CheckoutPermission);
await checkoutPermissionRepo.delete({ await deleteEntity(
task_id: In(targetTasks.map((task) => task.id)), checkoutPermissionRepo,
}); { task_id: In(targetTasks.map((task) => task.id)) },
await taskRepo.delete({ this.isCommentOut,
account_id: accountId, context,
}); );
await deleteEntity(
taskRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
// オーディオファイル系のテーブルのレコードを削除する // オーディオファイル系のテーブルのレコードを削除する
const audioFileRepo = entityManager.getRepository(AudioFile); const audioFileRepo = entityManager.getRepository(AudioFile);
@ -1078,14 +1257,23 @@ export class AccountsRepositoryService {
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const audioOptionItemsRepo = entityManager.getRepository(AudioOptionItem); const audioOptionItemsRepo = entityManager.getRepository(AudioOptionItem);
await audioOptionItemsRepo.delete({ await deleteEntity(
audioOptionItemsRepo,
{
audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)), audio_file_id: In(targetaudioFiles.map((audioFile) => audioFile.id)),
}); },
await audioFileRepo.delete({ this.isCommentOut,
account_id: accountId, context,
}); );
await deleteEntity(
audioFileRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
// ユーザーグループ系のテーブルのレコードを削除する // ユーザーグループ系のテーブルのレコードを削除する
const userGroupRepo = entityManager.getRepository(UserGroup); const userGroupRepo = entityManager.getRepository(UserGroup);
@ -1093,30 +1281,50 @@ export class AccountsRepositoryService {
where: { where: {
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const userGroupMemberRepo = entityManager.getRepository(UserGroupMember); const userGroupMemberRepo = entityManager.getRepository(UserGroupMember);
await userGroupMemberRepo.delete({ await deleteEntity(
userGroupMemberRepo,
{
user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)), user_group_id: In(targetUserGroup.map((userGroup) => userGroup.id)),
}); },
await userGroupRepo.delete({ this.isCommentOut,
account_id: accountId, context,
}); );
await deleteEntity(
userGroupRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
// テンプレートファイルテーブルのレコードを削除する // テンプレートファイルテーブルのレコードを削除する
const templateFileRepo = entityManager.getRepository(TemplateFile); const templateFileRepo = entityManager.getRepository(TemplateFile);
await templateFileRepo.delete({ account_id: accountId }); await deleteEntity(
templateFileRepo,
{ account_id: accountId },
this.isCommentOut,
context,
);
// ユーザテーブルのレコードを削除する // ユーザテーブルのレコードを削除する
const userRepo = entityManager.getRepository(User); const userRepo = entityManager.getRepository(User);
await userRepo.delete({ await deleteEntity(
account_id: accountId, userRepo,
}); { account_id: accountId },
this.isCommentOut,
context,
);
// ソート条件のテーブルのレコードを削除する // ソート条件のテーブルのレコードを削除する
const sortCriteriaRepo = entityManager.getRepository(SortCriteria); const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
await sortCriteriaRepo.delete({ await deleteEntity(
user_id: In(users.map((user) => user.id)), sortCriteriaRepo,
}); { user_id: In(users.map((user) => user.id)) },
this.isCommentOut,
context,
);
return users; return users;
}); });
} }

View File

@ -12,6 +12,7 @@ import {
LICENSE_ALLOCATED_STATUS, LICENSE_ALLOCATED_STATUS,
LICENSE_ISSUE_STATUS, LICENSE_ISSUE_STATUS,
LICENSE_TYPE, LICENSE_TYPE,
NODE_ENV_TEST,
SWITCH_FROM_TYPE, SWITCH_FROM_TYPE,
TIERS, TIERS,
} from '../../constants'; } from '../../constants';
@ -32,13 +33,22 @@ import {
DateWithZeroTime, DateWithZeroTime,
} from '../../features/licenses/types/types'; } from '../../features/licenses/types/types';
import { NewAllocatedLicenseExpirationDate } from '../../features/licenses/types/types'; import { NewAllocatedLicenseExpirationDate } from '../../features/licenses/types/types';
import {
insertEntity,
insertEntities,
updateEntity,
} from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class LicensesRepositoryService { export class LicensesRepositoryService {
//クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
private readonly logger = new Logger(LicensesRepositoryService.name); private readonly logger = new Logger(LicensesRepositoryService.name);
async order( async order(
context: Context,
poNumber: string, poNumber: string,
fromAccountId: number, fromAccountId: number,
toAccountId: number, toAccountId: number,
@ -70,6 +80,7 @@ export class LicensesRepositoryService {
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 重複があった場合はエラーを返却する // 重複があった場合はエラーを返却する
if (isPoNumberDuplicated) { if (isPoNumberDuplicated) {
@ -78,13 +89,49 @@ export class LicensesRepositoryService {
const repo = entityManager.getRepository(LicenseOrder); const repo = entityManager.getRepository(LicenseOrder);
const newLicenseOrder = repo.create(licenseOrder); const newLicenseOrder = repo.create(licenseOrder);
const persisted = await repo.save(newLicenseOrder); const persisted = await insertEntity(
LicenseOrder,
repo,
newLicenseOrder,
this.isCommentOut,
context,
);
return persisted; return persisted;
}, },
); );
return createdEntity; return createdEntity;
} }
/**
* IDとPO番号と依頼元アカウントIDを元にライセンス注文を取得する
* @param context context
* @param fromAccountId ID
* @param poNumber PO番号
* @param orderId LicenseOrderのID
* @returns license order
*/
async getLicenseOrder(
context: Context,
fromAccountId: number,
poNumber: string,
orderId: number,
): Promise<LicenseOrder | null> {
return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(LicenseOrder);
// ステータスは問わず、指定したIDのランセンス注文を取得する
const entity = repo.findOne({
where: {
id: orderId,
po_number: poNumber,
from_account_id: fromAccountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return entity;
});
}
/** /**
* *
* @param accountId * @param accountId
@ -92,6 +139,7 @@ export class LicensesRepositoryService {
* @returns string[] * @returns string[]
*/ */
async createCardLicenses( async createCardLicenses(
context: Context,
accountId: number, accountId: number,
count: number, count: number,
): Promise<string[]> { ): Promise<string[]> {
@ -112,19 +160,24 @@ export class LicensesRepositoryService {
license.type = LICENSE_TYPE.CARD; license.type = LICENSE_TYPE.CARD;
licenses.push(license); licenses.push(license);
} }
const savedLicenses = await licensesRepo const savedLicenses = await insertEntities(
.createQueryBuilder() License,
.insert() licensesRepo,
.into(License) licenses,
.values(licenses) this.isCommentOut,
.execute(); context,
);
// カードライセンス発行テーブルを作成する // カードライセンス発行テーブルを作成する
const cardLicenseIssue = new CardLicenseIssue(); const cardLicenseIssue = new CardLicenseIssue();
cardLicenseIssue.issued_at = new Date(); cardLicenseIssue.issued_at = new Date();
const newCardLicenseIssue = cardLicenseIssueRepo.create(cardLicenseIssue); const newCardLicenseIssue = cardLicenseIssueRepo.create(cardLicenseIssue);
const savedCardLicensesIssue = await cardLicenseIssueRepo.save( const savedCardLicensesIssue = await insertEntity(
CardLicenseIssue,
cardLicenseIssueRepo,
newCardLicenseIssue, newCardLicenseIssue,
this.isCommentOut,
context,
); );
let isDuplicateKeysExist = true; let isDuplicateKeysExist = true;
@ -139,6 +192,7 @@ export class LicensesRepositoryService {
where: { where: {
card_license_key: In(generateKeys), card_license_key: In(generateKeys),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (existingCardLicenses.length > 0) { if (existingCardLicenses.length > 0) {
// 重複分を配列から削除 // 重複分を配列から削除
@ -168,17 +222,20 @@ export class LicensesRepositoryService {
// カードライセンステーブルを作成するBULK INSERT) // カードライセンステーブルを作成するBULK INSERT)
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const cardLicense = new CardLicense(); const cardLicense = new CardLicense();
cardLicense.license_id = savedLicenses.generatedMaps[i].id; // Licenseテーブルの自動採番されたIDを挿入 cardLicense.license_id = savedLicenses[i].id; // Licenseテーブルの自動採番されたIDを挿入
cardLicense.issue_id = savedCardLicensesIssue.id; // CardLicenseIssueテーブルの自動採番されたIDを挿入 cardLicense.issue_id = savedCardLicensesIssue.id; // CardLicenseIssueテーブルの自動採番されたIDを挿入
cardLicense.card_license_key = licenseKeys[i]; cardLicense.card_license_key = licenseKeys[i];
cardLicenses.push(cardLicense); cardLicenses.push(cardLicense);
} }
await cardLicenseRepo //TODO カードライセンステーブルのみPKがidではなかったためInsertEntitiesに置き換えができなかった。
const query = cardLicenseRepo
.createQueryBuilder() .createQueryBuilder()
.insert() .insert()
.into(CardLicense) .into(CardLicense);
.values(cardLicenses) if (this.isCommentOut) {
.execute(); query.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`);
}
query.values(cardLicenses).execute();
}); });
return licenseKeys; return licenseKeys;
} }
@ -222,6 +279,7 @@ export class LicensesRepositoryService {
* @returns void * @returns void
*/ */
async activateCardLicense( async activateCardLicense(
context: Context,
accountId: number, accountId: number,
licenseKey: string, licenseKey: string,
): Promise<void> { ): Promise<void> {
@ -233,6 +291,7 @@ export class LicensesRepositoryService {
where: { where: {
card_license_key: licenseKey, card_license_key: licenseKey,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// カードライセンスが存在しなければエラー // カードライセンスが存在しなければエラー
if (!targetCardLicense) { if (!targetCardLicense) {
@ -256,6 +315,7 @@ export class LicensesRepositoryService {
where: { where: {
id: targetCardLicense.license_id, id: targetCardLicense.license_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ライセンスが存在しなければエラー // ライセンスが存在しなければエラー
if (!targetLicense) { if (!targetLicense) {
@ -267,11 +327,23 @@ export class LicensesRepositoryService {
// ライセンステーブルを更新する // ライセンステーブルを更新する
targetLicense.account_id = accountId; targetLicense.account_id = accountId;
await licensesRepo.save(targetLicense); await updateEntity(
licensesRepo,
{ id: targetLicense.id },
targetLicense,
this.isCommentOut,
context,
);
// カードライセンステーブルを更新する // カードライセンステーブルを更新する
targetCardLicense.activated_at = new Date(); targetCardLicense.activated_at = new Date();
await cardLicenseRepo.save(targetCardLicense); await updateEntity(
cardLicenseRepo,
{ license_id: targetCardLicense.license_id },
targetCardLicense,
this.isCommentOut,
context,
);
this.logger.log( this.logger.log(
`activate success. licence_id: ${targetCardLicense.license_id}`, `activate success. licence_id: ${targetCardLicense.license_id}`,
@ -289,6 +361,7 @@ export class LicensesRepositoryService {
* @returns licenseOrders * @returns licenseOrders
*/ */
async getLicenseOrderHistoryInfo( async getLicenseOrderHistoryInfo(
context: Context,
accountId: number, accountId: number,
offset: number, offset: number,
limit: number, limit: number,
@ -303,6 +376,7 @@ export class LicensesRepositoryService {
where: { where: {
from_account_id: accountId, from_account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const licenseOrders = await licenseOrder.find({ const licenseOrders = await licenseOrder.find({
where: { where: {
@ -313,6 +387,7 @@ export class LicensesRepositoryService {
}, },
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return { return {
total: total, total: total,
@ -330,13 +405,14 @@ export class LicensesRepositoryService {
* @param poNumber * @param poNumber
*/ */
async issueLicense( async issueLicense(
context: Context,
orderedAccountId: number, orderedAccountId: number,
myAccountId: number, myAccountId: number,
tier: number, tier: number,
poNumber: string, poNumber: string,
): Promise<void> { ): Promise<{ issuedOrderId: number }> {
const nowDate = new Date(); const nowDate = new Date();
await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const licenseOrderRepo = entityManager.getRepository(LicenseOrder); const licenseOrderRepo = entityManager.getRepository(LicenseOrder);
const licenseRepo = entityManager.getRepository(License); const licenseRepo = entityManager.getRepository(License);
@ -345,9 +421,14 @@ export class LicensesRepositoryService {
from_account_id: orderedAccountId, from_account_id: orderedAccountId,
po_number: poNumber, po_number: poNumber,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
// テスト環境の場合はロックを行わない(sqliteがlockに対応していないため)
...(process.env.NODE_ENV !== NODE_ENV_TEST
? { lock: { mode: 'pessimistic_write' } }
: {}),
}); });
// 注文が存在しない場合、エラー
if (!issuingOrder) { if (!issuingOrder) {
// 注文が存在しない場合、エラー
throw new OrderNotFoundError(`No order found for PONumber:${poNumber}`); throw new OrderNotFoundError(`No order found for PONumber:${poNumber}`);
} }
// 既に発行済みの注文の場合、エラー // 既に発行済みの注文の場合、エラー
@ -367,20 +448,24 @@ export class LicensesRepositoryService {
return license; return license;
}); });
// ライセンス注文テーブルを更新(注文元) // ライセンス注文テーブルを更新(注文元)
await licenseOrderRepo.update( await updateEntity(
licenseOrderRepo,
{ id: issuingOrder.id }, { id: issuingOrder.id },
{ {
issued_at: nowDate, issued_at: nowDate,
status: LICENSE_ISSUE_STATUS.ISSUED, status: LICENSE_ISSUE_STATUS.ISSUED,
}, },
this.isCommentOut,
context,
); );
// ライセンステーブルを登録(注文元) // ライセンステーブルを登録(注文元)
await licenseRepo await insertEntities(
.createQueryBuilder() License,
.insert() licenseRepo,
.into(License) newLicenses,
.values(newLicenses) this.isCommentOut,
.execute(); context,
);
// 第一階層の場合はストックライセンスの概念が存在しないため、ストックライセンス変更処理は行わない // 第一階層の場合はストックライセンスの概念が存在しないため、ストックライセンス変更処理は行わない
if (tier !== TIERS.TIER1) { if (tier !== TIERS.TIER1) {
@ -394,6 +479,7 @@ export class LicensesRepositoryService {
id: 'ASC', id: 'ASC',
}, },
take: newLicenses.length, take: newLicenses.length,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 登録したライセンスに対して自身のライセンスが不足していた場合、エラー // 登録したライセンスに対して自身のライセンスが不足していた場合、エラー
@ -408,8 +494,20 @@ export class LicensesRepositoryService {
licenseToUpdate.delete_order_id = issuingOrder.id; licenseToUpdate.delete_order_id = issuingOrder.id;
} }
// 自身のライセンスを削除(論理削除)する // 自身のライセンスを削除(論理削除)する
await licenseRepo.save(licensesToUpdate); await updateEntity(
licenseRepo,
{ id: In(licensesToUpdate.map((l) => l.id)) },
{
status: LICENSE_ALLOCATED_STATUS.DELETED,
deleted_at: nowDate,
delete_order_id: issuingOrder.id,
},
this.isCommentOut,
context,
);
} }
return { issuedOrderId: issuingOrder.id };
}); });
} }
/** /**
@ -420,6 +518,7 @@ export class LicensesRepositoryService {
* @return AllocatableLicenseInfo[] * @return AllocatableLicenseInfo[]
*/ */
async getAllocatableLicenses( async getAllocatableLicenses(
context: Context,
myAccountId: number, myAccountId: number,
): Promise<AllocatableLicenseInfo[]> { ): Promise<AllocatableLicenseInfo[]> {
const nowDate = new DateWithZeroTime(); const nowDate = new DateWithZeroTime();
@ -438,6 +537,7 @@ export class LicensesRepositoryService {
'(license.expiry_date >= :nowDate OR license.expiry_date IS NULL)', '(license.expiry_date >= :nowDate OR license.expiry_date IS NULL)',
{ nowDate }, { nowDate },
) )
.comment(`${context.getTrackingId()}_${new Date().toUTCString()}`)
.orderBy('license.expiry_date IS NULL', 'DESC') .orderBy('license.expiry_date IS NULL', 'DESC')
.addOrderBy('license.expiry_date', 'DESC') .addOrderBy('license.expiry_date', 'DESC')
.addOrderBy('license.id', 'ASC'); .addOrderBy('license.id', 'ASC');
@ -453,6 +553,7 @@ export class LicensesRepositoryService {
* @param newLicenseId * @param newLicenseId
*/ */
async allocateLicense( async allocateLicense(
context: Context,
userId: number, userId: number,
newLicenseId: number, newLicenseId: number,
accountId: number, accountId: number,
@ -467,6 +568,7 @@ export class LicensesRepositoryService {
where: { where: {
id: newLicenseId, id: newLicenseId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ライセンスが存在しない場合はエラー // ライセンスが存在しない場合はエラー
@ -501,6 +603,7 @@ export class LicensesRepositoryService {
where: { where: {
allocated_user_id: userId, allocated_user_id: userId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 既にライセンスが割り当てられているなら、割り当てを解除 // 既にライセンスが割り当てられているなら、割り当てを解除
@ -508,7 +611,13 @@ export class LicensesRepositoryService {
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE; allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null; allocatedLicense.allocated_user_id = null;
await licenseRepo.save(allocatedLicense); await updateEntity(
licenseRepo,
{ id: allocatedLicense.id },
allocatedLicense,
this.isCommentOut,
context,
);
// ライセンス割り当て履歴テーブルへ登録 // ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory(); const deallocationHistory = new LicenseAllocationHistory();
@ -518,7 +627,13 @@ export class LicensesRepositoryService {
deallocationHistory.is_allocated = false; deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date(); deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE; deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory); await insertEntity(
LicenseAllocationHistory,
licenseAllocationHistoryRepo,
deallocationHistory,
this.isCommentOut,
context,
);
} }
// ライセンス割り当てを実施 // ライセンス割り当てを実施
@ -528,7 +643,13 @@ export class LicensesRepositoryService {
if (!targetLicense.expiry_date) { if (!targetLicense.expiry_date) {
targetLicense.expiry_date = new NewAllocatedLicenseExpirationDate(); targetLicense.expiry_date = new NewAllocatedLicenseExpirationDate();
} }
await licenseRepo.save(targetLicense); await updateEntity(
licenseRepo,
{ id: targetLicense.id },
targetLicense,
this.isCommentOut,
context,
);
// 直近割り当てたライセンス種別を取得 // 直近割り当てたライセンス種別を取得
const oldLicenseType = await licenseAllocationHistoryRepo.findOne({ const oldLicenseType = await licenseAllocationHistoryRepo.findOne({
@ -537,6 +658,7 @@ export class LicensesRepositoryService {
}, },
where: { user_id: userId, is_allocated: true }, where: { user_id: userId, is_allocated: true },
order: { executed_at: 'DESC' }, order: { executed_at: 'DESC' },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
let switchFromType = ''; let switchFromType = '';
@ -566,7 +688,13 @@ export class LicensesRepositoryService {
// TODO switchFromTypeの値については「PBI1234: 第一階層として、ライセンス数推移情報をCSV出力したい」で正式対応 // TODO switchFromTypeの値については「PBI1234: 第一階層として、ライセンス数推移情報をCSV出力したい」で正式対応
allocationHistory.switch_from_type = switchFromType; allocationHistory.switch_from_type = switchFromType;
await licenseAllocationHistoryRepo.save(allocationHistory); await insertEntity(
LicenseAllocationHistory,
licenseAllocationHistoryRepo,
allocationHistory,
this.isCommentOut,
context,
);
}); });
} }
@ -574,7 +702,11 @@ export class LicensesRepositoryService {
* *
* @param userId * @param userId
*/ */
async deallocateLicense(userId: number, accountId: number): Promise<void> { async deallocateLicense(
context: Context,
userId: number,
accountId: number,
): Promise<void> {
await this.dataSource.transaction(async (entityManager) => { await this.dataSource.transaction(async (entityManager) => {
const licenseRepo = entityManager.getRepository(License); const licenseRepo = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository( const licenseAllocationHistoryRepo = entityManager.getRepository(
@ -586,6 +718,7 @@ export class LicensesRepositoryService {
allocated_user_id: userId, allocated_user_id: userId,
status: LICENSE_ALLOCATED_STATUS.ALLOCATED, status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ライセンスが割り当てられていない場合はエラー // ライセンスが割り当てられていない場合はエラー
@ -598,7 +731,13 @@ export class LicensesRepositoryService {
// ライセンスの割り当てを解除 // ライセンスの割り当てを解除
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE; allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null; allocatedLicense.allocated_user_id = null;
await licenseRepo.save(allocatedLicense); await updateEntity(
licenseRepo,
{ id: allocatedLicense.id },
allocatedLicense,
this.isCommentOut,
context,
);
// ライセンス割り当て履歴テーブルへ登録 // ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory(); const deallocationHistory = new LicenseAllocationHistory();
@ -608,7 +747,13 @@ export class LicensesRepositoryService {
deallocationHistory.is_allocated = false; deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date(); deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE; deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory); await insertEntity(
LicenseAllocationHistory,
licenseAllocationHistoryRepo,
deallocationHistory,
this.isCommentOut,
context,
);
}); });
} }
@ -617,8 +762,12 @@ export class LicensesRepositoryService {
* @param accountId * @param accountId
* @param poNumber * @param poNumber
*/ */
async cancelOrder(accountId: number, poNumber: string): Promise<void> { async cancelOrder(
await this.dataSource.transaction(async (entityManager) => { context: Context,
accountId: number,
poNumber: string,
): Promise<{ canceledOrderId: number }> {
return await this.dataSource.transaction(async (entityManager) => {
const orderRepo = entityManager.getRepository(LicenseOrder); const orderRepo = entityManager.getRepository(LicenseOrder);
// キャンセル対象の注文を取得 // キャンセル対象の注文を取得
@ -628,6 +777,7 @@ export class LicensesRepositoryService {
po_number: poNumber, po_number: poNumber,
status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING, status: LICENSE_ISSUE_STATUS.ISSUE_REQUESTING,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// キャンセル対象の注文が存在しない場合エラー // キャンセル対象の注文が存在しない場合エラー
@ -640,7 +790,15 @@ export class LicensesRepositoryService {
// 注文キャンセル処理 // 注文キャンセル処理
targetOrder.status = LICENSE_ISSUE_STATUS.CANCELED; targetOrder.status = LICENSE_ISSUE_STATUS.CANCELED;
targetOrder.canceled_at = new Date(); targetOrder.canceled_at = new Date();
await orderRepo.save(targetOrder); await updateEntity(
orderRepo,
{ id: targetOrder.id },
targetOrder,
this.isCommentOut,
context,
);
return { canceledOrderId: targetOrder.id };
}); });
} }
@ -651,6 +809,7 @@ export class LicensesRepositoryService {
* @returns Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> * @returns Promise<{ state: 'allocated' | 'inallocated' | 'expired' }>
*/ */
async getLicenseState( async getLicenseState(
context: Context,
userId: number, userId: number,
): Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> { ): Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> {
const allocatedLicense = await this.dataSource const allocatedLicense = await this.dataSource
@ -660,6 +819,7 @@ export class LicensesRepositoryService {
allocated_user_id: userId, allocated_user_id: userId,
status: LICENSE_ALLOCATED_STATUS.ALLOCATED, status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ライセンスが割り当てられていない場合は未割当状態 // ライセンスが割り当てられていない場合は未割当状態

View File

@ -5,6 +5,8 @@ import {
TaskListSortableAttribute, TaskListSortableAttribute,
SortDirection, SortDirection,
} from '../../common/types/sort'; } from '../../common/types/sort';
import { updateEntity } from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class SortCriteriaRepositoryService { export class SortCriteriaRepositoryService {
@ -18,10 +20,11 @@ export class SortCriteriaRepositoryService {
* @returns sort criteria * @returns sort criteria
*/ */
async updateSortCriteria( async updateSortCriteria(
context: Context,
userId: number, userId: number,
parameter: TaskListSortableAttribute, parameter: TaskListSortableAttribute,
direction: SortDirection, direction: SortDirection,
): Promise<SortCriteria> { ): Promise<void> {
this.logger.log( this.logger.log(
` ${this.updateSortCriteria.name}; parameter:${parameter}, direction:${direction}`, ` ${this.updateSortCriteria.name}; parameter:${parameter}, direction:${direction}`,
); );
@ -31,6 +34,7 @@ export class SortCriteriaRepositoryService {
where: { where: {
user_id: userId, user_id: userId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!targetSortCriteria) { if (!targetSortCriteria) {
@ -40,8 +44,13 @@ export class SortCriteriaRepositoryService {
targetSortCriteria.parameter = parameter; targetSortCriteria.parameter = parameter;
targetSortCriteria.direction = direction; targetSortCriteria.direction = direction;
const persisted = await repo.save(targetSortCriteria); await updateEntity(
return persisted; repo,
{ id: targetSortCriteria.id },
targetSortCriteria,
false,
context,
);
}); });
} }
/** /**
@ -49,7 +58,10 @@ export class SortCriteriaRepositoryService {
* @param userId * @param userId
* @returns sort criteria * @returns sort criteria
*/ */
async getSortCriteria(userId: number): Promise<SortCriteria> { async getSortCriteria(
context: Context,
userId: number,
): Promise<SortCriteria> {
this.logger.log(` ${this.updateSortCriteria.name}; userId:${userId}`); this.logger.log(` ${this.updateSortCriteria.name}; userId:${userId}`);
const repo = this.dataSource.getRepository(SortCriteria); const repo = this.dataSource.getRepository(SortCriteria);
@ -57,6 +69,7 @@ export class SortCriteriaRepositoryService {
where: { where: {
user_id: userId, user_id: userId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!sortCriteria) { if (!sortCriteria) {

View File

@ -41,9 +41,18 @@ import { TaskStatus, isTaskStatus } from '../../common/types/taskStatus';
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity'; import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
import { Workflow } from '../workflows/entity/workflow.entity'; import { Workflow } from '../workflows/entity/workflow.entity';
import { Worktype } from '../worktypes/entity/worktype.entity'; import { Worktype } from '../worktypes/entity/worktype.entity';
import {
insertEntities,
insertEntity,
updateEntity,
deleteEntity,
} from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class TasksRepositoryService { export class TasksRepositoryService {
//クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
@ -54,6 +63,7 @@ export class TasksRepositoryService {
* @returns task and audio file * @returns task and audio file
*/ */
async getTaskAndAudioFile( async getTaskAndAudioFile(
context: Context,
audioFileId: number, audioFileId: number,
account_id: number, account_id: number,
status: string[], status: string[],
@ -71,6 +81,7 @@ export class TasksRepositoryService {
account_id: account_id, account_id: account_id,
status: In(status), status: In(status),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -103,6 +114,7 @@ export class TasksRepositoryService {
* @returns task from author id * @returns task from author id
*/ */
async getTaskFromAudioFileId( async getTaskFromAudioFileId(
context: Context,
audio_file_id: number, audio_file_id: number,
account_id: number, account_id: number,
author_id: string, author_id: string,
@ -117,6 +129,7 @@ export class TasksRepositoryService {
where: { where: {
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -147,6 +160,7 @@ export class TasksRepositoryService {
* @returns checkout * @returns checkout
*/ */
async checkout( async checkout(
context: Context,
audio_file_id: number, audio_file_id: number,
account_id: number, account_id: number,
user_id: number, user_id: number,
@ -159,6 +173,7 @@ export class TasksRepositoryService {
where: { where: {
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -173,6 +188,7 @@ export class TasksRepositoryService {
status: TASK_STATUS.IN_PROGRESS, status: TASK_STATUS.IN_PROGRESS,
typist_user_id: user_id, typist_user_id: user_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (tasks.length > 0) { if (tasks.length > 0) {
@ -210,6 +226,7 @@ export class TasksRepositoryService {
id: user_id, id: user_id,
}, },
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ユーザーの所属するすべてのグループIDを列挙 // ユーザーの所属するすべてのグループIDを列挙
const groupIds = groups.map((member) => member.user_group_id); const groupIds = groups.map((member) => member.user_group_id);
@ -231,6 +248,7 @@ export class TasksRepositoryService {
user_group_id: In(groupIds), user_group_id: In(groupIds),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
//チェックアウト権限がなければエラー //チェックアウト権限がなければエラー
@ -242,7 +260,8 @@ export class TasksRepositoryService {
// 対象タスクの文字起こし開始日時を現在時刻に更新。割り当てユーザーを自身のユーザーIDに更新 // 対象タスクの文字起こし開始日時を現在時刻に更新。割り当てユーザーを自身のユーザーIDに更新
// タスクのステータスがUploaded以外の場合、文字起こし開始時刻は更新しない // タスクのステータスがUploaded以外の場合、文字起こし開始時刻は更新しない
await taskRepo.update( await updateEntity(
taskRepo,
{ audio_file_id: audio_file_id }, { audio_file_id: audio_file_id },
{ {
started_at: started_at:
@ -252,18 +271,31 @@ export class TasksRepositoryService {
typist_user_id: user_id, typist_user_id: user_id,
status: TASK_STATUS.IN_PROGRESS, status: TASK_STATUS.IN_PROGRESS,
}, },
this.isCommentOut,
context,
); );
//対象のタスクに紐づくチェックアウト権限レコードを削除 //対象のタスクに紐づくチェックアウト権限レコードを削除
await checkoutRepo.delete({ await deleteEntity(
checkoutRepo,
{
task_id: task.id, task_id: task.id,
}); },
this.isCommentOut,
context,
);
//対象のタスクチェックアウト権限を自身のユーザーIDで作成 //対象のタスクチェックアウト権限を自身のユーザーIDで作成
await checkoutRepo.save({ insertEntity(
CheckoutPermission,
checkoutRepo,
{
task_id: task.id, task_id: task.id,
user_id: user_id, user_id: user_id,
}); },
this.isCommentOut,
context,
);
}); });
} }
@ -275,6 +307,7 @@ export class TasksRepositoryService {
* @returns checkin * @returns checkin
*/ */
async checkin( async checkin(
context: Context,
audio_file_id: number, audio_file_id: number,
user_id: number, user_id: number,
permittedSourceStatus: TaskStatus, permittedSourceStatus: TaskStatus,
@ -285,6 +318,7 @@ export class TasksRepositoryService {
where: { where: {
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -303,12 +337,15 @@ export class TasksRepositoryService {
} }
// 対象タスクの文字起こし終了日時を現在時刻に更新。ステータスをFinishedに更新 // 対象タスクの文字起こし終了日時を現在時刻に更新。ステータスをFinishedに更新
await taskRepo.update( await updateEntity(
taskRepo,
{ audio_file_id: audio_file_id }, { audio_file_id: audio_file_id },
{ {
finished_at: new Date().toISOString(), finished_at: new Date().toISOString(),
status: TASK_STATUS.FINISHED, status: TASK_STATUS.FINISHED,
}, },
this.isCommentOut,
context,
); );
}); });
} }
@ -322,6 +359,7 @@ export class TasksRepositoryService {
* @returns cancel * @returns cancel
*/ */
async cancel( async cancel(
context: Context,
audio_file_id: number, audio_file_id: number,
permittedSourceStatus: TaskStatus[], permittedSourceStatus: TaskStatus[],
account_id: number, account_id: number,
@ -333,6 +371,7 @@ export class TasksRepositoryService {
where: { where: {
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -360,12 +399,15 @@ export class TasksRepositoryService {
} }
// 対象タスクの文字起こし担当をnull,ステータスをUploadedに更新 // 対象タスクの文字起こし担当をnull,ステータスをUploadedに更新
await taskRepo.update( await updateEntity(
taskRepo,
{ audio_file_id: audio_file_id }, { audio_file_id: audio_file_id },
{ {
typist_user_id: null, typist_user_id: null,
status: TASK_STATUS.UPLOADED, status: TASK_STATUS.UPLOADED,
}, },
this.isCommentOut,
context,
); );
const checkoutPermissionRepo = const checkoutPermissionRepo =
@ -374,9 +416,14 @@ export class TasksRepositoryService {
// 対象タスクの文字起こし候補を削除 // 対象タスクの文字起こし候補を削除
/* Inprogress,PendingID /* Inprogress,PendingID
()*/ ()*/
await checkoutPermissionRepo.delete({ await deleteEntity(
checkoutPermissionRepo,
{
task_id: task.id, task_id: task.id,
}); },
this.isCommentOut,
context,
);
}); });
} }
@ -388,6 +435,7 @@ export class TasksRepositoryService {
* @returns suspend * @returns suspend
*/ */
async suspend( async suspend(
context: Context,
audio_file_id: number, audio_file_id: number,
user_id: number, user_id: number,
permittedSourceStatus: TaskStatus, permittedSourceStatus: TaskStatus,
@ -398,6 +446,7 @@ export class TasksRepositoryService {
where: { where: {
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -416,11 +465,14 @@ export class TasksRepositoryService {
} }
// 対象タスクの文字起こし終了日時を現在時刻に更新。ステータスをFinishedに更新 // 対象タスクの文字起こし終了日時を現在時刻に更新。ステータスをFinishedに更新
await taskRepo.update( await updateEntity(
taskRepo,
{ audio_file_id: audio_file_id }, { audio_file_id: audio_file_id },
{ {
status: TASK_STATUS.PENDING, status: TASK_STATUS.PENDING,
}, },
this.isCommentOut,
context,
); );
}); });
} }
@ -433,6 +485,7 @@ export class TasksRepositoryService {
* @returns backup * @returns backup
*/ */
async backup( async backup(
context: Context,
accountId: number, accountId: number,
audio_file_id: number, audio_file_id: number,
permittedSourceStatus: TaskStatus[], permittedSourceStatus: TaskStatus[],
@ -444,6 +497,7 @@ export class TasksRepositoryService {
account_id: accountId, account_id: accountId,
audio_file_id: audio_file_id, audio_file_id: audio_file_id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!task) { if (!task) {
throw new TasksNotFoundError( throw new TasksNotFoundError(
@ -461,12 +515,15 @@ export class TasksRepositoryService {
} }
// ステータスをバックアップに更新、JobNumberを無効化 // ステータスをバックアップに更新、JobNumberを無効化
await taskRepo.update( await updateEntity(
taskRepo,
{ audio_file_id: audio_file_id }, { audio_file_id: audio_file_id },
{ {
status: TASK_STATUS.BACKUP, status: TASK_STATUS.BACKUP,
is_job_number_enabled: false, is_job_number_enabled: false,
}, },
this.isCommentOut,
context,
); );
}); });
} }
@ -482,6 +539,7 @@ export class TasksRepositoryService {
* @returns tasks: タスク情報 / permissions:タスクに紐づくチェックアウト権限情報 / count: offset|limitを行わなかった場合の該当タスクの合計 * @returns tasks: タスク情報 / permissions:タスクに紐づくチェックアウト権限情報 / count: offset|limitを行わなかった場合の該当タスクの合計
*/ */
async getTasksFromAccountId( async getTasksFromAccountId(
context: Context,
account_id: number, account_id: number,
offset: number, offset: number,
limit: number, limit: number,
@ -504,6 +562,7 @@ export class TasksRepositoryService {
account_id: account_id, account_id: account_id,
status: In(status), status: In(status),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 条件に該当するTask一覧を取得 // 条件に該当するTask一覧を取得
@ -520,6 +579,7 @@ export class TasksRepositoryService {
order: order, // 引数によってOrderに使用するパラメータを変更 order: order, // 引数によってOrderに使用するパラメータを変更
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// TODO [Task2249] Task内にCheckoutPermissionを含める方法が上手くいかなかった複雑になりすぎた 原因未調査)ため、 // TODO [Task2249] Task内にCheckoutPermissionを含める方法が上手くいかなかった複雑になりすぎた 原因未調査)ため、
@ -535,6 +595,7 @@ export class TasksRepositoryService {
where: { where: {
task_id: In(taskIds), task_id: In(taskIds),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return { tasks, permissions, count }; return { tasks, permissions, count };
}); });
@ -552,6 +613,7 @@ export class TasksRepositoryService {
* @returns tasks: タスク情報 / permissions:タスクに紐づくチェックアウト権限情報 / count: offset|limitを行わなかった場合の該当タスクの合計 * @returns tasks: タスク情報 / permissions:タスクに紐づくチェックアウト権限情報 / count: offset|limitを行わなかった場合の該当タスクの合計
*/ */
async getTasksFromAuthorIdAndAccountId( async getTasksFromAuthorIdAndAccountId(
context: Context,
author_id: string, author_id: string,
account_id: number, account_id: number,
offset: number, offset: number,
@ -573,6 +635,7 @@ export class TasksRepositoryService {
status: In(status), status: In(status),
file: { author_id: author_id }, file: { author_id: author_id },
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const tasks = await taskRepo.find({ const tasks = await taskRepo.find({
@ -589,6 +652,7 @@ export class TasksRepositoryService {
order: order, // 引数によってOrderに使用するパラメータを変更 order: order, // 引数によってOrderに使用するパラメータを変更
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const checkoutRepo = entityManager.getRepository(CheckoutPermission); const checkoutRepo = entityManager.getRepository(CheckoutPermission);
@ -601,6 +665,7 @@ export class TasksRepositoryService {
where: { where: {
task_id: In(taskIds), task_id: In(taskIds),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return { tasks, permissions, count }; return { tasks, permissions, count };
}); });
@ -608,6 +673,7 @@ export class TasksRepositoryService {
} }
async getTasksFromTypistRelations( async getTasksFromTypistRelations(
context: Context,
external_user_id: string, external_user_id: string,
offset: number, offset: number,
limit: number, limit: number,
@ -633,6 +699,7 @@ export class TasksRepositoryService {
external_id: external_user_id, external_id: external_user_id,
}, },
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ユーザーの所属するすべてのグループIDを列挙 // ユーザーの所属するすべてのグループIDを列挙
const groupIds = groups.map((member) => member.user_group_id); const groupIds = groups.map((member) => member.user_group_id);
@ -655,6 +722,7 @@ export class TasksRepositoryService {
user_group_id: In(groupIds), user_group_id: In(groupIds),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成 // ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成
@ -683,6 +751,7 @@ export class TasksRepositoryService {
status: In(status), status: In(status),
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 条件に該当するTask一覧を取得 // 条件に該当するTask一覧を取得
@ -709,6 +778,7 @@ export class TasksRepositoryService {
order: order, // 引数によってOrderに使用するパラメータを変更 order: order, // 引数によってOrderに使用するパラメータを変更
take: limit, take: limit,
skip: offset, skip: offset,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// TODO [Task2249] Task内にCheckoutPermissionを含める方法が上手くいかなかった複雑になりすぎた 原因未調査)ため、 // TODO [Task2249] Task内にCheckoutPermissionを含める方法が上手くいかなかった複雑になりすぎた 原因未調査)ため、
@ -722,6 +792,7 @@ export class TasksRepositoryService {
where: { where: {
task_id: In(taskIds), task_id: In(taskIds),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return { tasks, permissions, count }; return { tasks, permissions, count };
}); });
@ -732,6 +803,7 @@ export class TasksRepositoryService {
* *
*/ */
async create( async create(
context: Context,
account_id: number, account_id: number,
owner_user_id: number, owner_user_id: number,
priority: string, priority: string,
@ -776,7 +848,13 @@ export class TasksRepositoryService {
async (entityManager) => { async (entityManager) => {
const audioFileRepo = entityManager.getRepository(AudioFile); const audioFileRepo = entityManager.getRepository(AudioFile);
const newAudioFile = audioFileRepo.create(audioFile); const newAudioFile = audioFileRepo.create(audioFile);
const savedAudioFile = await audioFileRepo.save(newAudioFile); const savedAudioFile = await insertEntity(
AudioFile,
audioFileRepo,
newAudioFile,
this.isCommentOut,
context,
);
task.audio_file_id = savedAudioFile.id; task.audio_file_id = savedAudioFile.id;
@ -786,6 +864,7 @@ export class TasksRepositoryService {
const lastTask = await taskRepo.findOne({ const lastTask = await taskRepo.findOne({
where: { account_id: account_id, is_job_number_enabled: true }, where: { account_id: account_id, is_job_number_enabled: true },
order: { created_at: 'DESC', job_number: 'DESC' }, order: { created_at: 'DESC', job_number: 'DESC' },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
let newJobNumber = '00000001'; let newJobNumber = '00000001';
@ -801,7 +880,13 @@ export class TasksRepositoryService {
} }
task.job_number = newJobNumber; task.job_number = newJobNumber;
const persisted = await taskRepo.save(task); const persisted = await insertEntity(
Task,
taskRepo,
task,
this.isCommentOut,
context,
);
const optionItems = paramOptionItems.map((x) => { const optionItems = paramOptionItems.map((x) => {
return { return {
@ -813,7 +898,13 @@ export class TasksRepositoryService {
const optionItemRepo = entityManager.getRepository(AudioOptionItem); const optionItemRepo = entityManager.getRepository(AudioOptionItem);
const newAudioOptionItems = optionItemRepo.create(optionItems); const newAudioOptionItems = optionItemRepo.create(optionItems);
await optionItemRepo.save(newAudioOptionItems); await insertEntities(
AudioOptionItem,
optionItemRepo,
newAudioOptionItems,
this.isCommentOut,
context,
);
return persisted; return persisted;
}, },
); );
@ -828,6 +919,7 @@ export class TasksRepositoryService {
* @returns checkout permission * @returns checkout permission
*/ */
async changeCheckoutPermission( async changeCheckoutPermission(
context: Context,
audio_file_id: number, audio_file_id: number,
author_id: string | undefined, author_id: string | undefined,
account_id: number, account_id: number,
@ -848,6 +940,7 @@ export class TasksRepositoryService {
account_id: account_id, account_id: account_id,
deleted_at: IsNull(), deleted_at: IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// idはユニークであるため取得件数の一致でグループの存在を確認 // idはユニークであるため取得件数の一致でグループの存在を確認
if (userGroupIds.length !== groupRecords.length) { if (userGroupIds.length !== groupRecords.length) {
@ -873,6 +966,7 @@ export class TasksRepositoryService {
email_verified: true, email_verified: true,
deleted_at: IsNull(), deleted_at: IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// idはユニークであるため取得件数の一致でユーザーの存在を確認 // idはユニークであるため取得件数の一致でユーザーの存在を確認
if (typistUserIds.length !== userRecords.length) { if (typistUserIds.length !== userRecords.length) {
@ -897,6 +991,7 @@ export class TasksRepositoryService {
: author_id, : author_id,
}, },
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
//タスクが存在しない or ステータスがUploadedでなければエラー //タスクが存在しない or ステータスがUploadedでなければエラー
if (!taskRecord) { if (!taskRecord) {
@ -908,9 +1003,14 @@ export class TasksRepositoryService {
// 当該タスクに紐づく既存checkoutPermissionをdelete // 当該タスクに紐づく既存checkoutPermissionをdelete
const checkoutPermissionRepo = const checkoutPermissionRepo =
entityManager.getRepository(CheckoutPermission); entityManager.getRepository(CheckoutPermission);
await checkoutPermissionRepo.delete({ await deleteEntity(
checkoutPermissionRepo,
{
task_id: taskRecord.id, task_id: taskRecord.id,
}); },
this.isCommentOut,
context,
);
// 当該タスクに紐づく新規checkoutPermissionをinsert // 当該タスクに紐づく新規checkoutPermissionをinsert
const checkoutPermissions: CheckoutPermission[] = assignees.map( const checkoutPermissions: CheckoutPermission[] = assignees.map(
@ -923,7 +1023,13 @@ export class TasksRepositoryService {
}, },
); );
return await checkoutPermissionRepo.save(checkoutPermissions); return await insertEntities(
CheckoutPermission,
checkoutPermissionRepo,
checkoutPermissions,
this.isCommentOut,
context,
);
}); });
} }
@ -935,6 +1041,7 @@ export class TasksRepositoryService {
* @returns sorted tasks * @returns sorted tasks
*/ */
async getSortedTasks( async getSortedTasks(
context: Context,
accountId: number, accountId: number,
userId: number, userId: number,
audioFileId: number, audioFileId: number,
@ -943,7 +1050,10 @@ export class TasksRepositoryService {
const taskRepo = entityManager.getRepository(Task); const taskRepo = entityManager.getRepository(Task);
const sortRepo = entityManager.getRepository(SortCriteria); const sortRepo = entityManager.getRepository(SortCriteria);
const sort = await sortRepo.findOne({ where: { user_id: userId } }); const sort = await sortRepo.findOne({
where: { user_id: userId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
// 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上はあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!sort) { if (!sort) {
@ -972,6 +1082,7 @@ export class TasksRepositoryService {
TASK_STATUS.UPLOADED, TASK_STATUS.UPLOADED,
]), ]),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!targetTask) { if (!targetTask) {
@ -982,7 +1093,10 @@ export class TasksRepositoryService {
const groupMemberRepo = entityManager.getRepository(UserGroupMember); const groupMemberRepo = entityManager.getRepository(UserGroupMember);
// ユーザーの所属するすべてのグループを列挙 // ユーザーの所属するすべてのグループを列挙
const groups = await groupMemberRepo.find({ where: { user_id: userId } }); const groups = await groupMemberRepo.find({
where: { user_id: userId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
// ユーザーの所属するすべてのグループIDを列挙 // ユーザーの所属するすべてのグループIDを列挙
const groupIds = groups.map((member) => member.user_group_id); const groupIds = groups.map((member) => member.user_group_id);
@ -995,6 +1109,7 @@ export class TasksRepositoryService {
// ユーザーの所属するユーザーグループがチェックアウト可能である // ユーザーの所属するユーザーグループがチェックアウト可能である
{ user_group_id: In(groupIds) }, { user_group_id: In(groupIds) },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成 // ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成
@ -1017,6 +1132,7 @@ export class TasksRepositoryService {
}, },
], ],
order: order, order: order,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return tasks; return tasks;
@ -1031,6 +1147,7 @@ export class TasksRepositoryService {
* @returns typistIds: タイピストIDの一覧 / typistGroupIds: タイピストグループIDの一覧 * @returns typistIds: タイピストIDの一覧 / typistGroupIds: タイピストグループIDの一覧
*/ */
async autoRouting( async autoRouting(
context: Context,
audioFileId: number, audioFileId: number,
accountId: number, accountId: number,
myAuthorId?: string, // API実行者のAuthorId myAuthorId?: string, // API実行者のAuthorId
@ -1046,6 +1163,7 @@ export class TasksRepositoryService {
id: audioFileId, id: audioFileId,
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!audioFile) { if (!audioFile) {
throw new Error( throw new Error(
@ -1067,6 +1185,7 @@ export class TasksRepositoryService {
author_id: audioFile.author_id, author_id: audioFile.author_id,
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 音声ファイル上のworktypeIdをもとにworktypeを取得 // 音声ファイル上のworktypeIdをもとにworktypeを取得
@ -1076,6 +1195,7 @@ export class TasksRepositoryService {
custom_worktype_id: audioFile.work_type_id, custom_worktype_id: audioFile.work_type_id,
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 音声ファイル上のworktypeIdが設定されているが、一致するworktypeが存在しない場合はエラーを出して終了 // 音声ファイル上のworktypeIdが設定されているが、一致するworktypeが存在しない場合はエラーを出して終了
@ -1096,11 +1216,13 @@ export class TasksRepositoryService {
author_id: authorUser?.id ?? IsNull(), // authorUserが存在しない場合は、必ずヒットしないようにNULLを設定する author_id: authorUser?.id ?? IsNull(), // authorUserが存在しない場合は、必ずヒットしないようにNULLを設定する
worktype_id: worktypeRecord?.id ?? IsNull(), worktype_id: worktypeRecord?.id ?? IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// Workflowルーティングルールがあればタスクのチェックアウト権限を設定する // Workflowルーティングルールがあればタスクのチェックアウト権限を設定する
if (workflow) { if (workflow) {
return await this.setCheckoutPermissionAndTemplate( return await this.setCheckoutPermissionAndTemplate(
context,
workflow, workflow,
task, task,
accountId, accountId,
@ -1121,6 +1243,7 @@ export class TasksRepositoryService {
author_id: myAuthorId, author_id: myAuthorId,
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!myAuthorUser) { if (!myAuthorUser) {
throw new Error( throw new Error(
@ -1136,6 +1259,7 @@ export class TasksRepositoryService {
author_id: myAuthorUser.id, author_id: myAuthorUser.id,
worktype_id: worktypeRecord?.id ?? IsNull(), worktype_id: worktypeRecord?.id ?? IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了 // API実行者のAuthorIdと音声ファイルのWorktypeをもとにルーティングルールを取得できない場合はエラーを出して終了
@ -1147,6 +1271,7 @@ export class TasksRepositoryService {
// Workflowルーティングルールがあればタスクのチェックアウト権限を設定する // Workflowルーティングルールがあればタスクのチェックアウト権限を設定する
return await this.setCheckoutPermissionAndTemplate( return await this.setCheckoutPermissionAndTemplate(
context,
defaultWorkflow, defaultWorkflow,
task, task,
accountId, accountId,
@ -1168,6 +1293,7 @@ export class TasksRepositoryService {
* @returns checkout permission * @returns checkout permission
*/ */
private async setCheckoutPermissionAndTemplate( private async setCheckoutPermissionAndTemplate(
context: Context,
workflow: Workflow, workflow: Workflow,
task: Task, task: Task,
accountId: number, accountId: number,
@ -1181,11 +1307,14 @@ export class TasksRepositoryService {
// タスクのテンプレートIDを更新 // タスクのテンプレートIDを更新
const taskRepo = entityManager.getRepository(Task); const taskRepo = entityManager.getRepository(Task);
await taskRepo.update( await updateEntity(
taskRepo,
{ id: task.id }, { id: task.id },
{ {
template_file_id: template_id, template_file_id: template_id,
}, },
this.isCommentOut,
context,
); );
// 取得したルーティングルールのタイピストまたはタイピストグループをチェックアウト権限に設定する // 取得したルーティングルールのタイピストまたはタイピストグループをチェックアウト権限に設定する
@ -1196,6 +1325,7 @@ export class TasksRepositoryService {
); );
const typistUsers = await userRepo.find({ const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) }, where: { account_id: accountId, id: In(typistIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistUsers.length !== typistIds.length) { if (typistUsers.length !== typistIds.length) {
throw new Error(`typist not found. ids: ${typistIds}`); throw new Error(`typist not found. ids: ${typistIds}`);
@ -1208,6 +1338,7 @@ export class TasksRepositoryService {
const userGroupRepo = entityManager.getRepository(UserGroup); const userGroupRepo = entityManager.getRepository(UserGroup);
const typistGroups = await userGroupRepo.find({ const typistGroups = await userGroupRepo.find({
where: { account_id: accountId, id: In(groupIds) }, where: { account_id: accountId, id: In(groupIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistGroups.length !== groupIds.length) { if (typistGroups.length !== groupIds.length) {
throw new Error(`typist group not found. ids: ${groupIds}`); throw new Error(`typist group not found. ids: ${groupIds}`);
@ -1217,9 +1348,14 @@ export class TasksRepositoryService {
entityManager.getRepository(CheckoutPermission); entityManager.getRepository(CheckoutPermission);
// 当該タスクに紐づく既存checkoutPermissionをdelete // 当該タスクに紐づく既存checkoutPermissionをdelete
await checkoutPermissionRepo.delete({ await deleteEntity(
checkoutPermissionRepo,
{
task_id: task.id, task_id: task.id,
}); },
this.isCommentOut,
context,
);
// ルーティング候補ユーザーのチェックアウト権限を作成 // ルーティング候補ユーザーのチェックアウト権限を作成
const typistPermissions = typistUsers.map((typistUser) => { const typistPermissions = typistUsers.map((typistUser) => {
@ -1236,7 +1372,13 @@ export class TasksRepositoryService {
return permission; return permission;
}); });
const permissions = [...typistPermissions, ...typistGroupPermissions]; const permissions = [...typistPermissions, ...typistGroupPermissions];
await checkoutPermissionRepo.save(permissions); await insertEntities(
CheckoutPermission,
checkoutPermissionRepo,
permissions,
this.isCommentOut,
context,
);
// user_idsとuser_group_idsを返却する // user_idsとuser_group_idsを返却する
return { return {
typistIds: typistIds, typistIds: typistIds,

View File

@ -1,9 +1,14 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { TemplateFile } from './entity/template_file.entity'; import { TemplateFile } from './entity/template_file.entity';
import { insertEntity, updateEntity } from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class TemplateFilesRepositoryService { export class TemplateFilesRepositoryService {
//クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
@ -11,12 +16,16 @@ export class TemplateFilesRepositoryService {
* @param accountId * @param accountId
* @returns template files * @returns template files
*/ */
async getTemplateFiles(accountId: number): Promise<TemplateFile[]> { async getTemplateFiles(
context: Context,
accountId: number,
): Promise<TemplateFile[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const templateFilesRepo = entityManager.getRepository(TemplateFile); const templateFilesRepo = entityManager.getRepository(TemplateFile);
const templates = await templateFilesRepo.find({ const templates = await templateFilesRepo.find({
where: { account_id: accountId }, where: { account_id: accountId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return templates; return templates;
@ -31,6 +40,7 @@ export class TemplateFilesRepositoryService {
* @returns template file * @returns template file
*/ */
async upsertTemplateFile( async upsertTemplateFile(
context: Context,
accountId: number, accountId: number,
fileName: string, fileName: string,
url: string, url: string,
@ -41,20 +51,30 @@ export class TemplateFilesRepositoryService {
// アカウント内に同名ファイルがあるか確認 // アカウント内に同名ファイルがあるか確認
const template = await templateFilesRepo.findOne({ const template = await templateFilesRepo.findOne({
where: { account_id: accountId, file_name: fileName }, where: { account_id: accountId, file_name: fileName },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 同名ファイルは同じものとして扱うため、すでにファイルがあれば更新(更新日時の履歴を残しておきたい) // 同名ファイルは同じものとして扱うため、すでにファイルがあれば更新(更新日時の履歴を残しておきたい)
if (template) { if (template) {
await templateFilesRepo.update( await updateEntity(
templateFilesRepo,
{ id: template.id }, { id: template.id },
{ file_name: fileName, url: url }, { file_name: fileName, url: url },
this.isCommentOut,
context,
); );
} else { } else {
const newTemplate = new TemplateFile(); const newTemplate = new TemplateFile();
newTemplate.account_id = accountId; newTemplate.account_id = accountId;
newTemplate.file_name = fileName; newTemplate.file_name = fileName;
newTemplate.url = url; newTemplate.url = url;
await templateFilesRepo.save(newTemplate); await insertEntity(
TemplateFile,
templateFilesRepo,
newTemplate,
this.isCommentOut,
context,
);
} }
}); });
} }

View File

@ -4,6 +4,7 @@ import { TermsVersion } from '../../features/terms/types/types';
import { Term } from './entity/term.entity'; import { Term } from './entity/term.entity';
import { TERM_TYPE } from '../../constants'; import { TERM_TYPE } from '../../constants';
import { TermInfoNotFoundError } from '../users/errors/types'; import { TermInfoNotFoundError } from '../users/errors/types';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class TermsRepositoryService { export class TermsRepositoryService {
@ -13,7 +14,7 @@ export class TermsRepositoryService {
* *
* @returns Term[] * @returns Term[]
*/ */
async getLatestTermsInfo(): Promise<TermsVersion> { async getLatestTermsInfo(context: Context): Promise<TermsVersion> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const termRepo = entityManager.getRepository(Term); const termRepo = entityManager.getRepository(Term);
const latestEulaInfo = await termRepo.findOne({ const latestEulaInfo = await termRepo.findOne({
@ -23,6 +24,7 @@ export class TermsRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const latestPrivacyNoticeInfo = await termRepo.findOne({ const latestPrivacyNoticeInfo = await termRepo.findOne({
where: { where: {
@ -31,6 +33,7 @@ export class TermsRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const latestDpaInfo = await termRepo.findOne({ const latestDpaInfo = await termRepo.findOne({
where: { where: {
@ -39,6 +42,7 @@ export class TermsRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) { if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {

View File

@ -5,18 +5,31 @@ import { UserGroupMember } from './entity/user_group_member.entity';
import { User } from '../users/entity/user.entity'; import { User } from '../users/entity/user.entity';
import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types'; import { TypistGroupNotExistError, TypistIdInvalidError } from './errors/types';
import { USER_ROLES } from '../../constants'; import { USER_ROLES } from '../../constants';
import {
insertEntities,
insertEntity,
updateEntity,
deleteEntity,
} from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class UserGroupsRepositoryService { export class UserGroupsRepositoryService {
//クエリログにコメントを付与するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
async getUserGroups(account_id: number): Promise<UserGroup[]> { async getUserGroups(
context: Context,
account_id: number,
): Promise<UserGroup[]> {
const value = await this.dataSource.transaction(async (entityManager) => { const value = await this.dataSource.transaction(async (entityManager) => {
const userGroupRepo = entityManager.getRepository(UserGroup); const userGroupRepo = entityManager.getRepository(UserGroup);
const userGroups = await userGroupRepo.find({ const userGroups = await userGroupRepo.find({
// 論理削除されていないレコードを取得 // 論理削除されていないレコードを取得
where: { account_id: account_id, deleted_at: IsNull() }, where: { account_id: account_id, deleted_at: IsNull() },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return userGroups; return userGroups;
@ -29,6 +42,7 @@ export class UserGroupsRepositoryService {
* @returns users from groups * @returns users from groups
*/ */
async getGroupMembersFromGroupIds( async getGroupMembersFromGroupIds(
context: Context,
groupIds: number[], groupIds: number[],
): Promise<UserGroupMember[]> { ): Promise<UserGroupMember[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
@ -41,6 +55,7 @@ export class UserGroupsRepositoryService {
where: { where: {
user_group_id: In(groupIds), user_group_id: In(groupIds),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return groupMembers; return groupMembers;
@ -54,6 +69,7 @@ export class UserGroupsRepositoryService {
* @returns typist group * @returns typist group
*/ */
async getTypistGroup( async getTypistGroup(
context: Context,
accountId: number, accountId: number,
typistGroupId: number, typistGroupId: number,
): Promise<UserGroup> { ): Promise<UserGroup> {
@ -69,6 +85,7 @@ export class UserGroupsRepositoryService {
relations: { relations: {
userGroupMembers: true, userGroupMembers: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!userGroup) { if (!userGroup) {
@ -88,6 +105,7 @@ export class UserGroupsRepositoryService {
* @returns createdTypistGroup * @returns createdTypistGroup
*/ */
async createTypistGroup( async createTypistGroup(
context: Context,
name: string, name: string,
typistIds: number[], typistIds: number[],
accountId: number, accountId: number,
@ -104,6 +122,7 @@ export class UserGroupsRepositoryService {
role: USER_ROLES.TYPIST, role: USER_ROLES.TYPIST,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (userRecords.length !== typistIds.length) { if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError( throw new TypistIdInvalidError(
@ -113,10 +132,16 @@ export class UserGroupsRepositoryService {
); );
} }
// userGroupをDBに保存する // userGroupをDBに保存する
const userGroup = await userGroupRepo.save({ const userGroup = await insertEntity(
account_id: accountId, UserGroup,
userGroupRepo,
{
name, name,
}); account_id: accountId,
},
this.isCommentOut,
context,
);
const userGroupMembers = userRecords.map((user) => { const userGroupMembers = userRecords.map((user) => {
return { return {
@ -125,7 +150,13 @@ export class UserGroupsRepositoryService {
}; };
}); });
// userGroupMembersをDBに保存する // userGroupMembersをDBに保存する
await userGroupMemberRepo.save(userGroupMembers); await insertEntities(
UserGroupMember,
userGroupMemberRepo,
userGroupMembers,
this.isCommentOut,
context,
);
return userGroup; return userGroup;
}); });
@ -139,6 +170,7 @@ export class UserGroupsRepositoryService {
* @returns createdTypistGroup * @returns createdTypistGroup
*/ */
async updateTypistGroup( async updateTypistGroup(
context: Context,
accountId: number, accountId: number,
typistGroupId: number, typistGroupId: number,
typistGroupName: string, typistGroupName: string,
@ -156,6 +188,7 @@ export class UserGroupsRepositoryService {
role: USER_ROLES.TYPIST, role: USER_ROLES.TYPIST,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (userRecords.length !== typistIds.length) { if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError( throw new TypistIdInvalidError(
@ -171,6 +204,7 @@ export class UserGroupsRepositoryService {
id: typistGroupId, id: typistGroupId,
account_id: accountId, account_id: accountId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!typistGroup) { if (!typistGroup) {
throw new TypistGroupNotExistError( throw new TypistGroupNotExistError(
@ -181,12 +215,23 @@ export class UserGroupsRepositoryService {
// 対象のタイピストグループを更新する // 対象のタイピストグループを更新する
// ユーザーグループ名を更新する // ユーザーグループ名を更新する
typistGroup.name = typistGroupName; typistGroup.name = typistGroupName;
await userGroupRepo.save(typistGroup); await updateEntity(
userGroupRepo,
{ id: typistGroupId },
typistGroup,
this.isCommentOut,
context,
);
// user_group_membersテーブルから対象のタイピストグループのユーザーを削除する // user_group_membersテーブルから対象のタイピストグループのユーザーを削除する
await userGroupMemberRepo.delete({ await deleteEntity(
userGroupMemberRepo,
{
user_group_id: typistGroupId, user_group_id: typistGroupId,
}); },
this.isCommentOut,
context,
);
const typistGroupMembers = userRecords.map((typist) => { const typistGroupMembers = userRecords.map((typist) => {
return { return {
@ -194,7 +239,13 @@ export class UserGroupsRepositoryService {
user_id: typist.id, user_id: typist.id,
}; };
}); });
await userGroupMemberRepo.save(typistGroupMembers); await insertEntities(
UserGroupMember,
userGroupMemberRepo,
typistGroupMembers,
this.isCommentOut,
context,
);
return typistGroup; return typistGroup;
}); });

View File

@ -1,6 +1,12 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { User, newUser } from './entity/user.entity'; import { User, newUser } from './entity/user.entity';
import { DataSource, IsNull, Not, UpdateResult } from 'typeorm'; import {
DataSource,
FindOptionsWhere,
IsNull,
Not,
UpdateResult,
} from 'typeorm';
import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity'; import { SortCriteria } from '../sort_criteria/entity/sort_criteria.entity';
import { import {
getDirection, getDirection,
@ -36,9 +42,18 @@ import { Account } from '../accounts/entity/account.entity';
import { Workflow } from '../workflows/entity/workflow.entity'; import { Workflow } from '../workflows/entity/workflow.entity';
import { Worktype } from '../worktypes/entity/worktype.entity'; import { Worktype } from '../worktypes/entity/worktype.entity';
import { Context } from '../../common/log'; import { Context } from '../../common/log';
import {
insertEntity,
insertEntities,
updateEntity,
deleteEntity,
} from '../../common/repository';
@Injectable() @Injectable()
export class UsersRepositoryService { export class UsersRepositoryService {
// クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
@ -46,7 +61,7 @@ export class UsersRepositoryService {
* @param user * @param user
* @returns User * @returns User
*/ */
async createNormalUser(user: newUser): Promise<User> { async createNormalUser(context: Context, user: newUser): Promise<User> {
const { const {
account_id: accountId, account_id: accountId,
external_id: externalUserId, external_id: externalUserId,
@ -80,7 +95,13 @@ export class UsersRepositoryService {
async (entityManager) => { async (entityManager) => {
const repo = entityManager.getRepository(User); const repo = entityManager.getRepository(User);
const newUser = repo.create(userEntity); const newUser = repo.create(userEntity);
const persisted = await repo.save(newUser); const persisted = await insertEntity(
User,
repo,
newUser,
this.isCommentOut,
context,
);
// ユーザーのタスクソート条件を作成 // ユーザーのタスクソート条件を作成
const sortCriteria = new SortCriteria(); const sortCriteria = new SortCriteria();
@ -91,7 +112,13 @@ export class UsersRepositoryService {
} }
const sortCriteriaRepo = entityManager.getRepository(SortCriteria); const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
const newSortCriteria = sortCriteriaRepo.create(sortCriteria); const newSortCriteria = sortCriteriaRepo.create(sortCriteria);
await sortCriteriaRepo.save(newSortCriteria); await insertEntity(
SortCriteria,
sortCriteriaRepo,
newSortCriteria,
this.isCommentOut,
context,
);
return persisted; return persisted;
}, },
@ -99,12 +126,16 @@ export class UsersRepositoryService {
return createdEntity; return createdEntity;
} }
async findVerifiedUser(sub: string): Promise<User | undefined> { async findVerifiedUser(
context: Context,
sub: string,
): Promise<User | undefined> {
const user = await this.dataSource.getRepository(User).findOne({ const user = await this.dataSource.getRepository(User).findOne({
where: { where: {
external_id: sub, external_id: sub,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -113,7 +144,7 @@ export class UsersRepositoryService {
return user; return user;
} }
async findUserByExternalId(sub: string): Promise<User> { async findUserByExternalId(context: Context, sub: string): Promise<User> {
const user = await this.dataSource.getRepository(User).findOne({ const user = await this.dataSource.getRepository(User).findOne({
where: { where: {
external_id: sub, external_id: sub,
@ -121,6 +152,7 @@ export class UsersRepositoryService {
relations: { relations: {
account: true, account: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -129,11 +161,12 @@ export class UsersRepositoryService {
return user; return user;
} }
async findUserById(id: number): Promise<User> { async findUserById(context: Context, id: number): Promise<User> {
const user = await this.dataSource.getRepository(User).findOne({ const user = await this.dataSource.getRepository(User).findOne({
where: { where: {
id: id, id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -142,12 +175,81 @@ export class UsersRepositoryService {
return user; return user;
} }
/**
* AuthorIDをもとにユーザーを取得します
* AuthorIDがセットされていない場合や
* @param context
* @param authorId AuthorID
* @param accountId ID
* @returns user by author id
*/
async findUserByAuthorId(
context: Context,
authorId: string,
accountId: number,
): Promise<User> {
if (!authorId) {
throw new Error('authorId is not set.');
}
const user = await this.dataSource.getRepository(User).findOne({
where: {
author_id: authorId,
account_id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!user) {
throw new UserNotFoundError(`User not Found.`);
}
return user;
}
/**
* IDを持つアカウントのプライマリ管理者とセカンダリ管理者を取得する
* @param context context
* @param accountId ID
* @throws AccountNotFoundError
* @returns admin users
*/
async findAdminUsers(context: Context, accountId: number): Promise<User[]> {
return this.dataSource.transaction(async (entityManager) => {
const account = await entityManager.getRepository(Account).findOne({
where: {
id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (account == null) {
throw new AccountNotFoundError('account not found');
}
const primaryAdminId = account.primary_admin_user_id;
const secondaryAdminId = account.secondary_admin_user_id;
// IDが有効なユーザーだけを検索対象とする
const targets = [primaryAdminId, secondaryAdminId]
.flatMap((x) => (x == null ? [] : [x]))
.map((x): FindOptionsWhere<User> => ({ id: x }));
const users = await entityManager.getRepository(User).find({
where: targets,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
return users;
});
}
/** /**
* AuthorIdが既に存在するか確認する * AuthorIdが既に存在するか確認する
* @param user * @param user
* @returns true false * @returns true false
*/ */
async existsAuthorId(accountId: number, authorId: string): Promise<boolean> { async existsAuthorId(
context: Context,
accountId: number,
authorId: string,
): Promise<boolean> {
const user = await this.dataSource.getRepository(User).findOne({ const user = await this.dataSource.getRepository(User).findOne({
where: [ where: [
{ {
@ -155,6 +257,7 @@ export class UsersRepositoryService {
author_id: authorId, author_id: authorId,
}, },
], ],
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (user) { if (user) {
@ -169,6 +272,7 @@ export class UsersRepositoryService {
* @returns update * @returns update
*/ */
async update( async update(
context: Context,
accountId: number, accountId: number,
id: number, id: number,
role: string, role: string,
@ -186,6 +290,7 @@ export class UsersRepositoryService {
// 変更対象のユーザーを取得 // 変更対象のユーザーを取得
const targetUser = await repo.findOne({ const targetUser = await repo.findOne({
where: { id: id, account_id: accountId }, where: { id: id, account_id: accountId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
@ -202,6 +307,7 @@ export class UsersRepositoryService {
// ユーザーのロールがAuthorの場合はAuthorIDの重複チェックを行う // ユーザーのロールがAuthorの場合はAuthorIDの重複チェックを行う
const authorIdDuplicatedUser = await repo.findOne({ const authorIdDuplicatedUser = await repo.findOne({
where: { account_id: accountId, id: Not(id), author_id: authorId }, where: { account_id: accountId, id: Not(id), author_id: authorId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 重複したAuthorIDがあった場合はエラー // 重複したAuthorIDがあった場合はエラー
@ -242,7 +348,13 @@ export class UsersRepositoryService {
targetUser.license_alert = licenseAlart; targetUser.license_alert = licenseAlart;
targetUser.notification = notification; targetUser.notification = notification;
const result = await repo.update({ id: id }, targetUser); const result = await updateEntity(
repo,
{ id: id },
targetUser,
this.isCommentOut,
context,
);
// 想定外の更新が行われた場合はロールバックを行った上でエラー送出 // 想定外の更新が行われた場合はロールバックを行った上でエラー送出
if (result.affected !== 1) { if (result.affected !== 1) {
@ -257,13 +369,17 @@ export class UsersRepositoryService {
* @param user * @param user
* @returns update * @returns update
*/ */
async updateUserVerified(id: number): Promise<UpdateResult> { async updateUserVerified(
context: Context,
id: number,
): Promise<UpdateResult> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User); const repo = entityManager.getRepository(User);
const targetUser = await repo.findOne({ const targetUser = await repo.findOne({
where: { where: {
id: id, id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
@ -277,7 +393,13 @@ export class UsersRepositoryService {
targetUser.email_verified = true; targetUser.email_verified = true;
return await repo.update({ id: targetUser.id }, targetUser); return await updateEntity(
repo,
{ id: targetUser.id },
targetUser,
this.isCommentOut,
context,
);
}); });
} }
@ -286,7 +408,10 @@ export class UsersRepositoryService {
* @param id * @param id
* @returns user verified and create trial license * @returns user verified and create trial license
*/ */
async updateUserVerifiedAndCreateTrialLicense(id: number): Promise<void> { async updateUserVerifiedAndCreateTrialLicense(
context: Context,
id: number,
): Promise<void> {
await this.dataSource.transaction(async (entityManager) => { await this.dataSource.transaction(async (entityManager) => {
const userRepo = entityManager.getRepository(User); const userRepo = entityManager.getRepository(User);
const targetUser = await userRepo.findOne({ const targetUser = await userRepo.findOne({
@ -296,6 +421,7 @@ export class UsersRepositoryService {
where: { where: {
id: id, id: id,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
@ -309,7 +435,13 @@ export class UsersRepositoryService {
targetUser.email_verified = true; targetUser.email_verified = true;
await userRepo.update({ id: targetUser.id }, targetUser); await updateEntity(
userRepo,
{ id: targetUser.id },
targetUser,
this.isCommentOut,
context,
);
// トライアルライセンス100件を作成する // トライアルライセンス100件を作成する
const licenseRepo = entityManager.getRepository(License); const licenseRepo = entityManager.getRepository(License);
@ -327,12 +459,13 @@ export class UsersRepositoryService {
licenses.push(license); licenses.push(license);
} }
await licenseRepo await insertEntities(
.createQueryBuilder() License,
.insert() licenseRepo,
.into(License) licenses,
.values(licenses) this.isCommentOut,
.execute(); context,
);
}); });
} }
@ -348,8 +481,12 @@ export class UsersRepositoryService {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User); const repo = entityManager.getRepository(User);
const accountId = (await repo.findOne({ where: { external_id } })) const accountId = (
?.account_id; await repo.findOne({
where: { external_id },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
})
)?.account_id;
if (!accountId) { if (!accountId) {
throw new AccountNotFoundError('Account is Not Found.'); throw new AccountNotFoundError('Account is Not Found.');
@ -363,7 +500,7 @@ export class UsersRepositoryService {
license: true, license: true,
}, },
where: { account_id: accountId }, where: { account_id: accountId },
comment: `${context.getTrackingId()}`, comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return dbUsers; return dbUsers;
@ -375,7 +512,7 @@ export class UsersRepositoryService {
* @param sub * @param sub
* @returns typist users * @returns typist users
*/ */
async findTypistUsers(sub: string): Promise<User[]> { async findTypistUsers(context: Context, sub: string): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User); const repo = entityManager.getRepository(User);
@ -383,6 +520,7 @@ export class UsersRepositoryService {
where: { where: {
external_id: sub, external_id: sub,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上ユーザがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
@ -397,6 +535,7 @@ export class UsersRepositoryService {
email_verified: true, email_verified: true,
deleted_at: IsNull(), deleted_at: IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return typists; return typists;
@ -408,7 +547,7 @@ export class UsersRepositoryService {
* @param accountId * @param accountId
* @returns author users * @returns author users
*/ */
async findAuthorUsers(accountId: number): Promise<User[]> { async findAuthorUsers(context: Context, accountId: number): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User); const repo = entityManager.getRepository(User);
const authors = await repo.find({ const authors = await repo.find({
@ -418,6 +557,7 @@ export class UsersRepositoryService {
email_verified: true, email_verified: true,
deleted_at: IsNull(), deleted_at: IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return authors; return authors;
}); });
@ -428,16 +568,19 @@ export class UsersRepositoryService {
* @param userId * @param userId
* @returns delete * @returns delete
*/ */
async deleteNormalUser(userId: number): Promise<void> { async deleteNormalUser(context: Context, userId: number): Promise<void> {
await this.dataSource.transaction(async (entityManager) => { await this.dataSource.transaction(async (entityManager) => {
const usersRepo = entityManager.getRepository(User); const usersRepo = entityManager.getRepository(User);
const sortCriteriaRepo = entityManager.getRepository(SortCriteria); const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
// ソート条件を削除 // ソート条件を削除
await sortCriteriaRepo.delete({ await deleteEntity(
user_id: userId, sortCriteriaRepo,
}); { user_id: userId },
this.isCommentOut,
context,
);
// プライマリ管理者を削除 // プライマリ管理者を削除
await usersRepo.delete({ id: userId }); await deleteEntity(usersRepo, { id: userId }, this.isCommentOut, context);
}); });
} }
@ -447,6 +590,7 @@ export class UsersRepositoryService {
* @returns TermsCheckInfo * @returns TermsCheckInfo
*/ */
async getAcceptedAndLatestVersion( async getAcceptedAndLatestVersion(
context: Context,
externalId: string, externalId: string,
): Promise<TermsCheckInfo> { ): Promise<TermsCheckInfo> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
@ -458,6 +602,7 @@ export class UsersRepositoryService {
relations: { relations: {
account: true, account: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -475,6 +620,7 @@ export class UsersRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const latestPrivacyNoticeInfo = await termRepo.findOne({ const latestPrivacyNoticeInfo = await termRepo.findOne({
where: { where: {
@ -483,6 +629,7 @@ export class UsersRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
const latestDpaInfo = await termRepo.findOne({ const latestDpaInfo = await termRepo.findOne({
where: { where: {
@ -491,6 +638,7 @@ export class UsersRepositoryService {
order: { order: {
id: 'DESC', id: 'DESC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) { if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {
throw new TermInfoNotFoundError(`Terms info is not found.`); throw new TermInfoNotFoundError(`Terms info is not found.`);
@ -518,6 +666,7 @@ export class UsersRepositoryService {
* @returns update * @returns update
*/ */
async updateAcceptedTermsVersion( async updateAcceptedTermsVersion(
context: Context,
externalId: string, externalId: string,
eulaVersion: string, eulaVersion: string,
privacyNoticeVersion: string, privacyNoticeVersion: string,
@ -532,6 +681,7 @@ export class UsersRepositoryService {
relations: { relations: {
account: true, account: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -563,7 +713,13 @@ export class UsersRepositoryService {
user.accepted_privacy_notice_version = user.accepted_privacy_notice_version =
privacyNoticeVersion ?? user.accepted_privacy_notice_version; privacyNoticeVersion ?? user.accepted_privacy_notice_version;
user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version; user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version;
await userRepo.update({ id: user.id }, user); await updateEntity(
userRepo,
{ id: user.id },
user,
this.isCommentOut,
context,
);
}); });
} }
@ -574,6 +730,7 @@ export class UsersRepositoryService {
* @returns delegate accounts * @returns delegate accounts
*/ */
async findDelegateUser( async findDelegateUser(
context: Context,
delegateAccountId: number, delegateAccountId: number,
originAccountId: number, originAccountId: number,
): Promise<User> { ): Promise<User> {
@ -587,6 +744,7 @@ export class UsersRepositoryService {
parent_account_id: delegateAccountId, parent_account_id: delegateAccountId,
tier: TIERS.TIER5, tier: TIERS.TIER5,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!account) { if (!account) {
@ -619,6 +777,7 @@ export class UsersRepositoryService {
relations: { relations: {
account: true, account: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// 運用上、代行操作対象アカウントの管理者ユーザーがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理 // 運用上、代行操作対象アカウントの管理者ユーザーがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
@ -637,6 +796,7 @@ export class UsersRepositoryService {
* @returns delegate accounts * @returns delegate accounts
*/ */
async isAllowDelegationPermission( async isAllowDelegationPermission(
context: Context,
delegateAccountId: number, delegateAccountId: number,
originUserExternalId: string, originUserExternalId: string,
): Promise<boolean> { ): Promise<boolean> {
@ -653,6 +813,7 @@ export class UsersRepositoryService {
relations: { relations: {
account: true, account: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!primaryUser) { if (!primaryUser) {
@ -677,7 +838,10 @@ export class UsersRepositoryService {
* @param userId * @param userId
* @returns user relations * @returns user relations
*/ */
async getUserRelations(userId: number): Promise<{ async getUserRelations(
context: Context,
userId: number,
): Promise<{
user: User; user: User;
authors: User[]; authors: User[];
worktypes: Worktype[]; worktypes: Worktype[];
@ -689,6 +853,7 @@ export class UsersRepositoryService {
const user = await userRepo.findOne({ const user = await userRepo.findOne({
where: { id: userId }, where: { id: userId },
relations: { account: true }, relations: { account: true },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!user) { if (!user) {
@ -709,6 +874,7 @@ export class UsersRepositoryService {
role: USER_ROLES.AUTHOR, role: USER_ROLES.AUTHOR,
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
// ユーザーの所属するアカウント内のアクティブワークタイプを取得する // ユーザーの所属するアカウント内のアクティブワークタイプを取得する
@ -722,6 +888,7 @@ export class UsersRepositoryService {
account_id: user.account_id, account_id: user.account_id,
id: activeWorktypeId, id: activeWorktypeId,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
})) ?? undefined; })) ?? undefined;
} }
@ -740,6 +907,7 @@ export class UsersRepositoryService {
option_items: true, option_items: true,
}, },
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
worktypes = workflows.flatMap((workflow) => worktypes = workflows.flatMap((workflow) =>

View File

@ -15,9 +15,18 @@ import {
AuthorIdAndWorktypeIdPairAlreadyExistsError, AuthorIdAndWorktypeIdPairAlreadyExistsError,
WorkflowNotFoundError, WorkflowNotFoundError,
} from './errors/types'; } from './errors/types';
import {
insertEntities,
insertEntity,
deleteEntity,
} from '../../common/repository';
import { Context } from '../../common/log';
@Injectable() @Injectable()
export class WorkflowsRepositoryService { export class WorkflowsRepositoryService {
// クエリログにコメントを出力するかどうか
private readonly isCommentOut = process.env.STAGE !== 'local';
constructor(private dataSource: DataSource) {} constructor(private dataSource: DataSource) {}
/** /**
@ -25,7 +34,7 @@ export class WorkflowsRepositoryService {
* @param externalId * @param externalId
* @returns worktypes and active worktype id * @returns worktypes and active worktype id
*/ */
async getWorkflows(accountId: number): Promise<Workflow[]> { async getWorkflows(context: Context, accountId: number): Promise<Workflow[]> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const workflowRepo = entityManager.getRepository(Workflow); const workflowRepo = entityManager.getRepository(Workflow);
@ -43,6 +52,7 @@ export class WorkflowsRepositoryService {
order: { order: {
id: 'ASC', id: 'ASC',
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
return workflows; return workflows;
@ -59,6 +69,7 @@ export class WorkflowsRepositoryService {
* @returns workflows * @returns workflows
*/ */
async createtWorkflows( async createtWorkflows(
context: Context,
accountId: number, accountId: number,
authorId: number, authorId: number,
typists: WorkflowTypist[], typists: WorkflowTypist[],
@ -70,6 +81,7 @@ export class WorkflowsRepositoryService {
const userRepo = entityManager.getRepository(User); const userRepo = entityManager.getRepository(User);
const author = await userRepo.findOne({ const author = await userRepo.findOne({
where: { account_id: accountId, id: authorId, email_verified: true }, where: { account_id: accountId, id: authorId, email_verified: true },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!author) { if (!author) {
throw new UserNotFoundError( throw new UserNotFoundError(
@ -82,6 +94,7 @@ export class WorkflowsRepositoryService {
const worktypeRepo = entityManager.getRepository(Worktype); const worktypeRepo = entityManager.getRepository(Worktype);
const worktypes = await worktypeRepo.find({ const worktypes = await worktypeRepo.find({
where: { account_id: accountId, id: worktypeId }, where: { account_id: accountId, id: worktypeId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (worktypes.length === 0) { if (worktypes.length === 0) {
throw new WorktypeIdNotFoundError( throw new WorktypeIdNotFoundError(
@ -95,6 +108,7 @@ export class WorkflowsRepositoryService {
const templateRepo = entityManager.getRepository(TemplateFile); const templateRepo = entityManager.getRepository(TemplateFile);
const template = await templateRepo.findOne({ const template = await templateRepo.findOne({
where: { account_id: accountId, id: templateId }, where: { account_id: accountId, id: templateId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!template) { if (!template) {
throw new TemplateFileNotExistError('template not found.'); throw new TemplateFileNotExistError('template not found.');
@ -111,6 +125,7 @@ export class WorkflowsRepositoryService {
id: In(typistIds), id: In(typistIds),
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistUsers.length !== typistIds.length) { if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError( throw new UserNotFoundError(
@ -125,6 +140,7 @@ export class WorkflowsRepositoryService {
const userGroupRepo = entityManager.getRepository(UserGroup); const userGroupRepo = entityManager.getRepository(UserGroup);
const typistGroups = await userGroupRepo.find({ const typistGroups = await userGroupRepo.find({
where: { account_id: accountId, id: In(groupIds) }, where: { account_id: accountId, id: In(groupIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistGroups.length !== groupIds.length) { if (typistGroups.length !== groupIds.length) {
throw new TypistGroupNotExistError( throw new TypistGroupNotExistError(
@ -141,6 +157,7 @@ export class WorkflowsRepositoryService {
author_id: authorId, author_id: authorId,
worktype_id: worktypeId !== undefined ? worktypeId : IsNull(), worktype_id: worktypeId !== undefined ? worktypeId : IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (workflow.length !== 0) { if (workflow.length !== 0) {
throw new AuthorIdAndWorktypeIdPairAlreadyExistsError( throw new AuthorIdAndWorktypeIdPairAlreadyExistsError(
@ -156,7 +173,13 @@ export class WorkflowsRepositoryService {
templateId, templateId,
); );
await workflowRepo.save(newWorkflow); await insertEntity(
Workflow,
workflowRepo,
newWorkflow,
this.isCommentOut,
context,
);
// ルーティング候補のデータ作成 // ルーティング候補のデータ作成
const workflowTypists = typists.map((typist) => const workflowTypists = typists.map((typist) =>
@ -168,7 +191,13 @@ export class WorkflowsRepositoryService {
); );
const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist);
await workflowTypistsRepo.save(workflowTypists); await insertEntities(
DbWorkflowTypist,
workflowTypistsRepo,
workflowTypists,
this.isCommentOut,
context,
);
}); });
} }
@ -183,6 +212,7 @@ export class WorkflowsRepositoryService {
* @returns workflow * @returns workflow
*/ */
async updatetWorkflow( async updatetWorkflow(
context: Context,
accountId: number, accountId: number,
workflowId: number, workflowId: number,
authorId: number, authorId: number,
@ -196,6 +226,7 @@ export class WorkflowsRepositoryService {
// ワークフローの存在確認 // ワークフローの存在確認
const targetWorkflow = await workflowRepo.findOne({ const targetWorkflow = await workflowRepo.findOne({
where: { account_id: accountId, id: workflowId }, where: { account_id: accountId, id: workflowId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!targetWorkflow) { if (!targetWorkflow) {
throw new WorkflowNotFoundError( throw new WorkflowNotFoundError(
@ -207,6 +238,7 @@ export class WorkflowsRepositoryService {
const userRepo = entityManager.getRepository(User); const userRepo = entityManager.getRepository(User);
const author = await userRepo.findOne({ const author = await userRepo.findOne({
where: { account_id: accountId, id: authorId, email_verified: true }, where: { account_id: accountId, id: authorId, email_verified: true },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!author) { if (!author) {
throw new UserNotFoundError( throw new UserNotFoundError(
@ -219,6 +251,7 @@ export class WorkflowsRepositoryService {
const worktypeRepo = entityManager.getRepository(Worktype); const worktypeRepo = entityManager.getRepository(Worktype);
const worktypes = await worktypeRepo.find({ const worktypes = await worktypeRepo.find({
where: { account_id: accountId, id: worktypeId }, where: { account_id: accountId, id: worktypeId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (worktypes.length === 0) { if (worktypes.length === 0) {
throw new WorktypeIdNotFoundError( throw new WorktypeIdNotFoundError(
@ -232,6 +265,7 @@ export class WorkflowsRepositoryService {
const templateRepo = entityManager.getRepository(TemplateFile); const templateRepo = entityManager.getRepository(TemplateFile);
const template = await templateRepo.findOne({ const template = await templateRepo.findOne({
where: { account_id: accountId, id: templateId }, where: { account_id: accountId, id: templateId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!template) { if (!template) {
throw new TemplateFileNotExistError( throw new TemplateFileNotExistError(
@ -250,6 +284,7 @@ export class WorkflowsRepositoryService {
id: In(typistIds), id: In(typistIds),
email_verified: true, email_verified: true,
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistUsers.length !== typistIds.length) { if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError( throw new UserNotFoundError(
@ -264,6 +299,7 @@ export class WorkflowsRepositoryService {
const userGroupRepo = entityManager.getRepository(UserGroup); const userGroupRepo = entityManager.getRepository(UserGroup);
const typistGroups = await userGroupRepo.find({ const typistGroups = await userGroupRepo.find({
where: { account_id: accountId, id: In(groupIds) }, where: { account_id: accountId, id: In(groupIds) },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (typistGroups.length !== groupIds.length) { if (typistGroups.length !== groupIds.length) {
throw new TypistGroupNotExistError( throw new TypistGroupNotExistError(
@ -274,8 +310,18 @@ export class WorkflowsRepositoryService {
const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist);
// 既存データの削除 // 既存データの削除
await workflowTypistsRepo.delete({ workflow_id: workflowId }); await deleteEntity(
await workflowRepo.delete(workflowId); workflowTypistsRepo,
{ workflow_id: workflowId },
this.isCommentOut,
context,
);
await deleteEntity(
workflowRepo,
{ id: workflowId },
this.isCommentOut,
context,
);
{ {
// ワークフローの重複確認 // ワークフローの重複確認
@ -285,6 +331,7 @@ export class WorkflowsRepositoryService {
author_id: authorId, author_id: authorId,
worktype_id: worktypeId !== undefined ? worktypeId : IsNull(), worktype_id: worktypeId !== undefined ? worktypeId : IsNull(),
}, },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (duplicateWorkflow.length !== 0) { if (duplicateWorkflow.length !== 0) {
throw new AuthorIdAndWorktypeIdPairAlreadyExistsError( throw new AuthorIdAndWorktypeIdPairAlreadyExistsError(
@ -301,7 +348,13 @@ export class WorkflowsRepositoryService {
templateId, templateId,
); );
await workflowRepo.save(newWorkflow); await insertEntity(
Workflow,
workflowRepo,
newWorkflow,
this.isCommentOut,
context,
);
// ルーティング候補のデータ作成 // ルーティング候補のデータ作成
const workflowTypists = typists.map((typist) => const workflowTypists = typists.map((typist) =>
@ -312,7 +365,13 @@ export class WorkflowsRepositoryService {
), ),
); );
await workflowTypistsRepo.save(workflowTypists); await insertEntities(
DbWorkflowTypist,
workflowTypistsRepo,
workflowTypists,
this.isCommentOut,
context,
);
}); });
} }
@ -322,7 +381,11 @@ export class WorkflowsRepositoryService {
* @param workflowId * @param workflowId
* @returns workflow * @returns workflow
*/ */
async deleteWorkflow(accountId: number, workflowId: number): Promise<void> { async deleteWorkflow(
context: Context,
accountId: number,
workflowId: number,
): Promise<void> {
return await this.dataSource.transaction(async (entityManager) => { return await this.dataSource.transaction(async (entityManager) => {
const workflowRepo = entityManager.getRepository(Workflow); const workflowRepo = entityManager.getRepository(Workflow);
const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist); const workflowTypistsRepo = entityManager.getRepository(DbWorkflowTypist);
@ -330,15 +393,25 @@ export class WorkflowsRepositoryService {
// ワークフローの存在確認 // ワークフローの存在確認
const workflow = await workflowRepo.findOne({ const workflow = await workflowRepo.findOne({
where: { account_id: accountId, id: workflowId }, where: { account_id: accountId, id: workflowId },
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
}); });
if (!workflow) { if (!workflow) {
throw new WorkflowNotFoundError( throw new WorkflowNotFoundError(
`workflow not found. id: ${workflowId}`, `workflow not found. id: ${workflowId}`,
); );
} }
await deleteEntity(
await workflowTypistsRepo.delete({ workflow_id: workflowId }); workflowTypistsRepo,
await workflowRepo.delete(workflowId); { workflow_id: workflowId },
this.isCommentOut,
context,
);
await deleteEntity(
workflowRepo,
{ id: workflowId },
this.isCommentOut,
context,
);
}); });
} }

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