From 2fb8da1901b5d98f1b3f9876591181ff690a9aa8 Mon Sep 17 00:00:00 2001 From: Navaneethakrishnan Date: Tue, 3 Dec 2024 23:40:59 +0530 Subject: [PATCH 1/3] feat: added cache revalidation API integration --- .env.example | 4 +- apps/web-api/src/auth/auth.module.ts | 3 +- apps/web-api/src/members/members.service.ts | 13 ++--- .../participants-request.service.ts | 24 ++++---- .../src/pl-events/pl-event-guests.service.ts | 10 ++-- apps/web-api/src/projects/projects.service.ts | 12 ++-- apps/web-api/src/shared/shared.module.ts | 3 + apps/web-api/src/teams/teams.service.ts | 9 ++- apps/web-api/src/utils/cache/cache.service.ts | 58 +++++++++++++++++++ apps/web-api/src/utils/redis/redis.service.ts | 24 -------- 10 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 apps/web-api/src/utils/cache/cache.service.ts delete mode 100644 apps/web-api/src/utils/redis/redis.service.ts diff --git a/.env.example b/.env.example index 8ebeba876..c1467cdfe 100644 --- a/.env.example +++ b/.env.example @@ -116,7 +116,9 @@ ADMIN_LOGIN_USERNAME= # For local development, use: admin ADMIN_LOGIN_PASSWORD= - +# Revalidate cache API token +# For local development, use: random string +REVALIDATE_TOKEN= # Optional for local development diff --git a/apps/web-api/src/auth/auth.module.ts b/apps/web-api/src/auth/auth.module.ts index c3482180c..03ab0ff58 100644 --- a/apps/web-api/src/auth/auth.module.ts +++ b/apps/web-api/src/auth/auth.module.ts @@ -4,12 +4,11 @@ import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { HttpModule } from '@nestjs/axios'; import { PrismaService } from '../shared/prisma.service'; -import { RedisService } from '../utils/redis/redis.service'; import { OtpModule } from '../otp/otp.module'; @Module({ imports: [HttpModule, OtpModule], controllers: [AuthController], - providers: [AuthService, PrismaService, RedisService], + providers: [AuthService, PrismaService], exports: [AuthService] }) export class AuthModule {} diff --git a/apps/web-api/src/members/members.service.ts b/apps/web-api/src/members/members.service.ts index 815f1134c..dde769aac 100644 --- a/apps/web-api/src/members/members.service.ts +++ b/apps/web-api/src/members/members.service.ts @@ -1,7 +1,6 @@ /* eslint-disable prettier/prettier */ import { BadRequestException, - CACHE_MANAGER, ConflictException, NotFoundException, Inject, @@ -11,7 +10,6 @@ import { import { z } from 'zod'; import axios from 'axios'; import * as path from 'path'; -import { Cache } from 'cache-manager'; import { Prisma, Member, ParticipantsRequest } from '@prisma/client'; import { PrismaService } from '../shared/prisma.service'; import { ParticipantsRequestService } from '../participants-request/participants-request.service'; @@ -26,6 +24,7 @@ import { LogService } from '../shared/log.service'; import { DEFAULT_MEMBER_ROLES } from '../utils/constants'; import { hashFileName } from '../utils/hashing'; import { copyObj, buildMultiRelationMapping } from '../utils/helper/helper'; +import { CacheService } from '../utils/cache/cache.service'; @Injectable() export class MembersService { @@ -41,8 +40,8 @@ export class MembersService { private participantsRequestService: ParticipantsRequestService, @Inject(forwardRef(() => NotificationService)) private notificationService: NotificationService, - @Inject(CACHE_MANAGER) private cacheService: Cache - ) { } + private cacheService: CacheService + ) {} /** * Creates a new member in the database within a transaction. @@ -427,7 +426,7 @@ export class MembersService { newTokens = await this.authService.updateEmailInAuth(newEmail, oldEmail, memberInfo.externalId) }); this.logger.info(`Email has been successfully updated from ${oldEmail} to ${newEmail}`) - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'members'}); return { refreshToken: newTokens.refresh_token, idToken: newTokens.id_token, @@ -1166,7 +1165,7 @@ export class MembersService { */ async updatePreference(id: string, preferences: any): Promise { const updatedMember = await this.updateMemberByUid(id, { preferences }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'members'}); return updatedMember; } @@ -1175,7 +1174,7 @@ export class MembersService { * This ensures that the system is up-to-date with the latest changes. */ private async postUpdateActions(): Promise { - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'members'}); await this.forestadminService.triggerAirtableSync(); } diff --git a/apps/web-api/src/participants-request/participants-request.service.ts b/apps/web-api/src/participants-request/participants-request.service.ts index 9595e3199..e549454f4 100644 --- a/apps/web-api/src/participants-request/participants-request.service.ts +++ b/apps/web-api/src/participants-request/participants-request.service.ts @@ -1,15 +1,13 @@ /* eslint-disable prettier/prettier */ -import { - BadRequestException, - ConflictException, +import { + BadRequestException, + ConflictException, NotFoundException, - Inject, Injectable, - CACHE_MANAGER, - forwardRef + forwardRef, + Inject } from '@nestjs/common'; import { ApprovalStatus, ParticipantType } from '@prisma/client'; -import { Cache } from 'cache-manager'; import { Prisma, ParticipantsRequest, PrismaClient } from '@prisma/client'; import { generateProfileURL } from '../utils/helper/helper'; import { LogService } from '../shared/log.service'; @@ -19,6 +17,7 @@ import { TeamsService } from '../teams/teams.service'; import { NotificationService } from '../utils/notification/notification.service'; import { LocationTransferService } from '../utils/location-transfer/location-transfer.service'; import { ForestAdminService } from '../utils/forest-admin/forest-admin.service'; +import { CacheService } from '../utils/cache/cache.service'; @Injectable() export class ParticipantsRequestService { @@ -28,8 +27,7 @@ export class ParticipantsRequestService { private locationTransferService: LocationTransferService, private forestAdminService: ForestAdminService, private notificationService: NotificationService, - @Inject(CACHE_MANAGER) - private cacheService: Cache, + private cacheService: CacheService, @Inject(forwardRef(() => MembersService)) private membersService: MembersService, @Inject(forwardRef(() => TeamsService)) @@ -166,7 +164,7 @@ export class ParticipantsRequestService { where: { uid }, data: formattedData, }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: "participants-requests" }); return result; } catch (err) { return this.handleErrors(err) @@ -187,7 +185,7 @@ export class ParticipantsRequestService { where: { uid: uidToReject }, data: { status: ApprovalStatus.REJECTED } }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: "participants-requests" }); return result; } catch (err) { return this.handleErrors(err) @@ -250,7 +248,7 @@ export class ParticipantsRequestService { participantsRequest.requesterEmailId ); } - await this.cacheService.reset(); + await this.cacheService.reset({ service: "participants-requests" }); await this.forestAdminService.triggerAirtableSync(); return result; } @@ -296,7 +294,7 @@ export class ParticipantsRequestService { if (!disableNotification) { this.notifyForCreate(result); } - await this.cacheService.reset(); + await this.cacheService.reset({ service: "participants-requests" }); return result; } diff --git a/apps/web-api/src/pl-events/pl-event-guests.service.ts b/apps/web-api/src/pl-events/pl-event-guests.service.ts index 8ea507220..e16df6f5e 100644 --- a/apps/web-api/src/pl-events/pl-event-guests.service.ts +++ b/apps/web-api/src/pl-events/pl-event-guests.service.ts @@ -1,9 +1,8 @@ -import { Injectable, NotFoundException, ConflictException, BadRequestException, Inject, CACHE_MANAGER } from '@nestjs/common'; +import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common'; import { LogService } from '../shared/log.service'; import { PrismaService } from '../shared/prisma.service'; import { Prisma, Member } from '@prisma/client'; import { MembersService } from '../members/members.service'; -import { Cache } from 'cache-manager'; import { PLEventLocationsService } from './pl-event-locations.service'; import { CreatePLEventGuestSchemaDto, @@ -13,6 +12,7 @@ import { FormattedLocationWithEvents, PLEvent } from './pl-event-locations.types'; +import { CacheService } from '../utils/cache/cache.service'; @Injectable() export class PLEventGuestsService { @@ -21,7 +21,7 @@ export class PLEventGuestsService { private logger: LogService, private memberService: MembersService, private eventLocationsService: PLEventLocationsService, - @Inject(CACHE_MANAGER) private cacheService: Cache + private cacheService: CacheService ) {} /** @@ -43,7 +43,7 @@ export class PLEventGuestsService { data.memberUid = isAdmin ? data.memberUid : member.uid; const guests = this.formatInputToEventGuests(data); const result = await (tx || this.prisma).pLEventGuest.createMany({ data: guests }); - this.cacheService.reset(); + this.cacheService.reset({ service: 'PLEventGuest' }); return result; } catch(err) { this.handleErrors(err); @@ -100,7 +100,7 @@ export class PLEventGuestsService { OR: deleteConditions } }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'PLEventGuest' }); return result; } catch (err) { this.handleErrors(err); diff --git a/apps/web-api/src/projects/projects.service.ts b/apps/web-api/src/projects/projects.service.ts index 4ea954232..253c08924 100644 --- a/apps/web-api/src/projects/projects.service.ts +++ b/apps/web-api/src/projects/projects.service.ts @@ -1,9 +1,9 @@ -import { Inject, CACHE_MANAGER, BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException, HttpException } from '@nestjs/common'; +import { BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { LogService } from '../shared/log.service'; import { PrismaService } from '../shared/prisma.service'; import { Prisma } from '@prisma/client'; import { MembersService } from '../members/members.service'; -import { Cache } from 'cache-manager'; +import { CacheService } from '../utils/cache/cache.service'; @Injectable() export class ProjectsService { @@ -11,7 +11,7 @@ export class ProjectsService { private prisma: PrismaService, private memberService: MembersService, private logger: LogService, - @Inject(CACHE_MANAGER) private cacheService: Cache + private cacheService: CacheService ) { } async createProject(project: Prisma.ProjectUncheckedCreateInput, userEmail: string) { @@ -34,7 +34,7 @@ export class ProjectsService { } } }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'projects'}); return result; } catch (err) { this.handleErrors(err); @@ -81,7 +81,7 @@ export class ProjectsService { } } }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'projects'}); return result; }); } catch (err) { @@ -169,7 +169,7 @@ export class ProjectsService { where: { uid }, data: { isDeleted: true } }); - await this.cacheService.reset(); + await this.cacheService.reset({ service: 'projects'}); return result; } catch (err) { this.handleErrors(err, `${uid}`); diff --git a/apps/web-api/src/shared/shared.module.ts b/apps/web-api/src/shared/shared.module.ts index 26a424c30..a29c68c0a 100644 --- a/apps/web-api/src/shared/shared.module.ts +++ b/apps/web-api/src/shared/shared.module.ts @@ -10,6 +10,7 @@ import { ImagesController } from '../images/images.controller'; import { ImagesService } from '../images/images.service'; import { FileUploadService } from '../utils/file-upload/file-upload.service'; import { FileEncryptionService } from '../utils/file-encryption/file-encryption.service'; +import { CacheService } from '../utils/cache/cache.service'; @Global() @Module({ @@ -26,6 +27,7 @@ import { FileEncryptionService } from '../utils/file-encryption/file-encryption. ImagesService, FileUploadService, FileEncryptionService, + CacheService ], exports: [ PrismaService, @@ -39,6 +41,7 @@ import { FileEncryptionService } from '../utils/file-encryption/file-encryption. ImagesService, FileUploadService, FileEncryptionService, + CacheService ], }) export class SharedModule {} \ No newline at end of file diff --git a/apps/web-api/src/teams/teams.service.ts b/apps/web-api/src/teams/teams.service.ts index 49510534f..8c9456ee3 100644 --- a/apps/web-api/src/teams/teams.service.ts +++ b/apps/web-api/src/teams/teams.service.ts @@ -4,9 +4,8 @@ import { ForbiddenException, BadRequestException, NotFoundException, - Inject, forwardRef, - CACHE_MANAGER + Inject } from '@nestjs/common'; import * as path from 'path'; import { z } from 'zod'; @@ -20,8 +19,8 @@ import { hashFileName } from '../utils/hashing'; import { ForestAdminService } from '../utils/forest-admin/forest-admin.service'; import { MembersService } from '../members/members.service'; import { LogService } from '../shared/log.service'; -import { Cache } from 'cache-manager'; import { copyObj, buildMultiRelationMapping, buildRelationMapping } from '../utils/helper/helper'; +import { CacheService } from '../utils/cache/cache.service'; @Injectable() export class TeamsService { @@ -35,7 +34,7 @@ export class TeamsService { private logger: LogService, private forestadminService: ForestAdminService, private notificationService: NotificationService, - @Inject(CACHE_MANAGER) private cacheService: Cache + private cacheService: CacheService ) { } /** @@ -321,7 +320,7 @@ export class TeamsService { * This ensures that the system is up-to-date with the latest changes. */ private async postUpdateActions(): Promise { - await this.cacheService.reset(); + await this.cacheService.reset({ service: "teams" }); await this.forestadminService.triggerAirtableSync(); } diff --git a/apps/web-api/src/utils/cache/cache.service.ts b/apps/web-api/src/utils/cache/cache.service.ts new file mode 100644 index 000000000..b078cae12 --- /dev/null +++ b/apps/web-api/src/utils/cache/cache.service.ts @@ -0,0 +1,58 @@ +import { Injectable, Inject, CACHE_MANAGER } from '@nestjs/common'; +import { Cache } from 'cache-manager'; +import axios from 'axios'; +import { LogService } from '../../shared/log.service'; + +@Injectable() +export class CacheService { + constructor( + @Inject(CACHE_MANAGER) private cache: Cache, + private logService: LogService + ) {} + + // Mapping service names to tags + private serviceTagsMap = { + members: ['member-filters', 'member-list'], + projects: ['project-list', 'focus-areas'], + teams: ['team-filters', 'team-list', 'focus-areas'], + }; + + // Reset cache and call API based on service + async reset(data) { + const { service } = data; + await this.cache.reset(); // Reset the cache + const tags = this.serviceTagsMap[service]; + if (tags) { + await this.revalidateCache(tags); + } + } + + // Function to call the revalidate API + private async revalidateCache(tags: string[]) { + const baseUrl = process.env.WEB_UI_BASE_URL; + const token = process.env.REVALIDATE_API_TOKEN; // Assuming token is stored in env variable + if (!baseUrl) { + this.logService.error('WEB_UI_BASE_URL is not defined in the environment variables.'); + return; + } + if (!token) { + this.logService.error('REVALIDATE_API_TOKEN is not defined in the environment variables.'); + return; + } + const url = `${baseUrl}/api/revalidate`; + try { + await axios.post( + url, + { tags }, + { + headers: { + Authorization: `Bearer ${token}`, // Adding Bearer token to headers + }, + }, + ); + this.logService.info(`Revalidation API called successfully with tags: ${tags.join(', ')}`); + } catch (error) { + this.logService.error('Error calling revalidate API:', error.message); + } + } +} diff --git a/apps/web-api/src/utils/redis/redis.service.ts b/apps/web-api/src/utils/redis/redis.service.ts deleted file mode 100644 index a44562f50..000000000 --- a/apps/web-api/src/utils/redis/redis.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* eslint-disable prettier/prettier */ -import { Injectable } from '@nestjs/common'; -import cacheManager from 'cache-manager'; -import redisStore from 'cache-manager-redis-store'; - -@Injectable() -export class RedisService { - async resetAllCache() { - const redisCache = cacheManager.caching({ - store: redisStore, - host: process.env.REDIS_HOST, - url: process.env.REDIS_TLS_URL, - port: Number(process.env.REDIS_PORT), - password: process.env.REDIS_PASSWORD, - tls: process.env.REDIS_WITH_TLS - ? { - rejectUnauthorized: false, - requestCert: true, - } - : null, - }); - await redisCache.reset(); - } -} From 7cc3955f9b3548f65eaf434e933a9e14df16c479 Mon Sep 17 00:00:00 2001 From: navneethkrish Date: Wed, 4 Dec 2024 11:49:20 +0530 Subject: [PATCH 2/3] fix(cache service): added participant request tags for cache reset --- apps/web-api/src/utils/cache/cache.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web-api/src/utils/cache/cache.service.ts b/apps/web-api/src/utils/cache/cache.service.ts index b078cae12..0bfbd5eb5 100644 --- a/apps/web-api/src/utils/cache/cache.service.ts +++ b/apps/web-api/src/utils/cache/cache.service.ts @@ -15,6 +15,7 @@ export class CacheService { members: ['member-filters', 'member-list'], projects: ['project-list', 'focus-areas'], teams: ['team-filters', 'team-list', 'focus-areas'], + 'participants-requests': ['member-filters', 'member-list','team-filters', 'team-list', 'focus-areas'] }; // Reset cache and call API based on service @@ -41,7 +42,7 @@ export class CacheService { } const url = `${baseUrl}/api/revalidate`; try { - await axios.post( + const response = await axios.post( url, { tags }, { From 40e56620e82a727664e0a456a76f00540634613e Mon Sep 17 00:00:00 2001 From: navneethkrish Date: Wed, 4 Dec 2024 12:14:47 +0530 Subject: [PATCH 3/3] fix(sample env): modified token key name --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index c1467cdfe..af921c8c4 100644 --- a/.env.example +++ b/.env.example @@ -118,7 +118,7 @@ ADMIN_LOGIN_PASSWORD= # Revalidate cache API token # For local development, use: random string -REVALIDATE_TOKEN= +REVALIDATE_API_TOKEN= # Optional for local development