From 8988bfcd2de6d8725b6475c641bf3c9329f588dc Mon Sep 17 00:00:00 2001 From: IgorZynov Date: Mon, 17 Mar 2025 17:29:09 +0400 Subject: [PATCH] feat: use crud for fcm-tokens --- configs/cron.json | 27 +++++++ .../list/methods/notification.json | 46 ++++++++--- .../permissions/list/models/notification.json | 80 ++++++++++++------- ...23121658-use-userid-and-useragent-as-pk.ts | 31 +++++++ .../notification/src/entities/fcm-token.ts | 16 ++-- .../src/methods/fcm-token/crud.ts | 13 +++ .../methods/fcm-token/remove-user-tokens.ts | 38 --------- .../src/methods/fcm-token/remove.ts | 38 --------- .../notification/src/methods/index.ts | 14 ++-- .../src/services/firebase/index.ts | 57 +------------ 10 files changed, 174 insertions(+), 186 deletions(-) create mode 100644 microservices/notification/migrations/1742223121658-use-userid-and-useragent-as-pk.ts create mode 100644 microservices/notification/src/methods/fcm-token/crud.ts delete mode 100644 microservices/notification/src/methods/fcm-token/remove-user-tokens.ts delete mode 100644 microservices/notification/src/methods/fcm-token/remove.ts diff --git a/configs/cron.json b/configs/cron.json index 0465e8f1..84dd44b7 100644 --- a/configs/cron.json +++ b/configs/cron.json @@ -53,6 +53,33 @@ "responseTemplate": "<%= `deleted: ${deleted.length}` %>" } }, + { + "rule": "0 1 * * *", + "method": "notification.fcm-token.remove", + "description": "Cleanup 2 months old fcm tokens", + "payload": { + "params": { + "query": { + "where": { + "updatedAt": { + "<": "<%= new Date().setMonth(new Date().getMonth() - 2) %>" + } + } + }, + "payload": { + "authorization": { + "filter": { + "methodOptions": { + "isAllowMultiple": true + } + } + } + } + }, + "allowErrorCodes": [-33485], + "responseTemplate": "<%= `deleted: ${deleted.length}` %>" + } + }, { "rule": "* * * * *", "method": "notification.job.task.process", diff --git a/microservices/authorization/migrations/permissions/list/methods/notification.json b/microservices/authorization/migrations/permissions/list/methods/notification.json index 05f53c35..24d8eea4 100644 --- a/microservices/authorization/migrations/permissions/list/methods/notification.json +++ b/microservices/authorization/migrations/permissions/list/methods/notification.json @@ -325,14 +325,14 @@ { "microservice": "notification", "method": "fcm-token.remove", - "description": "Remove FCM token", + "description": "Remove FcmToken by given condition", "allowGroup": [ "admin" ], "denyGroup": [], - "createdAt": "2025-03-10T18:03:29.296Z", - "modelIn": "notification.FcmTokenRemoveInput", - "modelOut": "notification.FcmTokenRemoveOutput", + "createdAt": "2025-03-17T09:02:17.638Z", + "modelIn": "RemoveRequestParams", + "modelOut": "notification.RemoveOutputParams.7e57e480187ae365dff017e73a6af35c", "methodFilters": [] }, { @@ -340,7 +340,7 @@ "method": "fcm-token.save", "description": "Save FCM token for user", "allowGroup": [ - "admin" + "user" ], "denyGroup": [], "createdAt": "2025-03-10T18:03:29.296Z", @@ -350,15 +350,41 @@ }, { "microservice": "notification", - "method": "fcm-token.remove-user-tokens", - "description": "Remove all FCM tokens for a user", + "method": "fcm-token.list", + "description": "Returns list of FcmToken by given condition", + "allowGroup": [ + "admin" + ], + "denyGroup": [], + "createdAt": "2025-03-17T09:02:17.638Z", + "modelIn": "ListRequestParams", + "modelOut": "notification.ListOutputParams.07cb21604c24f47f2672215971731023", + "methodFilters": [] + }, + { + "microservice": "notification", + "method": "fcm-token.view", + "description": "Returns FcmToken by given condition", + "allowGroup": [ + "admin" + ], + "denyGroup": [], + "createdAt": "2025-03-17T09:02:17.638Z", + "modelIn": "ViewRequestParams", + "modelOut": "notification.ViewOutputParams.c7183e89e574222a2a3d0fca89605104", + "methodFilters": [] + }, + { + "microservice": "notification", + "method": "fcm-token.count", + "description": "Returns count of FcmToken by given condition", "allowGroup": [ "admin" ], "denyGroup": [], - "createdAt": "2025-03-10T19:45:43.110Z", - "modelIn": "notification.FcmTokenRemoveUserTokensInput", - "modelOut": "notification.FcmTokenRemoveUserTokensOutput", + "createdAt": "2025-03-17T09:02:17.638Z", + "modelIn": "CountRequestParams", + "modelOut": "CountOutputParams", "methodFilters": [] } ] diff --git a/microservices/authorization/migrations/permissions/list/models/notification.json b/microservices/authorization/migrations/permissions/list/models/notification.json index fbbd23a3..be121c35 100644 --- a/microservices/authorization/migrations/permissions/list/models/notification.json +++ b/microservices/authorization/migrations/permissions/list/models/notification.json @@ -111,7 +111,7 @@ "task": "notification.Task", "recipient": "notification.Recipient" }, - "createdAt": "2022-04-14T05:34:31.136Z" + "createdAt": "2025-03-17T09:02:17.638Z" }, { "microservice": "notification", @@ -128,7 +128,7 @@ } } }, - "createdAt": "2022-04-14T05:34:31.136Z" + "createdAt": "2025-03-17T09:02:17.638Z" }, { "microservice": "notification", @@ -807,8 +807,8 @@ }, { "microservice": "notification", - "alias": "notification.FcmTokenRemoveInput", - "title": "Fcm Token Remove Input", + "alias": "notification.FcmToken", + "title": "Fcm Token", "schema": { "token": { "in": { @@ -817,32 +817,34 @@ "out": { "admin": "allow" } - } - }, - "createdAt": "2025-03-10T18:03:29.296Z" - }, - { - "microservice": "notification", - "alias": "notification.FcmTokenRemoveOutput", - "title": "Fcm Token Remove Output", - "schema": { - "success": { + }, + "userId": { + "in": { + "user": { + "condition": "Fields: user owner" + } + }, + "out": { + "admin": "allow" + } + }, + "userAgent": { "in": { "admin": "allow" }, "out": { "admin": "allow" } - } - }, - "createdAt": "2025-03-10T18:03:29.296Z" - }, - { - "microservice": "notification", - "alias": "notification.FcmTokenRemoveUserTokensInput", - "title": "Fcm Token Remove User Tokens Input", - "schema": { - "userId": { + }, + "createdAt": { + "in": { + "admin": "allow" + }, + "out": { + "admin": "allow" + } + }, + "updatedAt": { "in": { "admin": "allow" }, @@ -851,14 +853,15 @@ } } }, - "createdAt": "2025-03-10T19:45:43.110Z" + "createdAt": "2025-03-17T09:02:17.638Z" }, { "microservice": "notification", - "alias": "notification.FcmTokenRemoveUserTokensOutput", - "title": "Fcm Token Remove User Tokens Output", + "alias": "notification.ListOutputParams.07cb21604c24f47f2672215971731023", + "title": "List Output Params", "schema": { - "success": { + "list": "notification.FcmToken", + "count": { "in": { "admin": "allow" }, @@ -867,6 +870,25 @@ } } }, - "createdAt": "2025-03-10T19:45:43.110Z" + "createdAt": "2025-03-17T09:02:17.638Z" + }, + { + "microservice": "notification", + "alias": "notification.ViewOutputParams.c7183e89e574222a2a3d0fca89605104", + "title": "View Output Params", + "schema": { + "entity": "notification.FcmToken" + }, + "createdAt": "2025-03-17T09:02:17.638Z" + }, + { + "microservice": "notification", + "alias": "notification.RemoveOutputParams.7e57e480187ae365dff017e73a6af35c", + "title": "Remove Output Params", + "schema": { + "deleted": "notification.FcmToken", + "entities": "notification.FcmToken" + }, + "createdAt": "2025-03-17T09:02:17.638Z" } ] diff --git a/microservices/notification/migrations/1742223121658-use-userid-and-useragent-as-pk.ts b/microservices/notification/migrations/1742223121658-use-userid-and-useragent-as-pk.ts new file mode 100644 index 00000000..b90a6316 --- /dev/null +++ b/microservices/notification/migrations/1742223121658-use-userid-and-useragent-as-pk.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export default class useUseridAndUseragentAsPk1742223121658 implements MigrationInterface { + name = 'useUseridAndUseragentAsPk1742223121658'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_fcm_token_userId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_fcm_token_token"`); + await queryRunner.query(`ALTER TABLE "fcm_token" DROP CONSTRAINT IF EXISTS "PK_fcm_token"`); + await queryRunner.query(`UPDATE "fcm_token" + SET "userAgent" = '' + WHERE "userAgent" IS NULL`); + await queryRunner.query(`ALTER TABLE "fcm_token" + ALTER COLUMN "userAgent" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "fcm_token" + ADD CONSTRAINT "PK_fcm_token_new" PRIMARY KEY ("userId", "userAgent")`); + await queryRunner.query(`ALTER TABLE "fcm_token" + ADD CONSTRAINT "UQ_fcm_token_token" UNIQUE ("token")`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "fcm_token" DROP CONSTRAINT "UQ_fcm_token_token"`); + await queryRunner.query(`ALTER TABLE "fcm_token" DROP CONSTRAINT "PK_fcm_token_new"`); + await queryRunner.query(`ALTER TABLE "fcm_token" + ALTER COLUMN "userAgent" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "fcm_token" + ADD CONSTRAINT "PK_fcm_token" PRIMARY KEY ("token")`); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_fcm_token_token" ON "fcm_token" ("token") `); + await queryRunner.query(`CREATE INDEX "IDX_fcm_token_userId" ON "fcm_token" ("userId") `); + } +} diff --git a/microservices/notification/src/entities/fcm-token.ts b/microservices/notification/src/entities/fcm-token.ts index ed3e6794..4542b68d 100644 --- a/microservices/notification/src/entities/fcm-token.ts +++ b/microservices/notification/src/entities/fcm-token.ts @@ -1,29 +1,29 @@ -import { IsTypeormDate, IsUndefinable } from '@lomray/microservice-helpers'; +import { IsTypeormDate } from '@lomray/microservice-helpers'; import { IsString, Length } from 'class-validator'; import { JSONSchema } from 'class-validator-jsonschema'; -import { Entity, Column, CreateDateColumn, Index, UpdateDateColumn, PrimaryColumn } from 'typeorm'; +import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryColumn, Unique } from 'typeorm'; @JSONSchema({ title: 'FCM Token', }) @Entity() class FcmToken { - @PrimaryColumn({ type: 'varchar', length: 255 }) + @Column({ type: 'varchar', length: 255 }) @Length(1, 255) @IsString() + @Unique(['token']) token: string; - @Index('IDX_fcm_token_userId', ['userId']) - @Column({ type: 'varchar', length: 36 }) + @PrimaryColumn({ type: 'varchar', length: 36 }) @Length(1, 36) @IsString() userId: string; - @Column({ type: 'varchar', length: 255, nullable: true }) + @Column({ type: 'varchar', length: 255 }) @Length(1, 255) @IsString() - @IsUndefinable() - userAgent?: string; + @PrimaryColumn() + userAgent: string; @IsTypeormDate() @CreateDateColumn() diff --git a/microservices/notification/src/methods/fcm-token/crud.ts b/microservices/notification/src/methods/fcm-token/crud.ts new file mode 100644 index 00000000..57e07ab2 --- /dev/null +++ b/microservices/notification/src/methods/fcm-token/crud.ts @@ -0,0 +1,13 @@ +import { Endpoint } from '@lomray/microservice-helpers'; +import { getRepository } from 'typeorm'; +import FcmToken from '@entities/fcm-token'; + +/** + * CRUD controller for FCMToken entities + */ +const crud = Endpoint.controller(() => getRepository(FcmToken), { + restore: false, + create: false, +}); + +export default crud; diff --git a/microservices/notification/src/methods/fcm-token/remove-user-tokens.ts b/microservices/notification/src/methods/fcm-token/remove-user-tokens.ts deleted file mode 100644 index fc6b4304..00000000 --- a/microservices/notification/src/methods/fcm-token/remove-user-tokens.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Endpoint } from '@lomray/microservice-helpers'; -import { IsBoolean, IsString, Length } from 'class-validator'; -import { getRepository } from 'typeorm'; -import FcmToken from '@entities/fcm-token'; -import FCMService from '@services/firebase'; - -class FcmTokenRemoveUserTokensInput { - @IsString() - @Length(1, 36) - userId: string; -} - -class FcmTokenRemoveUserTokensOutput { - @IsBoolean() - success: boolean; -} - -/** - * Remove all FCM tokens for a user - */ -const removeUserTokens = Endpoint.custom( - () => ({ - input: FcmTokenRemoveUserTokensInput, - output: FcmTokenRemoveUserTokensOutput, - description: 'Remove all FCM tokens for a user', - }), - async ({ userId }) => { - const fcmService = new FCMService(getRepository(FcmToken)); - - await fcmService.removeUserTokens(userId); - - return { - success: true, - }; - }, -); - -export default removeUserTokens; diff --git a/microservices/notification/src/methods/fcm-token/remove.ts b/microservices/notification/src/methods/fcm-token/remove.ts deleted file mode 100644 index aad3b100..00000000 --- a/microservices/notification/src/methods/fcm-token/remove.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Endpoint } from '@lomray/microservice-helpers'; -import { IsBoolean, IsString, MaxLength } from 'class-validator'; -import { getRepository } from 'typeorm'; -import FcmToken from '@entities/fcm-token'; -import FCMService from '@services/firebase'; - -class FcmTokenRemoveInput { - @IsString() - @MaxLength(255) - token: string; -} - -class FcmTokenRemoveOutput { - @IsBoolean() - success: boolean; -} - -/** - * Remove FCM token - */ -const remove = Endpoint.custom( - () => ({ - input: FcmTokenRemoveInput, - output: FcmTokenRemoveOutput, - description: 'Remove FCM token', - }), - async ({ token }) => { - const fcmService = new FCMService(getRepository(FcmToken)); - - await fcmService.removeToken(token); - - return { - success: true, - }; - }, -); - -export default remove; diff --git a/microservices/notification/src/methods/index.ts b/microservices/notification/src/methods/index.ts index 368655fb..863bc0dd 100644 --- a/microservices/notification/src/methods/index.ts +++ b/microservices/notification/src/methods/index.ts @@ -2,8 +2,7 @@ import MetaEndpoint from '@lomray/microservice-helpers/methods/meta'; import type { Microservice, IEndpointHandler } from '@lomray/microservice-nodejs-lib'; import CONST from '@constants/index'; import EmailSend from '@methods/email/send'; -import FcmTokenRemove from '@methods/fcm-token/remove'; -import FcmTokenRemoveUserTokens from '@methods/fcm-token/remove-user-tokens'; +import FcmTokenCrud from '@methods/fcm-token/crud'; import FcmTokenSave from '@methods/fcm-token/save'; import CrudMessage from '@methods/messages/crud'; import CrudNotice from '@methods/notice/crud'; @@ -23,6 +22,10 @@ export default (ms: Microservice): void => { 'hide-all': NoticeHideAll, }, task: CrudTask, + 'fcm-token': { + ...FcmTokenCrud, + save: FcmTokenSave, + }, }; /** @@ -49,13 +52,6 @@ export default (ms: Microservice): void => { */ ms.addEndpoint('push.send', PushSend); - /** - * FCM Token methods - */ - ms.addEndpoint('fcm-token.save', FcmTokenSave); - ms.addEndpoint('fcm-token.remove', FcmTokenRemove); - ms.addEndpoint('fcm-token.remove-user-tokens', FcmTokenRemoveUserTokens); - /** * Microservice metadata endpoint */ diff --git a/microservices/notification/src/services/firebase/index.ts b/microservices/notification/src/services/firebase/index.ts index a6153252..48633667 100644 --- a/microservices/notification/src/services/firebase/index.ts +++ b/microservices/notification/src/services/firebase/index.ts @@ -155,14 +155,13 @@ class FCMService { /** * Save or update FCM token for a user */ - public async saveToken(userId: string, token: string, userAgent?: string): Promise { + public async saveToken(userId: string, token: string, userAgent: string): Promise { // Check if token already exists - let fcmToken = await this.tokenRepository.findOne({ where: { token } }); + let fcmToken = await this.tokenRepository.findOne({ where: { userId, userAgent } }); if (fcmToken) { // Update existing token - fcmToken.userId = userId; - fcmToken.userAgent = userAgent; + fcmToken.token = token; return this.tokenRepository.save(fcmToken); } @@ -176,56 +175,6 @@ class FCMService { return this.tokenRepository.save(fcmToken); } - - /** - * Remove FCM token - */ - public async removeToken(token: string): Promise { - try { - const result = await this.tokenRepository.delete({ token }); - - if (result.affected === 0) { - throw new BaseException({ - status: 404, - message: 'FCM token not found', - }); - } - } catch (error) { - if (error instanceof BaseException) { - throw error; - } - - throw new BaseException({ - status: 500, - message: 'Failed to remove FCM token', - }); - } - } - - /** - * Remove all FCM tokens for a user - */ - public async removeUserTokens(userId: string): Promise { - try { - const result = await this.tokenRepository.delete({ userId }); - - if (result.affected === 0) { - throw new BaseException({ - status: 404, - message: 'No FCM tokens found for this user', - }); - } - } catch (error) { - if (error instanceof BaseException) { - throw error; - } - - throw new BaseException({ - status: 500, - message: 'Failed to remove user FCM tokens', - }); - } - } } export default FCMService;