diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json
index f7e485d..71b532f 100644
--- a/dictation_client/src/translation/de.json
+++ b/dictation_client/src/translation/de.json
@@ -532,6 +532,7 @@
"title": "(de)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(de)Click here to read the terms of use.",
"linkOfDpa": "(de)Click here to read the terms of use.",
+ "linkOfPrivacyNotice": "(de)Click here to read the terms of use.",
"checkBoxForConsent": "(de)Yes, I agree to the terms of use.",
"forOdds": "(de)for ODDS.",
"button": "(de)Continue",
diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json
index 6da4f95..cbf9f66 100644
--- a/dictation_client/src/translation/en.json
+++ b/dictation_client/src/translation/en.json
@@ -532,6 +532,7 @@
"title": "Terms of Use has updated. Please confirm again.",
"linkOfEula": "Click here to read the terms of use.",
"linkOfDpa": "Click here to read the terms of use.",
+ "linkOfPrivacyNotice": "Click here to read the terms of use.",
"checkBoxForConsent": "Yes, I agree to the terms of use.",
"forOdds": "for ODDS.",
"button": "Continue",
diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json
index d6711e3..552edb2 100644
--- a/dictation_client/src/translation/es.json
+++ b/dictation_client/src/translation/es.json
@@ -532,6 +532,7 @@
"title": "(es)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(es)Click here to read the terms of use.",
"linkOfDpa": "(es)Click here to read the terms of use.",
+ "linkOfPrivacyNotice": "(es)Click here to read the terms of use.",
"checkBoxForConsent": "(es)Yes, I agree to the terms of use.",
"forOdds": "(es)for ODDS.",
"button": "(es)Continue",
diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json
index d6735e1..e3bf194 100644
--- a/dictation_client/src/translation/fr.json
+++ b/dictation_client/src/translation/fr.json
@@ -532,6 +532,7 @@
"title": "(fr)Terms of Use has updated. Please confirm again.",
"linkOfEula": "(fr)Click here to read the terms of use.",
"linkOfDpa": "(fr)Click here to read the terms of use.",
+ "linkOfPrivacyNotice": "(fr)Click here to read the terms of use.",
"checkBoxForConsent": "(fr)Yes, I agree to the terms of use.",
"forOdds": "(fr)for ODDS.",
"button": "(fr)Continue",
diff --git a/dictation_server/src/api/odms/openapi.json b/dictation_server/src/api/odms/openapi.json
index 7667921..069d43c 100644
--- a/dictation_server/src/api/odms/openapi.json
+++ b/dictation_server/src/api/odms/openapi.json
@@ -3537,6 +3537,10 @@
"type": "string",
"description": "同意済み利用規約のバージョン(EULA)"
},
+ "acceptedPrivacyNoticeVersion": {
+ "type": "string",
+ "description": "同意済みプライバシーポリシーのバージョン"
+ },
"acceptedDpaVersion": {
"type": "string",
"description": "同意済み利用規約のバージョン(DPA)"
@@ -3550,6 +3554,7 @@
"adminMail",
"adminPassword",
"acceptedEulaVersion",
+ "acceptedPrivacyNoticeVersion",
"acceptedDpaVersion",
"token"
]
@@ -4292,12 +4297,20 @@
"type": "string",
"description": "更新バージョン(EULA)"
},
+ "acceptedPrivacyNoticeVersion": {
+ "type": "string",
+ "description": "更新バージョン(PrivacyNotice)"
+ },
"acceptedDPAVersion": {
"type": "string",
"description": "更新バージョン(DPA)"
}
},
- "required": ["idToken", "acceptedEULAVersion"]
+ "required": [
+ "idToken",
+ "acceptedEULAVersion",
+ "acceptedPrivacyNoticeVersion"
+ ]
},
"UpdateAcceptedVersionResponse": { "type": "object", "properties": {} },
"GetMyUserResponse": {
diff --git a/dictation_server/src/common/test/utility.ts b/dictation_server/src/common/test/utility.ts
index 00b19d7..18dda44 100644
--- a/dictation_server/src/common/test/utility.ts
+++ b/dictation_server/src/common/test/utility.ts
@@ -182,6 +182,8 @@ export const makeTestAccount = async (
role: d?.role ?? 'admin none',
author_id: d?.author_id ?? undefined,
accepted_eula_version: d?.accepted_eula_version ?? '1.0',
+ accepted_privacy_notice_version:
+ d?.accepted_privacy_notice_version ?? '1.0',
accepted_dpa_version: d?.accepted_dpa_version ?? '1.0',
email_verified: d?.email_verified ?? true,
auto_renew: d?.auto_renew ?? true,
diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts
index 0f8a7eb..439c7ed 100644
--- a/dictation_server/src/constants/index.ts
+++ b/dictation_server/src/constants/index.ts
@@ -287,6 +287,7 @@ export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]';
export const TERM_TYPE = {
EULA: 'EULA',
DPA: 'DPA',
+ PRIVACY_NOTICE: 'PrivacyNotice',
} as const;
/**
diff --git a/dictation_server/src/features/accounts/accounts.controller.ts b/dictation_server/src/features/accounts/accounts.controller.ts
index 04c8e26..619aa2b 100644
--- a/dictation_server/src/features/accounts/accounts.controller.ts
+++ b/dictation_server/src/features/accounts/accounts.controller.ts
@@ -118,6 +118,7 @@ export class AccountsController {
adminPassword,
adminName,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
} = body;
const role = USER_ROLES.NONE;
@@ -134,6 +135,7 @@ export class AccountsController {
adminName,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
diff --git a/dictation_server/src/features/accounts/accounts.service.spec.ts b/dictation_server/src/features/accounts/accounts.service.spec.ts
index 2656aed..27c3715 100644
--- a/dictation_server/src/features/accounts/accounts.service.spec.ts
+++ b/dictation_server/src/features/accounts/accounts.service.spec.ts
@@ -112,6 +112,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -144,6 +145,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
// 作成したアカウントのIDが返ってくるか確認
@@ -161,6 +163,9 @@ describe('createAccount', () => {
expect(account?.primary_admin_user_id).toBe(user?.id);
expect(account?.secondary_admin_user_id).toBe(null);
expect(user?.accepted_eula_version).toBe(acceptedEulaVersion);
+ expect(user?.accepted_privacy_notice_version).toBe(
+ acceptedPrivacyNoticeVersion,
+ );
expect(user?.accepted_dpa_version).toBe(acceptedDpaVersion);
expect(user?.account_id).toBe(accountId);
expect(user?.role).toBe(role);
@@ -195,6 +200,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'admin none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -216,6 +222,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -264,6 +271,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'admin none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -286,6 +294,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -318,6 +327,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -345,6 +355,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -384,6 +395,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -411,6 +423,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -452,6 +465,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -480,6 +494,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -520,6 +535,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -551,6 +567,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -593,6 +610,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -641,6 +659,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
@@ -689,6 +708,7 @@ describe('createAccount', () => {
const username = 'dummy_username';
const role = 'none';
const acceptedEulaVersion = '1.0.0';
+ const acceptedPrivacyNoticeVersion = '1.0.0';
const acceptedDpaVersion = '1.0.0';
overrideAdB2cService(service, {
@@ -734,6 +754,7 @@ describe('createAccount', () => {
username,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
} catch (e) {
diff --git a/dictation_server/src/features/accounts/accounts.service.ts b/dictation_server/src/features/accounts/accounts.service.ts
index 49f61f2..668f815 100644
--- a/dictation_server/src/features/accounts/accounts.service.ts
+++ b/dictation_server/src/features/accounts/accounts.service.ts
@@ -176,6 +176,7 @@ export class AccountsService {
username: string,
role: string,
acceptedEulaVersion: string,
+ acceptedPrivacyNoticeVersion: string,
acceptedDpaVersion: string,
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
this.logger.log(
@@ -185,6 +186,7 @@ export class AccountsService {
`dealerAccountId: ${dealerAccountId}, ` +
`role: ${role}, ` +
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
+ `acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` +
`acceptedDpaVersion: ${acceptedDpaVersion} };`,
);
try {
@@ -233,6 +235,7 @@ export class AccountsService {
externalUser.sub,
role,
acceptedEulaVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDpaVersion,
);
account = newAccount;
diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts
index 8a555d6..276fd0e 100644
--- a/dictation_server/src/features/accounts/types/types.ts
+++ b/dictation_server/src/features/accounts/types/types.ts
@@ -45,6 +45,8 @@ export class CreateAccountRequest {
adminPassword: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(EULA)' })
acceptedEulaVersion: string;
+ @ApiProperty({ description: '同意済みプライバシーポリシーのバージョン' })
+ acceptedPrivacyNoticeVersion: string;
@ApiProperty({ description: '同意済み利用規約のバージョン(DPA)' })
acceptedDpaVersion: string;
@ApiProperty({ description: 'reCAPTCHA Token' })
diff --git a/dictation_server/src/features/auth/auth.service.spec.ts b/dictation_server/src/features/auth/auth.service.spec.ts
index 4451cb0..f978889 100644
--- a/dictation_server/src/features/auth/auth.service.spec.ts
+++ b/dictation_server/src/features/auth/auth.service.spec.ts
@@ -196,6 +196,7 @@ describe('checkIsAcceptedLatestVersion', () => {
};
await createTermInfo(source, 'EULA', '1.0');
+ await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(true);
@@ -219,6 +220,7 @@ describe('checkIsAcceptedLatestVersion', () => {
};
await createTermInfo(source, 'EULA', '1.0');
+ await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(true);
@@ -242,6 +244,7 @@ describe('checkIsAcceptedLatestVersion', () => {
};
await createTermInfo(source, 'EULA', '1.1');
+ await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
@@ -265,6 +268,7 @@ describe('checkIsAcceptedLatestVersion', () => {
};
await createTermInfo(source, 'EULA', '1.1');
+ await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
@@ -288,10 +292,35 @@ describe('checkIsAcceptedLatestVersion', () => {
};
await createTermInfo(source, 'EULA', '1.0');
+ await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.1');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
});
+
+ it('同意済みプライバシーポリシーが最新でないときにチェックが通らないこと(第一~第四)', async () => {
+ if (!source) fail();
+ const module = await makeTestingModule(source);
+ if (!module) fail();
+ const service = module.get(AuthService);
+ const { admin } = await makeTestAccount(source, {
+ tier: 4,
+ });
+ const context = makeContext(uuidv4());
+
+ const idToken = {
+ emails: [],
+ sub: admin.external_id,
+ exp: 0,
+ iat: 0,
+ };
+
+ await createTermInfo(source, 'EULA', '1.0');
+ await createTermInfo(source, 'PrivacyNotice', '1.1');
+ await createTermInfo(source, 'DPA', '1.0');
+ const result = await service.isAcceptedLatestVersion(context, idToken);
+ expect(result).toBe(false);
+ });
});
describe('generateDelegationRefreshToken', () => {
diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts
index 543ca12..dbcdfc3 100644
--- a/dictation_server/src/features/auth/auth.service.ts
+++ b/dictation_server/src/features/auth/auth.service.ts
@@ -689,28 +689,38 @@ export class AuthService {
const {
acceptedEulaVersion,
latestEulaVersion,
+ acceptedPrivacyNoticeVersion,
+ latestPrivacyNoticeVersion,
acceptedDpaVersion,
latestDpaVersion,
tier,
} = await this.usersRepository.getAcceptedAndLatestVersion(idToken.sub);
- // 第五階層はEULAのみ判定
+ // 第五階層はEULAとPrivacyNoticeのみ判定
if (tier === TIERS.TIER5) {
- if (!acceptedEulaVersion) {
+ if (!acceptedEulaVersion || !acceptedPrivacyNoticeVersion) {
return false;
}
// 最新バージョンに同意済みか判定
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
- return eulaAccepted;
+ const privacyNoticeAccepted =
+ acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion;
+ return eulaAccepted && privacyNoticeAccepted;
} else {
- // 第一~第四階層はEULA、DPAを判定
- if (!acceptedEulaVersion || !acceptedDpaVersion) {
+ // 第一~第四階層はEULA、PrivacyNotice、DPAを判定
+ if (
+ !acceptedEulaVersion ||
+ !acceptedPrivacyNoticeVersion ||
+ !acceptedDpaVersion
+ ) {
return false;
}
// 最新バージョンに同意済みか判定
const eulaAccepted = acceptedEulaVersion === latestEulaVersion;
+ const privacyNoticeAccepted =
+ acceptedPrivacyNoticeVersion === latestPrivacyNoticeVersion;
const dpaAccepted = acceptedDpaVersion === latestDpaVersion;
- return eulaAccepted && dpaAccepted;
+ return eulaAccepted && privacyNoticeAccepted && dpaAccepted;
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
diff --git a/dictation_server/src/features/auth/types/types.ts b/dictation_server/src/features/auth/types/types.ts
index 1be9570..c031508 100644
--- a/dictation_server/src/features/auth/types/types.ts
+++ b/dictation_server/src/features/auth/types/types.ts
@@ -22,8 +22,10 @@ export class AccessTokenRequest {}
export type TermsCheckInfo = {
tier: number;
acceptedEulaVersion?: string;
+ acceptedPrivacyNoticeVersion?: string;
acceptedDpaVersion?: string;
latestEulaVersion: string;
+ latestPrivacyNoticeVersion: string;
latestDpaVersion: string;
};
diff --git a/dictation_server/src/features/files/test/files.service.mock.ts b/dictation_server/src/features/files/test/files.service.mock.ts
index a5b8f06..c69e7bf 100644
--- a/dictation_server/src/features/files/test/files.service.mock.ts
+++ b/dictation_server/src/features/files/test/files.service.mock.ts
@@ -139,6 +139,7 @@ export const makeDefaultUsersRepositoryMockValue =
role: 'none',
author_id: '',
accepted_eula_version: '1.0',
+ accepted_privacy_notice_version: '1.0',
accepted_dpa_version: '1.0',
email_verified: true,
deleted_at: null,
diff --git a/dictation_server/src/features/tasks/test/tasks.service.mock.ts b/dictation_server/src/features/tasks/test/tasks.service.mock.ts
index cf5aa71..5d5d97c 100644
--- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts
+++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts
@@ -470,6 +470,7 @@ const defaultTasksRepositoryMockValue: {
external_id: 'userId',
role: 'typist',
accepted_eula_version: '',
+ accepted_privacy_notice_version: '',
accepted_dpa_version: '',
email_verified: true,
auto_renew: true,
diff --git a/dictation_server/src/features/terms/terms.service.spec.ts b/dictation_server/src/features/terms/terms.service.spec.ts
index 772e9f5..6ff1176 100644
--- a/dictation_server/src/features/terms/terms.service.spec.ts
+++ b/dictation_server/src/features/terms/terms.service.spec.ts
@@ -34,6 +34,8 @@ describe('利用規約取得', () => {
await createTermInfo(source, 'EULA', 'v1.0');
await createTermInfo(source, 'EULA', 'v1.1');
+ await createTermInfo(source, 'PrivacyNotice', 'v1.0');
+ await createTermInfo(source, 'PrivacyNotice', 'v1.1');
await createTermInfo(source, 'DPA', 'v1.0');
await createTermInfo(source, 'DPA', 'v1.2');
@@ -42,8 +44,10 @@ describe('利用規約取得', () => {
expect(result[0].documentType).toBe('EULA');
expect(result[0].version).toBe('v1.1');
- expect(result[1].documentType).toBe('DPA');
- expect(result[1].version).toBe('v1.2');
+ expect(result[1].documentType).toBe('PrivacyNotice');
+ expect(result[1].version).toBe('v1.1');
+ expect(result[2].documentType).toBe('DPA');
+ expect(result[2].version).toBe('v1.2');
});
it('利用規約情報(EULA、DPA両方)が存在しない場合エラーとなる', async () => {
@@ -75,6 +79,21 @@ describe('利用規約取得', () => {
);
});
+ it('利用規約情報(PrivacyNoticeのみ)が存在しない場合エラーとなる', async () => {
+ if (!source) fail();
+ const module = await makeTestingModule(source);
+ if (!module) fail();
+ const service = module.get(TermsService);
+ await createTermInfo(source, 'PrivacyNotice', 'v1.0');
+ const context = makeContext(uuidv4());
+ await expect(service.getTermsInfo(context)).rejects.toEqual(
+ new HttpException(
+ makeErrorResponse('E009999'),
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ ),
+ );
+ });
+
it('利用規約情報(DPAのみ)が存在しない場合エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
diff --git a/dictation_server/src/features/terms/terms.service.ts b/dictation_server/src/features/terms/terms.service.ts
index 65137a1..ee52415 100644
--- a/dictation_server/src/features/terms/terms.service.ts
+++ b/dictation_server/src/features/terms/terms.service.ts
@@ -19,13 +19,17 @@ export class TermsService {
`[IN] [${context.getTrackingId()}] ${this.getTermsInfo.name}`,
);
try {
- const { eulaVersion, dpaVersion } =
+ const { eulaVersion, privacyNoticeVersion, dpaVersion } =
await this.termsRepository.getLatestTermsInfo();
return [
{
documentType: TERM_TYPE.EULA,
version: eulaVersion,
},
+ {
+ documentType: TERM_TYPE.PRIVACY_NOTICE,
+ version: privacyNoticeVersion,
+ },
{
documentType: TERM_TYPE.DPA,
version: dpaVersion,
diff --git a/dictation_server/src/features/terms/types/types.ts b/dictation_server/src/features/terms/types/types.ts
index 6a45eae..479960e 100644
--- a/dictation_server/src/features/terms/types/types.ts
+++ b/dictation_server/src/features/terms/types/types.ts
@@ -13,5 +13,6 @@ export class GetTermsInfoResponse {
export type TermsVersion = {
eulaVersion: string;
+ privacyNoticeVersion: string;
dpaVersion: string;
};
diff --git a/dictation_server/src/features/users/types/types.ts b/dictation_server/src/features/users/types/types.ts
index 9f48418..d21f72d 100644
--- a/dictation_server/src/features/users/types/types.ts
+++ b/dictation_server/src/features/users/types/types.ts
@@ -263,6 +263,8 @@ export class UpdateAcceptedVersionRequest {
idToken: string;
@ApiProperty({ description: '更新バージョン(EULA)' })
acceptedEULAVersion: string;
+ @ApiProperty({ description: '更新バージョン(PrivacyNotice)' })
+ acceptedPrivacyNoticeVersion: string;
@ApiProperty({ description: '更新バージョン(DPA)', required: false })
acceptedDPAVersion?: string;
}
diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts
index a92bbd2..9f88897 100644
--- a/dictation_server/src/features/users/users.controller.ts
+++ b/dictation_server/src/features/users/users.controller.ts
@@ -627,7 +627,12 @@ export class UsersController {
async updateAcceptedVersion(
@Body() body: UpdateAcceptedVersionRequest,
): Promise {
- const { idToken, acceptedEULAVersion, acceptedDPAVersion } = body;
+ const {
+ idToken,
+ acceptedEULAVersion,
+ acceptedPrivacyNoticeVersion,
+ acceptedDPAVersion,
+ } = body;
const context = makeContext(uuidv4());
@@ -650,6 +655,7 @@ export class UsersController {
context,
verifiedIdToken.sub,
acceptedEULAVersion,
+ acceptedPrivacyNoticeVersion,
acceptedDPAVersion,
);
return {};
diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts
index 83894ee..cea6f31 100644
--- a/dictation_server/src/features/users/users.service.spec.ts
+++ b/dictation_server/src/features/users/users.service.spec.ts
@@ -208,6 +208,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
account_id: 1,
role: 'None',
accepted_eula_version: 'string',
+ accepted_privacy_notice_version: 'string',
accepted_dpa_version: 'string',
email_verified: false,
created_by: 'string;',
@@ -259,6 +260,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
account_id: 1,
role: 'None',
accepted_eula_version: 'string',
+ accepted_privacy_notice_version: 'string',
accepted_dpa_version: 'string',
email_verified: false,
created_by: 'string;',
@@ -306,6 +308,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
account_id: 1,
role: 'None',
accepted_eula_version: 'string',
+ accepted_privacy_notice_version: 'string',
accepted_dpa_version: 'string',
email_verified: true,
created_by: 'string;',
@@ -358,6 +361,7 @@ describe('UsersService.confirmUserAndInitPassword', () => {
account_id: 1,
role: 'None',
accepted_eula_version: 'string',
+ accepted_privacy_notice_version: 'string',
accepted_dpa_version: 'string',
email_verified: false,
created_by: 'string;',
@@ -2617,7 +2621,12 @@ describe('UsersService.updateAcceptedVersion', () => {
const context = makeContext(uuidv4());
const service = module.get(UsersService);
- await service.updateAcceptedVersion(context, admin.external_id, 'v2.0');
+ await service.updateAcceptedVersion(
+ context,
+ admin.external_id,
+ 'v2.0',
+ 'v2.0',
+ );
const user = await getUser(source, admin.id);
expect(user?.accepted_eula_version).toBe('v2.0');
@@ -2637,6 +2646,7 @@ describe('UsersService.updateAcceptedVersion', () => {
context,
admin.external_id,
'v2.0',
+ 'v2.0',
'v3.0',
);
const user = await getUser(source, admin.id);
@@ -2660,6 +2670,7 @@ describe('UsersService.updateAcceptedVersion', () => {
context,
admin.external_id,
'v2.0',
+ 'v2.0',
undefined,
),
).rejects.toEqual(
diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts
index cf82fd5..60e3e92 100644
--- a/dictation_server/src/features/users/users.service.ts
+++ b/dictation_server/src/features/users/users.service.ts
@@ -403,6 +403,7 @@ export class UsersService {
role,
accepted_dpa_version: null,
accepted_eula_version: null,
+ accepted_privacy_notice_version: null,
encryption: false,
encryption_password: null,
prompt: false,
@@ -422,6 +423,7 @@ export class UsersService {
prompt: prompt ?? false,
accepted_dpa_version: null,
accepted_eula_version: null,
+ accepted_privacy_notice_version: null,
};
default:
//不正なroleが指定された場合はログを出力してエラーを返す
@@ -1044,12 +1046,14 @@ export class UsersService {
* @param context
* @param idToken
* @param eulaVersion
+ * @param privacyNoticeVersion
* @param dpaVersion
*/
async updateAcceptedVersion(
context: Context,
externalId: string,
eulaVersion: string,
+ privacyNoticeVersion: string,
dpaVersion?: string,
): Promise {
this.logger.log(
@@ -1058,6 +1062,7 @@ export class UsersService {
} | params: { ` +
`externalId: ${externalId}, ` +
`eulaVersion: ${eulaVersion}, ` +
+ `privacyNoticeVersion: ${privacyNoticeVersion}, ` +
`dpaVersion: ${dpaVersion}, };`,
);
@@ -1065,6 +1070,7 @@ export class UsersService {
await this.usersRepository.updateAcceptedTermsVersion(
externalId,
eulaVersion,
+ privacyNoticeVersion,
dpaVersion,
);
} catch (e) {
diff --git a/dictation_server/src/repositories/accounts/accounts.repository.service.ts b/dictation_server/src/repositories/accounts/accounts.repository.service.ts
index 1df7d13..d7762bd 100644
--- a/dictation_server/src/repositories/accounts/accounts.repository.service.ts
+++ b/dictation_server/src/repositories/accounts/accounts.repository.service.ts
@@ -127,6 +127,7 @@ export class AccountsRepositoryService {
adminExternalUserId: string,
adminUserRole: string,
adminUserAcceptedEulaVersion?: string,
+ adminUserAcceptedPrivacyNoticeVersion?: string,
adminUserAcceptedDpaVersion?: string,
): Promise<{ newAccount: Account; adminUser: User }> {
return await this.dataSource.transaction(async (entityManager) => {
@@ -148,6 +149,8 @@ export class AccountsRepositoryService {
user.external_id = adminExternalUserId;
user.role = adminUserRole;
user.accepted_eula_version = adminUserAcceptedEulaVersion ?? null;
+ user.accepted_privacy_notice_version =
+ adminUserAcceptedPrivacyNoticeVersion ?? null;
user.accepted_dpa_version = adminUserAcceptedDpaVersion ?? null;
}
const usersRepo = entityManager.getRepository(User);
diff --git a/dictation_server/src/repositories/terms/terms.repository.service.ts b/dictation_server/src/repositories/terms/terms.repository.service.ts
index 7c79f24..7deee0f 100644
--- a/dictation_server/src/repositories/terms/terms.repository.service.ts
+++ b/dictation_server/src/repositories/terms/terms.repository.service.ts
@@ -24,6 +24,14 @@ export class TermsRepositoryService {
id: 'DESC',
},
});
+ const latestPrivacyNoticeInfo = await termRepo.findOne({
+ where: {
+ document_type: TERM_TYPE.PRIVACY_NOTICE,
+ },
+ order: {
+ id: 'DESC',
+ },
+ });
const latestDpaInfo = await termRepo.findOne({
where: {
document_type: TERM_TYPE.DPA,
@@ -33,13 +41,16 @@ export class TermsRepositoryService {
},
});
- if (!latestEulaInfo || !latestDpaInfo) {
+ if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {
throw new TermInfoNotFoundError(
- `Terms info is not found. latestEulaInfo: ${latestEulaInfo}, latestDpaInfo: ${latestDpaInfo}`,
+ `Terms info is not found. latestEulaInfo: ${latestEulaInfo},
+ latestPrivacyNoticeInfo: ${latestPrivacyNoticeInfo},
+ latestDpaInfo: ${latestDpaInfo}`,
);
}
return {
eulaVersion: latestEulaInfo.version,
+ privacyNoticeVersion: latestEulaInfo.version,
dpaVersion: latestDpaInfo.version,
};
});
diff --git a/dictation_server/src/repositories/users/entity/user.entity.ts b/dictation_server/src/repositories/users/entity/user.entity.ts
index 34d0bca..0f4e57c 100644
--- a/dictation_server/src/repositories/users/entity/user.entity.ts
+++ b/dictation_server/src/repositories/users/entity/user.entity.ts
@@ -34,6 +34,9 @@ export class User {
@Column({ nullable: true, type: 'varchar' })
accepted_eula_version: string | null;
+ @Column({ nullable: true, type: 'varchar' })
+ accepted_privacy_notice_version: string | null;
+
@Column({ nullable: true, type: 'varchar' })
accepted_dpa_version: string | null;
@@ -112,6 +115,9 @@ export class UserArchive {
@Column({ nullable: true, type: 'varchar' })
accepted_eula_version: string | null;
+ @Column({ nullable: true, type: 'varchar' })
+ accepted_privacy_notice_version: string | null;
+
@Column({ nullable: true, type: 'varchar' })
accepted_dpa_version: string | null;
diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts
index af179e9..7cfec4d 100644
--- a/dictation_server/src/repositories/users/users.repository.service.ts
+++ b/dictation_server/src/repositories/users/users.repository.service.ts
@@ -360,7 +360,7 @@ export class UsersRepositoryService {
},
where: { account_id: accountId },
});
-
+
return dbUsers;
});
}
@@ -471,6 +471,14 @@ export class UsersRepositoryService {
id: 'DESC',
},
});
+ const latestPrivacyNoticeInfo = await termRepo.findOne({
+ where: {
+ document_type: TERM_TYPE.PRIVACY_NOTICE,
+ },
+ order: {
+ id: 'DESC',
+ },
+ });
const latestDpaInfo = await termRepo.findOne({
where: {
document_type: TERM_TYPE.DPA,
@@ -479,16 +487,18 @@ export class UsersRepositoryService {
id: 'DESC',
},
});
-
- if (!latestEulaInfo || !latestDpaInfo) {
+ if (!latestEulaInfo || !latestPrivacyNoticeInfo || !latestDpaInfo) {
throw new TermInfoNotFoundError(`Terms info is not found.`);
}
return {
tier: user.account.tier,
acceptedEulaVersion: user.accepted_eula_version ?? undefined,
+ acceptedPrivacyNoticeVersion:
+ user.accepted_privacy_notice_version ?? undefined,
acceptedDpaVersion: user.accepted_dpa_version ?? undefined,
latestEulaVersion: latestEulaInfo.version,
+ latestPrivacyNoticeVersion: latestPrivacyNoticeInfo.version,
latestDpaVersion: latestDpaInfo.version,
};
});
@@ -498,12 +508,14 @@ export class UsersRepositoryService {
* 同意済み利用規約のバージョンを更新する
* @param externalId
* @param eulaVersion
+ * @param privacyNoticeVersion
* @param dpaVersion
* @returns update
*/
async updateAcceptedTermsVersion(
externalId: string,
eulaVersion: string,
+ privacyNoticeVersion: string,
dpaVersion: string | undefined,
): Promise {
await this.dataSource.transaction(async (entityManager) => {
@@ -531,6 +543,11 @@ export class UsersRepositoryService {
if (!eulaVersion) {
throw new UpdateTermsVersionNotSetError(`EULA version param not set.`);
}
+ if (!privacyNoticeVersion) {
+ throw new UpdateTermsVersionNotSetError(
+ `PrivacyNotice version param not set.`,
+ );
+ }
if (user.account.tier !== TIERS.TIER5 && !dpaVersion) {
throw new UpdateTermsVersionNotSetError(
`DPA version param not set. User's tier: ${user.account.tier}`,
@@ -538,6 +555,8 @@ export class UsersRepositoryService {
}
user.accepted_eula_version = eulaVersion;
+ user.accepted_privacy_notice_version =
+ privacyNoticeVersion ?? user.accepted_privacy_notice_version;
user.accepted_dpa_version = dpaVersion ?? user.accepted_dpa_version;
await userRepo.update({ id: user.id }, user);
});
From 4399a61f2b6c17049380f090f642c6c3ce036eef Mon Sep 17 00:00:00 2001
From: "oura.a"
Date: Wed, 6 Dec 2023 00:46:53 +0000
Subject: [PATCH 09/14] =?UTF-8?q?Merged=20PR=20600:=20[=E3=83=A9=E3=82=A4?=
=?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B9=E3=82=A2=E3=83=A9=E3=83=BC=E3=83=88?=
=?UTF-8?q?=E6=94=B9=E5=96=84]=E3=83=AA=E3=83=88=E3=83=A9=E3=82=A4?=
=?UTF-8?q?=E5=AF=BE=E5=BF=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 概要
[Task3025: [ライセンスアラート改善]リトライ対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3025)
ライセンスアラート処理にリトライ処理を追加しました。
メールの多重送信を防ぐために、送信成功したメールについてはredisに保存し、送信時にキャッシュをチェックする処理を入れました。
## レビューポイント
処理の流れが妥当か。
redisに保存するキー、値は適切か。
if文のネストが相当深くなってしまったが、改善できるポイントはあるか。
## UIの変更
なし
## 動作確認状況
ローカルで動作確認済み。(テスト用コードで無理やりエラーを発生させての確認)
## 補足
なし
---
dictation_function/host.json | 7 +-
dictation_function/src/adb2c/adb2c.ts | 35 +-
.../src/common/cache/constants.ts | 6 +-
dictation_function/src/common/cache/index.ts | 25 +-
.../src/functions/licenseAlert.ts | 505 ++++++++++++++----
.../src/functions/redisTimerTest.ts | 29 -
dictation_function/src/redis/redis.ts | 22 +
.../src/test/licenseAlert.spec.ts | 43 +-
8 files changed, 500 insertions(+), 172 deletions(-)
delete mode 100644 dictation_function/src/functions/redisTimerTest.ts
diff --git a/dictation_function/host.json b/dictation_function/host.json
index 9df9136..a75de3b 100644
--- a/dictation_function/host.json
+++ b/dictation_function/host.json
@@ -11,5 +11,10 @@
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
+ },
+ "retry": {
+ "strategy": "fixedDelay",
+ "maxRetryCount": 3,
+ "delayInterval": "00:00:10"
}
-}
\ No newline at end of file
+}
diff --git a/dictation_function/src/adb2c/adb2c.ts b/dictation_function/src/adb2c/adb2c.ts
index f58f9b3..b9bde0c 100644
--- a/dictation_function/src/adb2c/adb2c.ts
+++ b/dictation_function/src/adb2c/adb2c.ts
@@ -5,8 +5,8 @@ import { AdB2cResponse, AdB2cUser } from "./types/types";
import { error } from "console";
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
import { promisify } from "util";
-import { createRedisClient } from "../redis/redis";
import { InvocationContext } from "@azure/functions";
+import { RedisClient } from "redis";
export class Adb2cTooManyRequestsError extends Error {}
@@ -23,16 +23,24 @@ export class AdB2cService {
) {
throw error;
}
- const credential = new ClientSecretCredential(
- process.env.ADB2C_TENANT_ID,
- process.env.ADB2C_CLIENT_ID,
- process.env.ADB2C_CLIENT_SECRET
- );
- const authProvider = new TokenCredentialAuthenticationProvider(credential, {
- scopes: ["https://graph.microsoft.com/.default"],
- });
+ try {
+ const credential = new ClientSecretCredential(
+ process.env.ADB2C_TENANT_ID,
+ process.env.ADB2C_CLIENT_ID,
+ process.env.ADB2C_CLIENT_SECRET
+ );
+ const authProvider = new TokenCredentialAuthenticationProvider(
+ credential,
+ {
+ scopes: ["https://graph.microsoft.com/.default"],
+ }
+ );
- this.graphClient = Client.initWithMiddleware({ authProvider });
+ this.graphClient = Client.initWithMiddleware({ authProvider });
+ } catch (error) {
+ console.log(error);
+ throw error;
+ }
}
/**
@@ -42,9 +50,9 @@ export class AdB2cService {
*/
async getUsers(
context: InvocationContext,
+ redisClient: RedisClient,
externalIds: string[]
- ): Promise {
- const redisClient = createRedisClient();
+ ): Promise {
try {
const b2cUsers: AdB2cUser[] = [];
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
@@ -123,7 +131,7 @@ export class AdB2cService {
return [...cachedUsers, ...b2cUsers];
} else {
- return undefined;
+ return b2cUsers;
}
} catch (e) {
const { statusCode } = e;
@@ -132,7 +140,6 @@ export class AdB2cService {
}
throw e;
} finally {
- redisClient.quit;
}
}
}
diff --git a/dictation_function/src/common/cache/constants.ts b/dictation_function/src/common/cache/constants.ts
index da6c13e..ffa7fe7 100644
--- a/dictation_function/src/common/cache/constants.ts
+++ b/dictation_function/src/common/cache/constants.ts
@@ -1 +1,5 @@
-export const ADB2C_PREFIX = "adb2c-external-id:"
\ No newline at end of file
+export const ADB2C_PREFIX = "adb2c-external-id:";
+export const SEND_COMPLETE_PREFIX = "send-complete-id:";
+export const MAIL_U103 = "[U103]";
+export const MAIL_U104 = "[U104]";
+export const DONE = "Done"; // メール送信成功時にredisにキャッシュする値
diff --git a/dictation_function/src/common/cache/index.ts b/dictation_function/src/common/cache/index.ts
index 067be25..27c0eab 100644
--- a/dictation_function/src/common/cache/index.ts
+++ b/dictation_function/src/common/cache/index.ts
@@ -1,4 +1,4 @@
-import { ADB2C_PREFIX } from './constants';
+import { ADB2C_PREFIX, SEND_COMPLETE_PREFIX } from "./constants";
/**
* ADB2Cのユーザー格納用のキーを生成する
@@ -6,8 +6,8 @@ import { ADB2C_PREFIX } from './constants';
* @returns キャッシュのキー
*/
export const makeADB2CKey = (externalId: string): string => {
- return `${ADB2C_PREFIX}${externalId}`;
-}
+ return `${ADB2C_PREFIX}${externalId}`;
+};
/**
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
@@ -15,5 +15,20 @@ export const makeADB2CKey = (externalId: string): string => {
* @returns 外部ユーザーID
*/
export const restoreAdB2cID = (key: string): string => {
- return key.replace(ADB2C_PREFIX, '');
-}
\ No newline at end of file
+ return key.replace(ADB2C_PREFIX, "");
+};
+
+/**
+ * ライセンスアラートメール送信履歴格納用のキーを生成する
+ * @param formattedDate 当日の日付(YYYY:MM:DD)
+ * @param externalId 外部ユーザーID
+ * @param mail メール種別
+ * @returns キャッシュのキー
+ */
+export const makeSendCompKey = (
+ formattedDate: string,
+ externalId: string,
+ mail: string
+): string => {
+ return `${SEND_COMPLETE_PREFIX}${formattedDate}${mail}${externalId}`;
+};
diff --git a/dictation_function/src/functions/licenseAlert.ts b/dictation_function/src/functions/licenseAlert.ts
index f8f2a71..bbab8b0 100644
--- a/dictation_function/src/functions/licenseAlert.ts
+++ b/dictation_function/src/functions/licenseAlert.ts
@@ -19,36 +19,166 @@ import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
import { getMailFrom } from "../common/getEnv/getEnv";
+import { createRedisClient } from "../redis/redis";
+import { RedisClient } from "redis";
+import { promisify } from "util";
+import { makeSendCompKey } from "../common/cache";
+import {
+ MAIL_U103,
+ MAIL_U104,
+ SEND_COMPLETE_PREFIX,
+ DONE,
+} from "../common/cache/constants";
export async function licenseAlertProcessing(
context: InvocationContext,
datasource: DataSource,
+ redisClient: RedisClient,
sendgrid: SendGridService,
adb2c: AdB2cService
) {
- context.log("[IN]licenseAlertProcessing");
- const mailFrom = getMailFrom();
- const accountRepository = datasource.getRepository(Account);
+ try {
+ context.log("[IN]licenseAlertProcessing");
- // 第五のアカウントを取得
- const accounts = await accountRepository.find({
- where: {
- tier: TIERS.TIER5,
- },
- relations: {
- primaryAdminUser: true,
- secondaryAdminUser: true,
- },
- });
+ // redisのキー用
+ const currentDate = new DateWithZeroTime();
+ const formattedDate = `${currentDate.getFullYear()}-${(
+ currentDate.getMonth() + 1
+ ).toString()}-${currentDate.getDate().toString()}`;
+ const keysAsync = promisify(redisClient.keys).bind(redisClient);
- const licenseRepository = datasource.getRepository(License);
- const currentDate = new DateWithZeroTime();
- const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
- const currentDateWithZeroTime = new DateWithZeroTime();
- const currentDateWithDayEndTime = new DateWithDayEndTime();
- const sendTargetAccounts = [] as accountInfo[];
+ // メール送信対象のアカウント情報を取得
+ const sendTargetAccounts = await getAlertMailTargetAccount(
+ context,
+ datasource
+ );
- const counts = async () => {
+ // adb2cからメールアドレスを取得し、上記で取得したアカウントにマージする
+ const sendTargetAccountsMargedAdb2c = await createAccountInfo(
+ context,
+ redisClient,
+ adb2c,
+ sendTargetAccounts
+ );
+
+ // メール送信
+ await sendAlertMail(
+ context,
+ redisClient,
+ sendgrid,
+ sendTargetAccountsMargedAdb2c,
+ formattedDate
+ );
+
+ // 最後まで処理が正常に通ったら、redisに保存した送信情報を削除する
+ try {
+ const delAsync = promisify(redisClient.del).bind(redisClient);
+
+ const keys = await keysAsync(`${SEND_COMPLETE_PREFIX}${formattedDate}*`);
+ console.log(`delete terget:${keys}`);
+ if (keys.length > 0) {
+ const delResult = await delAsync(...keys);
+ console.log(`delete number:${delResult}`);
+ }
+ } catch (e) {
+ context.log("redis delete failed");
+ throw e;
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ context.log("[OUT]licenseAlertProcessing");
+ }
+}
+
+export async function licenseAlert(
+ myTimer: Timer,
+ context: InvocationContext
+): Promise {
+ context.log("[IN]licenseAlert");
+
+ dotenv.config({ path: ".env" });
+ dotenv.config({ path: ".env.local", override: true });
+ let datasource: DataSource;
+ try {
+ datasource = new DataSource({
+ type: "mysql",
+ host: process.env.DB_HOST,
+ port: Number(process.env.DB_PORT),
+ username: process.env.DB_USERNAME,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ entities: [User, Account, License],
+ });
+ await datasource.initialize();
+ } catch (e) {
+ context.log("database initialize failed.");
+ context.error(e);
+ throw e;
+ }
+
+ let redisClient: RedisClient;
+ try {
+ // redis接続
+ redisClient = createRedisClient();
+ } catch (e) {
+ context.log("redis client create failed.");
+ context.error(e);
+ throw e;
+ }
+
+ try {
+ const adb2c = new AdB2cService();
+ const sendgrid = new SendGridService();
+ await licenseAlertProcessing(
+ context,
+ datasource,
+ redisClient,
+ sendgrid,
+ adb2c
+ );
+ } catch (e) {
+ context.log("licenseAlertProcessing failed.");
+ context.error(e);
+ throw e;
+ } finally {
+ await datasource.destroy();
+ redisClient.quit;
+ context.log("[OUT]licenseAlert");
+ }
+}
+
+/**
+ * アラートメールを送信する対象のアカウントを取得する
+ * @param context
+ * @param datasource
+ * @returns accountInfo[] メール送信対象のアカウント情報
+ */
+async function getAlertMailTargetAccount(
+ context: InvocationContext,
+ datasource: DataSource
+): Promise {
+ try {
+ context.log("[IN]getAlertMailTargetAccount");
+ const currentDate = new DateWithZeroTime();
+ const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
+ const currentDateWithZeroTime = new DateWithZeroTime();
+ const currentDateWithDayEndTime = new DateWithDayEndTime();
+
+ // 第五のアカウントを取得
+ const accountRepository = datasource.getRepository(Account);
+ const accounts = await accountRepository.find({
+ where: {
+ tier: TIERS.TIER5,
+ },
+ relations: {
+ primaryAdminUser: true,
+ secondaryAdminUser: true,
+ },
+ });
+
+ const sendTargetAccounts = [] as accountInfo[];
+ const licenseRepository = datasource.getRepository(License);
for (const account of accounts) {
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
const allocatableLicenseWithMargin = await licenseRepository.count({
@@ -109,6 +239,7 @@ export async function licenseAlertProcessing(
let primaryAdminExternalId: string | undefined;
let secondaryAdminExternalId: string | undefined;
let parentCompanyName: string | undefined;
+
if (shortage !== 0 || userCount !== 0) {
primaryAdminExternalId = account.primaryAdminUser
? account.primaryAdminUser.external_id
@@ -143,12 +274,33 @@ export async function licenseAlertProcessing(
secondaryAdminEmail: undefined,
});
}
- };
- await counts();
+ return sendTargetAccounts;
+ } catch (e) {
+ context.error(e);
+ context.log("getAlertMailTargetAccount failed.");
+ throw e;
+ } finally {
+ context.log("[OUT]getAlertMailTargetAccount");
+ }
+}
+/**
+ * Azure AD B2Cからユーザ情報を取得し、アカウント情報を作成する
+ * @param context
+ * @param redisClient
+ * @param adb2c
+ * @param sendTargetAccounts RDBから取得したアカウント情報
+ * @returns accountInfo[] メール送信対象のアカウント情報
+ */
+async function createAccountInfo(
+ context: InvocationContext,
+ redisClient: RedisClient,
+ adb2c: AdB2cService,
+ sendTargetAccounts: accountInfo[]
+): Promise {
// ADB2Cからユーザーを取得する用の外部ID配列を作成
const externalIds = [] as string[];
- sendTargetAccounts.map((x) => {
+ sendTargetAccounts.forEach((x) => {
if (x.primaryAdminExternalId) {
externalIds.push(x.primaryAdminExternalId);
}
@@ -156,11 +308,10 @@ export async function licenseAlertProcessing(
externalIds.push(x.secondaryAdminExternalId);
}
});
- const adb2cUsers = await adb2c.getUsers(context, externalIds);
- if (!adb2cUsers) {
+ const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
+ if (adb2cUsers.length === 0) {
context.log("Target user not found");
- context.log("[OUT]licenseAlertProcessing");
- return;
+ return [];
}
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
sendTargetAccounts.map((info) => {
@@ -188,17 +339,54 @@ export async function licenseAlertProcessing(
}
}
});
+ return sendTargetAccounts;
+}
+
+/**
+ * アラートメールを送信する
+ * @param context
+ * @param redisClient
+ * @param sendgrid
+ * @param sendTargetAccounts メール送信対象のアカウント情報
+ * @param formattedDate redisのキーに使用する日付
+ * @returns ユーザ情報
+ */
+async function sendAlertMail(
+ context: InvocationContext,
+ redisClient: RedisClient,
+ sendgrid: SendGridService,
+ sendTargetAccounts: accountInfo[],
+ formattedDate: string
+): Promise {
+ try {
+ context.log("[IN]sendAlertMail");
+
+ // redis用
+ const getAsync = promisify(redisClient.get).bind(redisClient);
+ const setexAsync = promisify(redisClient.setex).bind(redisClient);
+ const ttl = process.env.ADB2C_CACHE_TTL;
+ const mailFrom = getMailFrom();
- const sendMail = async () => {
for (const targetAccount of sendTargetAccounts) {
// プライマリ管理者が入っているかチェック
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
if (targetAccount.primaryAdminExternalId) {
// メール送信
// strictNullChecks対応
- if (targetAccount.primaryAdminEmail) {
- // ライセンス不足メール
- if (targetAccount.shortage !== 0) {
+ if (!targetAccount.primaryAdminEmail) {
+ continue;
+ }
+ // ライセンス不足メール
+ if (targetAccount.shortage !== 0) {
+ // redisに送信履歴がない場合のみ送信する
+ const mailResult = await getAsync(
+ makeSendCompKey(
+ formattedDate,
+ targetAccount.primaryAdminExternalId,
+ MAIL_U103
+ )
+ );
+ if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseShortage(
targetAccount.companyName,
@@ -217,45 +405,107 @@ export async function licenseAlertProcessing(
context.log(
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
- } catch {
+ // 送信成功時、成功履歴をredisに保存
+ try {
+ const key = makeSendCompKey(
+ formattedDate,
+ targetAccount.primaryAdminExternalId,
+ MAIL_U103
+ );
+ await setexAsync(key, ttl, DONE);
+ context.log(
+ "setex Result:",
+ `key:${key},ttl:${ttl},value:Done`
+ );
+ } catch (e) {
+ context.error(e);
+ context.log(
+ "setex failed.",
+ `target: ${targetAccount.primaryAdminEmail}`
+ );
+ }
+ } catch (e) {
+ context.error(e);
context.log(
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
- }
-
- // セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
- if (targetAccount.secondaryAdminEmail) {
- // ライセンス不足メール
- if (targetAccount.shortage !== 0) {
- const { subject, text, html } =
- await createMailContentOfLicenseShortage(
- targetAccount.companyName,
- targetAccount.shortage,
- targetAccount.parentCompanyName
- );
- // メールを送信
- try {
- await sendgrid.sendMail(
- targetAccount.secondaryAdminEmail,
- mailFrom,
- subject,
- text,
- html
- );
- context.log(
- `Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
- );
- } catch {
- context.log(
- `Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
- );
- }
- }
+ throw e;
}
}
- // ライセンス失効警告メール
- if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
+ // セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
+ if (
+ targetAccount.secondaryAdminEmail &&
+ targetAccount.secondaryAdminExternalId
+ ) {
+ // redisに送信履歴がない場合のみ送信する
+ const mailResult = await getAsync(
+ makeSendCompKey(
+ formattedDate,
+ targetAccount.secondaryAdminExternalId,
+ MAIL_U103
+ )
+ );
+ if (mailResult !== DONE) {
+ const { subject, text, html } =
+ await createMailContentOfLicenseShortage(
+ targetAccount.companyName,
+ targetAccount.shortage,
+ targetAccount.parentCompanyName
+ );
+ // メールを送信
+ try {
+ await sendgrid.sendMail(
+ targetAccount.secondaryAdminEmail,
+ mailFrom,
+ subject,
+ text,
+ html
+ );
+ context.log(
+ `Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
+ );
+ // 送信成功時、成功履歴をredisに保存
+ try {
+ const key = makeSendCompKey(
+ formattedDate,
+ targetAccount.secondaryAdminExternalId,
+ MAIL_U103
+ );
+ await setexAsync(key, ttl, DONE);
+ context.log(
+ "setex Result:",
+ `key:${key},ttl:${ttl},value:Done`
+ );
+ } catch (e) {
+ context.error(e);
+ context.log(
+ "setex failed.",
+ `target: ${targetAccount.secondaryAdminEmail}`
+ );
+ }
+ } catch (e) {
+ context.error(e);
+ context.log(
+ `Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
+ );
+ throw e;
+ }
+ }
+ }
+ }
+
+ // ライセンス失効警告メール
+ if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
+ // redisに送信履歴がない場合のみ送信する
+ const mailResult = await getAsync(
+ makeSendCompKey(
+ formattedDate,
+ targetAccount.primaryAdminExternalId,
+ MAIL_U104
+ )
+ );
+ if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
@@ -274,80 +524,99 @@ export async function licenseAlertProcessing(
context.log(
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
- } catch {
+ // 送信成功時、成功履歴をredisに保存
+ try {
+ const key = makeSendCompKey(
+ formattedDate,
+ targetAccount.primaryAdminExternalId,
+ MAIL_U104
+ );
+ await setexAsync(key, ttl, DONE);
+ context.log(
+ "setex Result:",
+ `key:${key},ttl:${ttl},value:Done`
+ );
+ } catch (e) {
+ context.error(e);
+ context.log(
+ "setex failed.",
+ `target: ${targetAccount.primaryAdminEmail}`
+ );
+ }
+ } catch (e) {
+ context.error(e);
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
+ throw e;
}
+ }
- // セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
- if (targetAccount.secondaryAdminEmail) {
- // ライセンス不足メール
- if (targetAccount.shortage !== 0) {
- const { subject, text, html } =
- await createMailContentOfLicenseExpiringSoon(
- targetAccount.companyName,
- targetAccount.userCountOfLicenseExpiringSoon,
- targetAccount.parentCompanyName
- );
- // メールを送信
+ // セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
+ if (
+ targetAccount.secondaryAdminEmail &&
+ targetAccount.secondaryAdminExternalId
+ ) {
+ // redisに送信履歴がない場合のみ送信する
+ const mailResult = makeSendCompKey(
+ formattedDate,
+ targetAccount.secondaryAdminExternalId,
+ MAIL_U104
+ );
+ if (mailResult !== DONE) {
+ const { subject, text, html } =
+ await createMailContentOfLicenseExpiringSoon(
+ targetAccount.companyName,
+ targetAccount.userCountOfLicenseExpiringSoon,
+ targetAccount.parentCompanyName
+ );
+ // メールを送信
+ try {
+ await sendgrid.sendMail(
+ targetAccount.secondaryAdminEmail,
+ mailFrom,
+ subject,
+ text,
+ html
+ );
+ context.log(
+ `Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
+ );
try {
- await sendgrid.sendMail(
- targetAccount.secondaryAdminEmail,
- mailFrom,
- subject,
- text,
- html
+ const key = makeSendCompKey(
+ formattedDate,
+ targetAccount.secondaryAdminExternalId,
+ MAIL_U104
);
+ await setexAsync(key, ttl, DONE);
context.log(
- `Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
+ "setex Result:",
+ `key:${key},ttl:${ttl},value:Done`
);
- } catch {
+ } catch (e) {
+ context.error(e);
context.log(
- `Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
+ "setex failed.",
+ `target: ${targetAccount.secondaryAdminEmail}`
);
}
+ } catch (e) {
+ context.error(e);
+ context.log(
+ `Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
+ );
+ throw e;
}
}
}
}
}
}
- };
- await sendMail();
-
- context.log("[OUT]licenseAlertProcessing");
-}
-
-export async function licenseAlert(
- myTimer: Timer,
- context: InvocationContext
-): Promise {
- context.log("[IN]licenseAlert");
-
- dotenv.config({ path: ".env" });
- dotenv.config({ path: ".env.local", override: true });
- const datasource = new DataSource({
- type: "mysql",
- host: process.env.DB_HOST,
- port: Number(process.env.DB_PORT),
- username: process.env.DB_USERNAME,
- password: process.env.DB_PASSWORD,
- database: process.env.DB_NAME,
- entities: [User, Account, License],
- });
- await datasource.initialize();
-
- const adb2c = new AdB2cService();
- const sendgrid = new SendGridService();
- try {
- await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
} catch (e) {
- context.log("licenseAlertProcessing failed");
- context.error(e);
+ context.log("sendAlertMail failed.");
+ throw e;
} finally {
- await datasource.destroy();
- context.log("[OUT]licenseAlert");
+ context.log("[OUT]sendAlertMail");
}
}
diff --git a/dictation_function/src/functions/redisTimerTest.ts b/dictation_function/src/functions/redisTimerTest.ts
deleted file mode 100644
index 419079d..0000000
--- a/dictation_function/src/functions/redisTimerTest.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { app, InvocationContext, Timer } from "@azure/functions";
-import * as dotenv from "dotenv";
-import { promisify } from "util";
-import { createRedisClient } from "../redis/redis";
-
-export async function redisTimerTest(
- myTimer: Timer,
- context: InvocationContext
-): Promise {
- context.log("---Timer function processed request.");
-
- dotenv.config({ path: ".env" });
- dotenv.config({ path: ".env.local", override: true });
-
- const redisClient = createRedisClient();
- const setAsync = promisify(redisClient.set).bind(redisClient);
- const getAsync = promisify(redisClient.get).bind(redisClient);
-
- await setAsync("foo", "bar");
- const value = await getAsync("foo");
- context.log(`value=${value}`); // returns 'bar'
-
- await redisClient.quit;
-}
-
-app.timer("redisTimerTest", {
- schedule: "*/30 * * * * *",
- handler: redisTimerTest,
-});
diff --git a/dictation_function/src/redis/redis.ts b/dictation_function/src/redis/redis.ts
index aedb573..c4ef775 100644
--- a/dictation_function/src/redis/redis.ts
+++ b/dictation_function/src/redis/redis.ts
@@ -20,12 +20,34 @@ export const createRedisClient = (): RedisClient => {
host: host,
port: port,
password: password,
+ retry_strategy: (options) => {
+ if (options.attempt <= 3) {
+ console.log(
+ `Retrying connection to Redis. Attempt ${options.attempt}`
+ );
+ return 10000; // ミリ秒単位でのリトライまでの間隔
+ } else {
+ console.log("Exceeded maximum number of connection attempts.");
+ return undefined; // リトライを終了
+ }
+ },
});
} else {
client = createClient({
url: `rediss://${host}:${port}`,
password: password,
tls: {},
+ retry_strategy: (options) => {
+ if (options.attempt <= 3) {
+ console.log(
+ `Retrying connection to Redis. Attempt ${options.attempt}`
+ );
+ return 10000; // ミリ秒単位でのリトライまでの間隔
+ } else {
+ console.log("Exceeded maximum number of connection attempts.");
+ return undefined; // リトライを終了
+ }
+ },
});
}
diff --git a/dictation_function/src/test/licenseAlert.spec.ts b/dictation_function/src/test/licenseAlert.spec.ts
index a37e9d4..d790cf0 100644
--- a/dictation_function/src/test/licenseAlert.spec.ts
+++ b/dictation_function/src/test/licenseAlert.spec.ts
@@ -13,6 +13,8 @@ import { ADB2C_SIGN_IN_TYPE } from "../constants";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { InvocationContext } from "@azure/functions";
+import { RedisClient } from "redis";
+import { createRedisClient } from "../redis/redis";
describe("licenseAlert", () => {
dotenv.config({ path: ".env" });
@@ -40,6 +42,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
+ const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@@ -63,8 +66,15 @@ describe("licenseAlert", () => {
null
);
- await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
+ await licenseAlertProcessing(
+ context,
+ source,
+ redisClient,
+ sendgridMock,
+ adb2cMock
+ );
expect(spySend.mock.calls).toHaveLength(1);
+ redisClient.quit;
});
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
@@ -72,6 +82,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
+ const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@@ -96,8 +107,15 @@ describe("licenseAlert", () => {
null
);
- await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
+ await licenseAlertProcessing(
+ context,
+ source,
+ redisClient,
+ sendgridMock,
+ adb2cMock
+ );
expect(spySend.mock.calls).toHaveLength(2);
+ redisClient.quit;
});
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
@@ -105,6 +123,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
+ const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@@ -142,8 +161,15 @@ describe("licenseAlert", () => {
null
);
- await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
+ await licenseAlertProcessing(
+ context,
+ source,
+ redisClient,
+ sendgridMock,
+ adb2cMock
+ );
expect(spySend.mock.calls).toHaveLength(0);
+ redisClient.quit;
});
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
@@ -151,6 +177,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
+ const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@@ -175,8 +202,15 @@ describe("licenseAlert", () => {
null
);
- await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
+ await licenseAlertProcessing(
+ context,
+ source,
+ redisClient,
+ sendgridMock,
+ adb2cMock
+ );
expect(spySend.mock.calls).toHaveLength(1);
+ redisClient.quit;
});
});
@@ -211,6 +245,7 @@ export class AdB2cServiceMock {
*/
async getUsers(
context: InvocationContext,
+ redisClient: RedisClient,
externalIds: string[]
): Promise {
const AdB2cMockUsers: AdB2cUser[] = [
From c2465864985d8061de4e332c02e8745ee5221c90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=B0=B4=E6=9C=AC=20=E7=A5=90=E5=B8=8C?=
Date: Wed, 6 Dec 2023 02:10:01 +0000
Subject: [PATCH 10/14] =?UTF-8?q?Merged=20PR=20602:=20=E3=83=A9=E3=82=A4?=
=?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B9=E7=A2=BA=E8=AA=8D=E7=94=BB=E9=9D=A2?=
=?UTF-8?q?=EF=BC=88=E7=AC=AC=E4=BA=94=E9=9A=8E=E5=B1=A4=EF=BC=89=E3=81=AB?=
=?UTF-8?q?=E4=BC=9A=E7=A4=BE=E5=90=8D=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99?=
=?UTF-8?q?=E3=82=8B=EF=BC=88client=E5=81=B4=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## 概要
[Task3225: ライセンス確認画面(第五階層)に会社名を表示する(client側)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3225)
- 何をどう変更したか、追加したライブラリなど
ライセンス確認画面(第5階層)の会社名取得APIを呼び出す実装を追加。
- このPull Requestでの対象/対象外
server側の実装は別ブランチで行うためここでは対象外
## レビューポイント
特になし
## UIの変更
- Before/Afterのスクショなど
- スクショ置き場
## 動作確認状況
- ローカルで確認(完了)
---
dictation_client/src/api/api.ts | 101 ++++++++++++++++++
.../licenseSummary/licenseSummarySlice.ts | 34 +++---
.../license/licenseSummary/operations.ts | 57 ++++++++++
.../license/licenseSummary/selectors.ts | 5 +-
.../features/license/licenseSummary/state.ts | 27 +++--
.../src/pages/LicensePage/licenseSummary.tsx | 6 +-
6 files changed, 204 insertions(+), 26 deletions(-)
diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts
index b77b7b4..1c1ac87 100644
--- a/dictation_client/src/api/api.ts
+++ b/dictation_client/src/api/api.ts
@@ -752,6 +752,32 @@ export interface GetAuthorsResponse {
*/
'authors': Array;
}
+/**
+ *
+ * @export
+ * @interface GetCompanyNameRequest
+ */
+export interface GetCompanyNameRequest {
+ /**
+ *
+ * @type {number}
+ * @memberof GetCompanyNameRequest
+ */
+ 'accountId': number;
+}
+/**
+ *
+ * @export
+ * @interface GetCompanyNameResponse
+ */
+export interface GetCompanyNameResponse {
+ /**
+ *
+ * @type {string}
+ * @memberof GetCompanyNameResponse
+ */
+ 'companyName': string;
+}
/**
*
* @export
@@ -2739,6 +2765,46 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
options: localVarRequestOptions,
};
},
+ /**
+ * 指定したアカウントの会社名を取得します
+ * @summary
+ * @param {GetCompanyNameRequest} getCompanyNameRequest
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ getCompanyName: async (getCompanyNameRequest: GetCompanyNameRequest, options: AxiosRequestConfig = {}): Promise => {
+ // verify required parameter 'getCompanyNameRequest' is not null or undefined
+ assertParamExists('getCompanyName', 'getCompanyNameRequest', getCompanyNameRequest)
+ const localVarPath = `/accounts/company-name`;
+ // use dummy base URL string because the URL constructor only accepts absolute URLs.
+ const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+ let baseOptions;
+ if (configuration) {
+ baseOptions = configuration.baseOptions;
+ }
+
+ const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+ const localVarHeaderParameter = {} as any;
+ const localVarQueryParameter = {} as any;
+
+ // authentication bearer required
+ // http bearer authentication required
+ await setBearerAuthToObject(localVarHeaderParameter, configuration)
+
+
+
+ localVarHeaderParameter['Content-Type'] = 'application/json';
+
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
+ let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+ localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+ localVarRequestOptions.data = serializeDataIfNeeded(getCompanyNameRequest, localVarRequestOptions, configuration)
+
+ return {
+ url: toPathString(localVarUrlObj),
+ options: localVarRequestOptions,
+ };
+ },
/**
*
* @summary
@@ -3500,6 +3566,19 @@ export const AccountsApiFp = function(configuration?: Configuration) {
const operationBasePath = operationServerMap['AccountsApi.getAuthors']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
+ /**
+ * 指定したアカウントの会社名を取得します
+ * @summary
+ * @param {GetCompanyNameRequest} getCompanyNameRequest
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ async getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
+ const localVarAxiosArgs = await localVarAxiosParamCreator.getCompanyName(getCompanyNameRequest, options);
+ const index = configuration?.serverIndex ?? 0;
+ const operationBasePath = operationServerMap['AccountsApi.getCompanyName']?.[index]?.url;
+ return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
+ },
/**
*
* @summary
@@ -3816,6 +3895,16 @@ export const AccountsApiFactory = function (configuration?: Configuration, baseP
getAuthors(options?: any): AxiosPromise {
return localVarFp.getAuthors(options).then((request) => request(axios, basePath));
},
+ /**
+ * 指定したアカウントの会社名を取得します
+ * @summary
+ * @param {GetCompanyNameRequest} getCompanyNameRequest
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ */
+ getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: any): AxiosPromise {
+ return localVarFp.getCompanyName(getCompanyNameRequest, options).then((request) => request(axios, basePath));
+ },
/**
*
* @summary
@@ -4104,6 +4193,18 @@ export class AccountsApi extends BaseAPI {
return AccountsApiFp(this.configuration).getAuthors(options).then((request) => request(this.axios, this.basePath));
}
+ /**
+ * 指定したアカウントの会社名を取得します
+ * @summary
+ * @param {GetCompanyNameRequest} getCompanyNameRequest
+ * @param {*} [options] Override http request option.
+ * @throws {RequiredError}
+ * @memberof AccountsApi
+ */
+ public getCompanyName(getCompanyNameRequest: GetCompanyNameRequest, options?: AxiosRequestConfig) {
+ return AccountsApiFp(this.configuration).getCompanyName(getCompanyNameRequest, options).then((request) => request(this.axios, this.basePath));
+ }
+
/**
*
* @summary
diff --git a/dictation_client/src/features/license/licenseSummary/licenseSummarySlice.ts b/dictation_client/src/features/license/licenseSummary/licenseSummarySlice.ts
index 3888909..e9d9816 100644
--- a/dictation_client/src/features/license/licenseSummary/licenseSummarySlice.ts
+++ b/dictation_client/src/features/license/licenseSummary/licenseSummarySlice.ts
@@ -1,20 +1,25 @@
import { createSlice } from "@reduxjs/toolkit";
import { LicenseSummaryState } from "./state";
-import { getLicenseSummaryAsync } from "./operations";
+import { getCompanyNameAsync, getLicenseSummaryAsync } from "./operations";
const initialState: LicenseSummaryState = {
domain: {
- totalLicense: 0,
- allocatedLicense: 0,
- reusableLicense: 0,
- freeLicense: 0,
- expiringWithin14daysLicense: 0,
- issueRequesting: 0,
- numberOfRequesting: 0,
- shortage: 0,
- storageSize: 0,
- usedSize: 0,
- isStorageAvailable: false,
+ licenseSummaryInfo: {
+ totalLicense: 0,
+ allocatedLicense: 0,
+ reusableLicense: 0,
+ freeLicense: 0,
+ expiringWithin14daysLicense: 0,
+ issueRequesting: 0,
+ numberOfRequesting: 0,
+ shortage: 0,
+ storageSize: 0,
+ usedSize: 0,
+ isStorageAvailable: false,
+ },
+ accountInfo: {
+ companyName: "",
+ },
},
apps: {
isLoading: false,
@@ -31,7 +36,10 @@ export const licenseSummarySlice = createSlice({
},
extraReducers: (builder) => {
builder.addCase(getLicenseSummaryAsync.fulfilled, (state, action) => {
- state.domain = action.payload;
+ state.domain.licenseSummaryInfo = action.payload;
+ });
+ builder.addCase(getCompanyNameAsync.fulfilled, (state, action) => {
+ state.domain.accountInfo.companyName = action.payload.companyName;
});
},
});
diff --git a/dictation_client/src/features/license/licenseSummary/operations.ts b/dictation_client/src/features/license/licenseSummary/operations.ts
index b88df82..a5f18f8 100644
--- a/dictation_client/src/features/license/licenseSummary/operations.ts
+++ b/dictation_client/src/features/license/licenseSummary/operations.ts
@@ -5,6 +5,7 @@ import { openSnackbar } from "features/ui/uiSlice";
import { getAccessToken } from "features/auth";
import {
AccountsApi,
+ GetCompanyNameResponse,
GetLicenseSummaryResponse,
PartnerLicenseInfo,
} from "../../../api/api";
@@ -66,3 +67,59 @@ export const getLicenseSummaryAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
+
+export const getCompanyNameAsync = createAsyncThunk<
+ // 正常時の戻り値の型
+ GetCompanyNameResponse,
+ // 引数
+ { selectedRow?: PartnerLicenseInfo },
+ {
+ // rejectした時の返却値の型
+ rejectValue: {
+ error: ErrorObject;
+ };
+ }
+>("licenses/getCompanyNameAsync", async (args, thunkApi) => {
+ // apiのConfigurationを取得する
+ const { getState } = thunkApi;
+ const state = getState() as RootState;
+ const { configuration } = state.auth;
+ const accessToken = getAccessToken(state.auth);
+ const config = new Configuration(configuration);
+ const accountsApi = new AccountsApi(config);
+ try {
+ const getMyAccountResponse = await accountsApi.getMyAccount({
+ headers: { authorization: `Bearer ${accessToken}` },
+ });
+
+ const { selectedRow } = args;
+ // 引数がない場合は自分のアカウントID取得
+ const accountId =
+ selectedRow?.accountId ?? getMyAccountResponse?.data?.account?.accountId;
+
+ if (accountId !== undefined) {
+ const getCompanyNameResponse = await accountsApi.getCompanyName(
+ { accountId },
+ {
+ headers: { authorization: `Bearer ${accessToken}` },
+ }
+ );
+ return getCompanyNameResponse.data;
+ }
+ throw new Error("accountId is undefined");
+ } catch (e) {
+ // e ⇒ errorObjectに変換"
+ const error = createErrorObject(e);
+
+ const errorMessage = getTranslationID("common.message.internalServerError");
+
+ thunkApi.dispatch(
+ openSnackbar({
+ level: "error",
+ message: errorMessage,
+ })
+ );
+
+ return thunkApi.rejectWithValue({ error });
+ }
+});
diff --git a/dictation_client/src/features/license/licenseSummary/selectors.ts b/dictation_client/src/features/license/licenseSummary/selectors.ts
index 87df71f..79ba5e9 100644
--- a/dictation_client/src/features/license/licenseSummary/selectors.ts
+++ b/dictation_client/src/features/license/licenseSummary/selectors.ts
@@ -2,6 +2,9 @@ import { RootState } from "app/store";
// 各値はそのまま画面に表示するので、licenseSummaryInfoとして値を取得する
export const selecLicenseSummaryInfo = (state: RootState) =>
- state.licenseSummary.domain;
+ state.licenseSummary.domain.licenseSummaryInfo;
+
+export const selectCompanyName = (state: RootState) =>
+ state.licenseSummary.domain.accountInfo.companyName;
export const selectIsLoading = (state: RootState) => state.license;
diff --git a/dictation_client/src/features/license/licenseSummary/state.ts b/dictation_client/src/features/license/licenseSummary/state.ts
index 6402581..2155005 100644
--- a/dictation_client/src/features/license/licenseSummary/state.ts
+++ b/dictation_client/src/features/license/licenseSummary/state.ts
@@ -4,17 +4,22 @@ export interface LicenseSummaryState {
}
export interface Domain {
- totalLicense: number;
- allocatedLicense: number;
- reusableLicense: number;
- freeLicense: number;
- expiringWithin14daysLicense: number;
- issueRequesting: number;
- numberOfRequesting: number;
- shortage: number;
- storageSize: number;
- usedSize: number;
- isStorageAvailable: boolean;
+ licenseSummaryInfo: {
+ totalLicense: number;
+ allocatedLicense: number;
+ reusableLicense: number;
+ freeLicense: number;
+ expiringWithin14daysLicense: number;
+ issueRequesting: number;
+ numberOfRequesting: number;
+ shortage: number;
+ storageSize: number;
+ usedSize: number;
+ isStorageAvailable: boolean;
+ };
+ accountInfo: {
+ companyName: string;
+ };
}
export interface Apps {
diff --git a/dictation_client/src/pages/LicensePage/licenseSummary.tsx b/dictation_client/src/pages/LicensePage/licenseSummary.tsx
index 44bb696..b305b66 100644
--- a/dictation_client/src/pages/LicensePage/licenseSummary.tsx
+++ b/dictation_client/src/pages/LicensePage/licenseSummary.tsx
@@ -8,8 +8,10 @@ import { useTranslation } from "react-i18next";
import { AppDispatch } from "app/store";
import { useDispatch, useSelector } from "react-redux";
import {
+ getCompanyNameAsync,
getLicenseSummaryAsync,
selecLicenseSummaryInfo,
+ selectCompanyName,
} from "features/license/licenseSummary";
import { selectSelectedRow } from "features/license/partnerLicense";
import { selectDelegationAccessToken } from "features/auth/selectors";
@@ -61,9 +63,11 @@ export const LicenseSummary: React.FC = (
// apiからの値取得関係
const licenseSummaryInfo = useSelector(selecLicenseSummaryInfo);
+ const companyName = useSelector(selectCompanyName);
useEffect(() => {
dispatch(getLicenseSummaryAsync({ selectedRow }));
+ dispatch(getCompanyNameAsync({ selectedRow }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch]);
@@ -118,7 +122,7 @@ export const LicenseSummary: React.FC = (
-
{"会社名" /* TODO 会社名を表示する */}
+
{companyName}
-
{/* 他アカウントのライセンス情報を見ている場合は、前画面に戻る用のreturnボタンを表示 */}
From d92cb1d28b2f8c4734937b216cdb62285af1224c Mon Sep 17 00:00:00 2001
From: "saito.k"
Date: Wed, 6 Dec 2023 17:15:32 +0900
Subject: [PATCH 11/14] =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=83=AA=E3=83=AD?=
=?UTF-8?q?=E3=82=B0=E3=81=AB=E8=BF=BD=E8=B7=A1=E7=94=A8=E3=81=AEID?=
=?UTF-8?q?=E3=82=92=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=81=A8=E3=81=97?=
=?UTF-8?q?=E3=81=A6=E8=BF=BD=E5=8A=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dictation_server/src/features/users/users.service.ts | 1 +
.../src/repositories/users/users.repository.service.ts | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts
index 60e3e92..e26a1c1 100644
--- a/dictation_server/src/features/users/users.service.ts
+++ b/dictation_server/src/features/users/users.service.ts
@@ -540,6 +540,7 @@ export class UsersService {
// DBから同一アカウントのユーザ一覧を取得する
const dbUsers = await this.usersRepository.findSameAccountUsers(
externalId,
+ context,
);
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts
index 7cfec4d..8894656 100644
--- a/dictation_server/src/repositories/users/users.repository.service.ts
+++ b/dictation_server/src/repositories/users/users.repository.service.ts
@@ -35,6 +35,7 @@ import {
import { Account } from '../accounts/entity/account.entity';
import { Workflow } from '../workflows/entity/workflow.entity';
import { Worktype } from '../worktypes/entity/worktype.entity';
+import { Context } from '../../common/log';
@Injectable()
export class UsersRepositoryService {
@@ -340,7 +341,10 @@ export class UsersRepositoryService {
* @param externalId
* @returns User[]
*/
- async findSameAccountUsers(external_id: string): Promise {
+ async findSameAccountUsers(
+ external_id: string,
+ context: Context,
+ ): Promise {
return await this.dataSource.transaction(async (entityManager) => {
const repo = entityManager.getRepository(User);
@@ -359,6 +363,7 @@ export class UsersRepositoryService {
license: true,
},
where: { account_id: accountId },
+ comment: `${context.getTrackingId()}`,
});
return dbUsers;
From 2d0697f56bf9292ab8caa4a1651a00482652b6af Mon Sep 17 00:00:00 2001
From: "saito.k"
Date: Wed, 6 Dec 2023 19:06:26 +0900
Subject: [PATCH 12/14] =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9?=
=?UTF-8?q?=E3=83=88=E3=81=AEIP=E3=82=92=E3=83=AD=E3=82=B0=E3=81=AB?=
=?UTF-8?q?=E5=87=BA=E5=8A=9B=E3=81=97=E3=81=A6=E7=A2=BA=E8=AA=8D=E3=81=99?=
=?UTF-8?q?=E3=82=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dictation_server/src/features/users/users.controller.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts
index 9f88897..034edf9 100644
--- a/dictation_server/src/features/users/users.controller.ts
+++ b/dictation_server/src/features/users/users.controller.ts
@@ -4,6 +4,7 @@ import {
Get,
HttpException,
HttpStatus,
+ Ip,
Post,
Query,
Req,
@@ -136,6 +137,9 @@ export class UsersController {
@Get()
async getUsers(@Req() req: Request): Promise {
const accessToken = retrieveAuthorizationToken(req);
+ console.log(req.ip);
+ console.log(req.headers['X-Forwarded-For']);
+
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
From 9f8e20230bb2c660024d0f3fc2d4cde3faa7a01f Mon Sep 17 00:00:00 2001
From: "saito.k"
Date: Wed, 6 Dec 2023 19:43:25 +0900
Subject: [PATCH 13/14] =?UTF-8?q?=E5=B0=8F=E6=96=87=E5=AD=97=E3=81=AB?=
=?UTF-8?q?=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dictation_server/src/features/users/users.controller.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts
index 034edf9..473c578 100644
--- a/dictation_server/src/features/users/users.controller.ts
+++ b/dictation_server/src/features/users/users.controller.ts
@@ -138,7 +138,7 @@ export class UsersController {
async getUsers(@Req() req: Request): Promise {
const accessToken = retrieveAuthorizationToken(req);
console.log(req.ip);
- console.log(req.headers['X-Forwarded-For']);
+ console.log(req.headers['x-forwarded-for']);
if (!accessToken) {
throw new HttpException(
From 3ad0d87814857fd6b2926e30f96e7e92b0f63e73 Mon Sep 17 00:00:00 2001
From: "saito.k"
Date: Wed, 6 Dec 2023 20:10:00 +0900
Subject: [PATCH 14/14] =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E7=94=A8=E3=81=AE?=
=?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=92=E5=89=8A=E9=99=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dictation_server/src/features/users/users.controller.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts
index 473c578..c8cdafc 100644
--- a/dictation_server/src/features/users/users.controller.ts
+++ b/dictation_server/src/features/users/users.controller.ts
@@ -137,8 +137,6 @@ export class UsersController {
@Get()
async getUsers(@Req() req: Request): Promise {
const accessToken = retrieveAuthorizationToken(req);
- console.log(req.ip);
- console.log(req.headers['x-forwarded-for']);
if (!accessToken) {
throw new HttpException(