From 54db2e8ab58ddda2c25afc74a0be8faa39ca11f1 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Wed, 26 Jul 2023 00:40:06 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20259:=20=E3=83=AB=E3=83=BC?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=83=B3=E3=82=B0=E9=80=9A=E7=9F=A5=E7=99=BB?= =?UTF-8?q?=E9=8C=B2API=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2218: ルーティング通知登録API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2218) - NotificationHubへの通知登録を実装しました。 ## レビューポイント - UUIDを使ったインストールIDの組み立てに問題はないか - 対象外 - account関連はフォーマット修正によるものなので対象外です。 ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/package-lock.json | 87 ++++++++--------- dictation_server/package.json | 2 +- dictation_server/src/constants/index.ts | 9 ++ .../notification/notification.controller.ts | 25 ++++- .../notification/notification.module.ts | 3 +- .../notification/notification.service.spec.ts | 68 +++++++++---- .../notification/notification.service.ts | 43 ++++++++- .../test/notification.service.mock.ts | 95 +++++++++++++++++++ .../src/features/notification/types/types.ts | 5 + .../notificationhub.service.ts | 68 +++++++------ 10 files changed, 305 insertions(+), 100 deletions(-) create mode 100644 dictation_server/src/features/notification/test/notification.service.mock.ts diff --git a/dictation_server/package-lock.json b/dictation_server/package-lock.json index f98b5f0..8c3825c 100644 --- a/dictation_server/package-lock.json +++ b/dictation_server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@azure/identity": "^3.1.3", "@azure/keyvault-secrets": "^4.6.0", - "@azure/notification-hubs": "^1.0.1", + "@azure/notification-hubs": "^1.0.2", "@azure/storage-blob": "^12.14.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs/axios": "^0.1.0", @@ -494,9 +494,9 @@ } }, "node_modules/@azure/core-util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.2.0.tgz", - "integrity": "sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", + "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", "dependencies": { "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" @@ -624,9 +624,9 @@ } }, "node_modules/@azure/notification-hubs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.1.tgz", - "integrity": "sha512-bYgWZNr9LjeSBTstuDRW2FFlZWHjnW09c4YLsB81dsaxMFWGN5mAnE5/+r/omEVqztoP5ClQ3j69ZUoZiJLUsQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.2.tgz", + "integrity": "sha512-INtgq8uFQpncwbKm4It8M0GkKIePNDNybhuXs4cQPf5H0i9CbfFEt2c6LtT1AdEzbWfUhjsmU5y0p3YDmecwwg==", "dependencies": { "@azure/abort-controller": "^1.1.0", "@azure/core-client": "^1.6.1", @@ -634,24 +634,15 @@ "@azure/core-paging": "^1.3.0", "@azure/core-rest-pipeline": "^1.8.1", "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.1.0", + "@azure/core-util": "^1.3.0", "@azure/core-xml": "^1.3.1", "@azure/logger": "^1.0.3", - "tslib": "^2.4.0", - "uuid": "^9.0.0" + "tslib": "^2.4.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@azure/notification-hubs/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@azure/storage-blob": { "version": "12.14.0", "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz", @@ -734,9 +725,9 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -791,9 +782,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -5753,9 +5744,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fast-xml-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", - "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz", + "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==", "funding": [ { "type": "paypal", @@ -6773,9 +6764,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -8084,9 +8075,9 @@ } }, "node_modules/license-checker/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -8218,9 +8209,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "devOptional": true, "bin": { "semver": "bin/semver.js" @@ -8986,9 +8977,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -9722,9 +9713,9 @@ } }, "node_modules/read-installed/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -10066,9 +10057,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -11882,9 +11873,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" diff --git a/dictation_server/package.json b/dictation_server/package.json index b2a5666..6da89b6 100644 --- a/dictation_server/package.json +++ b/dictation_server/package.json @@ -30,7 +30,7 @@ "dependencies": { "@azure/identity": "^3.1.3", "@azure/keyvault-secrets": "^4.6.0", - "@azure/notification-hubs": "^1.0.1", + "@azure/notification-hubs": "^1.0.2", "@azure/storage-blob": "^12.14.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs/axios": "^0.1.0", diff --git a/dictation_server/src/constants/index.ts b/dictation_server/src/constants/index.ts index 4fbd81b..276a4d6 100644 --- a/dictation_server/src/constants/index.ts +++ b/dictation_server/src/constants/index.ts @@ -173,3 +173,12 @@ export const TASK_LIST_SORTABLE_ATTRIBUTES = [ * タスク一覧のソート条件(昇順・降順) */ export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const; + +/** + * 通知のプラットフォーム種別文字列 + */ +export const PNS = { + WNS: 'wns', + APNS: 'apns', + FCM: 'fcm', +}; diff --git a/dictation_server/src/features/notification/notification.controller.ts b/dictation_server/src/features/notification/notification.controller.ts index fc33b6e..136c75d 100644 --- a/dictation_server/src/features/notification/notification.controller.ts +++ b/dictation_server/src/features/notification/notification.controller.ts @@ -1,14 +1,25 @@ -import { Body, Controller, HttpStatus, Post, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + HttpStatus, + Post, + Req, + UseGuards, +} from '@nestjs/common'; import { ApiResponse, ApiOperation, ApiBearerAuth, ApiTags, } from '@nestjs/swagger'; +import { Request } from 'express'; import { ErrorResponse } from '../../common/error/types/types'; import { RegisterRequest, RegisterResponse } from './types/types'; import { NotificationService } from './notification.service'; import { AuthGuard } from '../../common/guards/auth/authguards'; +import { retrieveAuthorizationToken } from '../../common/http/helper'; +import { AccessToken } from '../../common/token'; +import jwt from 'jsonwebtoken'; @ApiTags('notification') @Controller('notification') @@ -37,10 +48,18 @@ export class NotificationController { type: ErrorResponse, }) @ApiOperation({ operationId: 'register' }) + @ApiBearerAuth() @UseGuards(AuthGuard) - async register(@Body() body: RegisterRequest): Promise { + async register( + @Req() req: Request, + @Body() body: RegisterRequest, + ): Promise { const { handler, pns } = body; - await this.notificationService.register(pns, handler); + + const accessToken = retrieveAuthorizationToken(req); + const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken; + + await this.notificationService.register(userId, pns, handler); return {}; } } diff --git a/dictation_server/src/features/notification/notification.module.ts b/dictation_server/src/features/notification/notification.module.ts index a59a046..b40a54d 100644 --- a/dictation_server/src/features/notification/notification.module.ts +++ b/dictation_server/src/features/notification/notification.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { NotificationService } from './notification.service'; import { NotificationController } from './notification.controller'; import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module'; +import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; @Module({ - imports: [NotificationhubModule], + imports: [UsersRepositoryModule, NotificationhubModule], providers: [NotificationService], controllers: [NotificationController], }) diff --git a/dictation_server/src/features/notification/notification.service.spec.ts b/dictation_server/src/features/notification/notification.service.spec.ts index 460177f..d389da0 100644 --- a/dictation_server/src/features/notification/notification.service.spec.ts +++ b/dictation_server/src/features/notification/notification.service.spec.ts @@ -1,21 +1,57 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { NotificationService } from './notification.service'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { + makeNotificationServiceMock, + makeDefaultNotificationHubMockValue, + makeDefaultUsersRepositoryMockValue, +} from './test/notification.service.mock'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; -describe('NotificationService', () => { - let service: NotificationService; - const mockNotificationService = {}; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [NotificationService], - }) - .overrideProvider(NotificationService) - .useValue(mockNotificationService) - .compile(); +describe('NotificationService.register', () => { + it('ユーザーから渡されたPNSハンドルをNotificationHubに登録できる', async () => { + const notificationHubMockValue = makeDefaultNotificationHubMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - service = module.get(NotificationService); + const service = await makeNotificationServiceMock( + usersRepositoryMockValue, + notificationHubMockValue, + ); + + expect(await service.register('external_id', 'apns', 'handler')).toEqual( + undefined, + ); + }); + it('DBからのユーザー取得に失敗した場合、エラーとなる', async () => { + const notificationHubMockValue = makeDefaultNotificationHubMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + usersRepositoryMockValue.findUserByExternalId= new Error("DB failed.") + + const service = await makeNotificationServiceMock( + usersRepositoryMockValue, + notificationHubMockValue, + ); + + await expect(service.register('external_id', 'apns', 'handler')).rejects.toEqual( + new HttpException(makeErrorResponse("E009999"), HttpStatus.INTERNAL_SERVER_ERROR) + ); }); - it('should be defined', () => { - expect(service).toBeDefined(); - }); + it('NotificationHubへの登録に失敗した場合、エラーとなる', async () => { + const notificationHubMockValue = makeDefaultNotificationHubMockValue(); + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + notificationHubMockValue.register = new Error("register failed."); + + const service = await makeNotificationServiceMock( + usersRepositoryMockValue, + notificationHubMockValue, + ); + + await expect( + service.register('external_id', 'apns', 'handler'), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); }); diff --git a/dictation_server/src/features/notification/notification.service.ts b/dictation_server/src/features/notification/notification.service.ts index 3c4f1bf..f0ccf09 100644 --- a/dictation_server/src/features/notification/notification.service.ts +++ b/dictation_server/src/features/notification/notification.service.ts @@ -1,11 +1,15 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; +import { UserNotFoundError } from '../../repositories/users/errors/types'; +import { v4 as uuidv4 } from 'uuid'; @Injectable() export class NotificationService { private readonly logger = new Logger(NotificationService.name); constructor( + private readonly usersRepository: UsersRepositoryService, private readonly notificationhubService: NotificationhubService, ) {} /** @@ -14,10 +18,45 @@ export class NotificationService { * @param pnsHandler * @returns register */ - async register(pns: string, pnsHandler: string): Promise { + async register( + externalId: string, + pns: string, + pnsHandler: string, + ): Promise { this.logger.log(`[IN] ${this.register.name}`); + + // ユーザIDからアカウントIDを取得する + let userId: number; try { - await this.notificationhubService.register(pns, pnsHandler); + userId = (await this.usersRepository.findUserByExternalId(externalId)).id; + } catch (e) { + this.logger.error(`error=${e}`); + switch (e.constructor) { + case UserNotFoundError: + throw new HttpException( + makeErrorResponse('E010204'), + HttpStatus.BAD_REQUEST, + ); + default: + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + try { + // TODO: 登録毎に新規登録する想定でUUIDを付与している + // もしデバイスごとに登録を上書きするようであればUUID部分にデバイス識別子を設定 + const installationId = `${pns}_${userId}_${uuidv4()}`; + this.logger.log(installationId); + + await this.notificationhubService.register( + userId, + pns, + pnsHandler, + installationId, + ); } catch (e) { this.logger.error(`error=${e}`); throw new HttpException( diff --git a/dictation_server/src/features/notification/test/notification.service.mock.ts b/dictation_server/src/features/notification/test/notification.service.mock.ts new file mode 100644 index 0000000..64ce597 --- /dev/null +++ b/dictation_server/src/features/notification/test/notification.service.mock.ts @@ -0,0 +1,95 @@ +import { ConfigModule } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; +import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service'; +import { User } from '../../../repositories/users/entity/user.entity'; +import { NotificationService } from '../notification.service'; +import { UsersRepositoryService } from '../../../repositories/users/users.repository.service'; + +export type UsersRepositoryMockValue = { + findUserByExternalId: User | Error; +}; + +export type NotificationHubMockValue = { + register: undefined | Error; +}; + +export const makeNotificationServiceMock = async ( + usersRepositoryMockValue: UsersRepositoryMockValue, + notificationHubMockValue: NotificationHubMockValue, +): Promise => { + const module: TestingModule = await Test.createTestingModule({ + providers: [NotificationService], + imports: [ + ConfigModule.forRoot({ + envFilePath: ['.env.local', '.env'], + }), + ], + }) + .useMocker((token) => { + switch (token) { + case UsersRepositoryService: + return makeUsersRepositoryMock(usersRepositoryMockValue); + case NotificationhubService: + return makeNotificationHubMock(notificationHubMockValue); + } + }) + .compile(); + + return module.get(NotificationService); +}; + +export const makeNotificationHubMock = (value: NotificationHubMockValue) => { + const { register } = value; + + return { + register: + register instanceof Error + ? jest + .fn, [number, string, string, string]>() + .mockRejectedValue(register) + : jest + .fn, [number, string, string, string]>() + .mockResolvedValue(register), + }; +}; + +export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => { + const { findUserByExternalId } = value; + + return { + findUserByExternalId: + findUserByExternalId instanceof Error + ? jest.fn, []>().mockRejectedValue(findUserByExternalId) + : jest.fn, []>().mockResolvedValue(findUserByExternalId), + }; +}; + +export const makeDefaultNotificationHubMockValue = + (): NotificationHubMockValue => { + return { register: undefined }; + }; + +// 個別のテストケースに対応してそれぞれのMockを用意するのは無駄が多いのでテストケース内で個別の値を設定する +export const makeDefaultUsersRepositoryMockValue = + (): UsersRepositoryMockValue => { + const user = new User(); + user.id = 2; + user.external_id = 'external_id'; + user.account_id = 123; + user.role = 'none'; + user.author_id = undefined; + user.accepted_terms_version = '1.0'; + user.email_verified = true; + user.auto_renew = false; + user.license_alert = false; + user.notification = false; + user.deleted_at = undefined; + user.created_by = 'test'; + user.created_at = new Date(); + user.updated_by = 'test'; + user.updated_at = new Date(); + + return { + findUserByExternalId: user, + }; + }; diff --git a/dictation_server/src/features/notification/types/types.ts b/dictation_server/src/features/notification/types/types.ts index 631d697..b19bf08 100644 --- a/dictation_server/src/features/notification/types/types.ts +++ b/dictation_server/src/features/notification/types/types.ts @@ -1,7 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsIn } from 'class-validator'; +import { PNS } from '../../../constants'; export class RegisterRequest { @ApiProperty({ description: 'wns or apns' }) + @IsIn(Object.values(PNS), { + message: 'invalid pns', + }) pns: string; @ApiProperty({ description: 'wnsのチャネルURI or apnsのデバイストークン' }) handler: string; diff --git a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts index 8136f88..684c92d 100644 --- a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts +++ b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts @@ -2,10 +2,11 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NotificationHubsClient, - createWindowsRegistrationDescription, - RegistrationDescription, - createAppleRegistrationDescription, + createAppleInstallation, + createFcmLegacyInstallation, + createWindowsInstallation, } from '@azure/notification-hubs'; +import { PNS } from '../../constants'; @Injectable() export class NotificationhubService { private readonly logger = new Logger(NotificationhubService.name); @@ -23,25 +24,44 @@ export class NotificationhubService { * @param pnsHandler * @returns register */ - async register(pns: string, pnsHandler: string): Promise { + async register( + userId: number, + pns: string, + pnsHandler: string, + installationId: string, + ): Promise { this.logger.log(`[IN] ${this.register.name}`); - let reg: RegistrationDescription; + + const tag = `user_${userId}`; + //登録情報作成 + const installation = { + // XXX 登録の有効期限を設定したい場合 + //expirationTime: '', + installationId: installationId, + pushChannel: pnsHandler, + tags: [tag], + }; + try { - if (pns === 'wns') { - reg = createWindowsRegistrationDescription({ - channelUri: pnsHandler, - tags: [], - // XXX [Task2218] 登録の有効期限を設定したい場合等、本実装を別途Taskで行う - //expirationTime: '', - }); - } else if (pns === 'apns') { - reg = createAppleRegistrationDescription({ - deviceToken: pnsHandler, - tags: [], - }); - } else { - throw new Error('invalid pns'); + switch (pns) { + case PNS.WNS: + await this.client.createOrUpdateInstallation( + createWindowsInstallation(installation), + ); + break; + case PNS.APNS: + await this.client.createOrUpdateInstallation( + createAppleInstallation(installation), + ); + break; + case PNS.FCM: + await this.client.createOrUpdateInstallation( + createFcmLegacyInstallation(installation), + ); + break; + default: + throw new Error('invalid pns'); } } catch (e) { this.logger.error(`error=${e.message}`); @@ -49,15 +69,5 @@ export class NotificationhubService { } finally { this.logger.log(`[OUT] ${this.register.name}`); } - - try { - //登録 - await this.client.createOrUpdateRegistration(reg); - } catch (e) { - this.logger.error(`error=${e}`); - throw e; - } finally { - this.logger.log(`[OUT] ${this.register.name}`); - } } }