From ebd1e7df0b5be36b732bebe031a95538afe0bcfd Mon Sep 17 00:00:00 2001 From: Pooya Raki Date: Tue, 24 Dec 2024 13:57:05 +0100 Subject: [PATCH] Delete all notification subscriptions --- .../v2/notifications.repository.interface.ts | 11 +++--- .../v2/notifications.repository.ts | 34 ++++++++++++++++--- ...ifications-v2compatible.controller.spec.ts | 10 +++++- .../v1/notifications.controller.ts | 4 +++ .../notifications/v2/notifications.service.ts | 16 ++++++--- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/domain/notifications/v2/notifications.repository.interface.ts b/src/domain/notifications/v2/notifications.repository.interface.ts index f652c07cc6..0fa242a364 100644 --- a/src/domain/notifications/v2/notifications.repository.interface.ts +++ b/src/domain/notifications/v2/notifications.repository.interface.ts @@ -13,10 +13,13 @@ export interface INotificationsRepositoryV2 { notification: FirebaseNotification; }): Promise; - upsertSubscriptions(args: { - authPayload: AuthPayload; - upsertSubscriptionsDto: UpsertSubscriptionsDto; - }): Promise<{ + upsertSubscriptions( + args: { + authPayload: AuthPayload; + upsertSubscriptionsDto: UpsertSubscriptionsDto; + }, + deleteAllDeviceOwners?: boolean, + ): Promise<{ deviceUuid: UUID; }>; diff --git a/src/domain/notifications/v2/notifications.repository.ts b/src/domain/notifications/v2/notifications.repository.ts index e86e6913fb..b6a4240968 100644 --- a/src/domain/notifications/v2/notifications.repository.ts +++ b/src/domain/notifications/v2/notifications.repository.ts @@ -92,15 +92,24 @@ export class NotificationsRepositoryV2 implements INotificationsRepositoryV2 { return isNotFound && isUnregistered; } - public async upsertSubscriptions(args: { - authPayload: AuthPayload; - upsertSubscriptionsDto: UpsertSubscriptionsDto; - }): Promise<{ + public async upsertSubscriptions( + args: { + authPayload: AuthPayload; + upsertSubscriptionsDto: UpsertSubscriptionsDto; + }, + deleteAllDeviceOwners?: boolean, + ): Promise<{ deviceUuid: UUID; }> { const deviceUuid = await this.postgresDatabaseService.transaction( async (entityManager: EntityManager): Promise => { const device = await this.upsertDevice(entityManager, args); + if (deleteAllDeviceOwners) { + // Some clients, such as the mobile app, do not call the delete endpoint to remove an owner key. + // Instead, they resend the updated list of owners without the key they want to delete. + // In such cases, we need to clear all the previous owners to ensure the update is applied correctly. + await this.deleteDeviceOwners(entityManager, device.id); + } await this.deletePreviousSubscriptions(entityManager, { deviceId: device.id, signerAddress: args.authPayload.signer_address, @@ -147,6 +156,23 @@ export class NotificationsRepositoryV2 implements INotificationsRepositoryV2 { return { id: queryResult.identifiers[0].id, device_uuid: deviceUuid }; } + private async deleteDeviceOwners( + entityManager: EntityManager, + deviceId: number, + ): Promise { + const deviceSubscriptions = await entityManager.find( + NotificationSubscription, + { + where: { + push_notification_device: { + id: deviceId, + }, + }, + }, + ); + await entityManager.remove(deviceSubscriptions); + } + private async deletePreviousSubscriptions( entityManager: EntityManager, args: { diff --git a/src/routes/notifications/v1/notifications-v2compatible.controller.spec.ts b/src/routes/notifications/v1/notifications-v2compatible.controller.spec.ts index c502e76b52..de4f2a76bd 100644 --- a/src/routes/notifications/v1/notifications-v2compatible.controller.spec.ts +++ b/src/routes/notifications/v1/notifications-v2compatible.controller.spec.ts @@ -42,6 +42,7 @@ import { LoggingService, type ILoggingService, } from '@/logging/logging.interface'; +import { DeviceType } from '@/domain/notifications/v1/entities/device.entity'; describe('Notifications Controller (Unit)', () => { let app: INestApplication; @@ -167,6 +168,9 @@ describe('Notifications Controller (Unit)', () => { .expect(200) .expect({}); + const deleteAllDeviceOwners = + registerDeviceDto.deviceType !== DeviceType.Web; + // @TODO Remove NotificationModuleV2 after all clients have migrated and compatibility is no longer needed. // We call V2 as many times as we have a registration with at least one safe const safeRegistrationsWithSafe = @@ -185,7 +189,11 @@ describe('Notifications Controller (Unit)', () => { const nthCall = index + 1; // Convert zero-based index to a one-based call number expect( notificationServiceV2.upsertSubscriptions, - ).toHaveBeenNthCalledWith(nthCall, upsertSubscriptionsV2); + ).toHaveBeenNthCalledWith( + nthCall, + upsertSubscriptionsV2, + deleteAllDeviceOwners, + ); } }, ); diff --git a/src/routes/notifications/v1/notifications.controller.ts b/src/routes/notifications/v1/notifications.controller.ts index 15538c7a4c..92595524ca 100644 --- a/src/routes/notifications/v1/notifications.controller.ts +++ b/src/routes/notifications/v1/notifications.controller.ts @@ -77,10 +77,14 @@ export class NotificationsController { const v2Requests = []; + const deleteAllDeviceOwners = + registerDeviceDto.deviceType !== DeviceType.Web; + for (const compatibleV2Request of compatibleV2Requests) { v2Requests.push( await this.notificationServiceV2.upsertSubscriptions( compatibleV2Request, + deleteAllDeviceOwners, ), ); } diff --git a/src/routes/notifications/v2/notifications.service.ts b/src/routes/notifications/v2/notifications.service.ts index 3449b48f78..06efd261b5 100644 --- a/src/routes/notifications/v2/notifications.service.ts +++ b/src/routes/notifications/v2/notifications.service.ts @@ -12,13 +12,19 @@ export class NotificationsServiceV2 { private readonly notificationsRepository: INotificationsRepositoryV2, ) {} - async upsertSubscriptions(args: { - authPayload: AuthPayload; - upsertSubscriptionsDto: UpsertSubscriptionsDto; - }): Promise<{ + async upsertSubscriptions( + args: { + authPayload: AuthPayload; + upsertSubscriptionsDto: UpsertSubscriptionsDto; + }, + deleteAllDeviceOwners?: boolean, + ): Promise<{ deviceUuid: UUID; }> { - return this.notificationsRepository.upsertSubscriptions(args); + return this.notificationsRepository.upsertSubscriptions( + args, + deleteAllDeviceOwners, + ); } async getSafeSubscription(args: {