diff --git a/backend/package.json b/backend/package.json index 7ec1ba5..715cf0f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,7 @@ "@nestjs/core": "^8.0.0", "@nestjs/passport": "^8.2.1", "@nestjs/platform-express": "^8.4.5", + "cache-manager": "^4.1.0", "class-transformer": "^0.5.1", "dayjs": "^1.11.2", "express": "^4.18.1", @@ -42,6 +43,7 @@ "@nestjs/cli": "^8.0.0", "@nestjs/schematics": "^8.0.0", "@nestjs/testing": "^8.4.5", + "@types/cache-manager": "^4.0.1", "@types/express": "^4.17.13", "@types/jest": "27.5.0", "@types/node": "^16.0.0", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index ea143c0..3f6d69a 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { CacheModule, Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { FirebaseAuthStrategy } from './firebase/firebase-auth.strategy'; import { FirestoreModule } from './firestore/firestore.module'; @@ -19,6 +19,7 @@ import { UsersModule } from './users/users.module'; }), inject: [ConfigService], }), + CacheModule.register(), PostsModule, UsersModule, ], diff --git a/backend/src/posts/posts.controller.spec.ts b/backend/src/posts/posts.controller.spec.ts index a622054..c46e5e4 100644 --- a/backend/src/posts/posts.controller.spec.ts +++ b/backend/src/posts/posts.controller.spec.ts @@ -1,5 +1,6 @@ import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; +import { Cache } from 'cache-manager'; jest.mock('./utils', () => { return { @@ -13,12 +14,26 @@ jest.mock('./utils', () => { describe('PostsController', () => { let c: PostsController; let s: PostsService; + let cache: Cache; let old_env; beforeEach(() => { const collection = null; s = new PostsService(collection); - c = new PostsController(s); + cache = { + get: jest.fn(), + set: jest.fn(), + wrap: jest.fn(), + del: jest.fn(), + reset: jest.fn(), + store: { + get: jest.fn(), + set: jest.fn(), + }, + }; + jest.spyOn(cache, 'get').mockResolvedValue(null); // cache fails + jest.spyOn(cache, 'set'); + c = new PostsController(s, cache); old_env = process.env; process.env = { MAX_NUMBER_POSTS_PER_DAY: '5', MAX_MESSAGE_LENGTH: '100' }; jest.spyOn(s, 'countAllforUserByDate').mockResolvedValue(0); diff --git a/backend/src/posts/posts.controller.ts b/backend/src/posts/posts.controller.ts index 8709e64..d395d82 100644 --- a/backend/src/posts/posts.controller.ts +++ b/backend/src/posts/posts.controller.ts @@ -1,9 +1,11 @@ import { BadRequestException, Body, + CACHE_MANAGER, Controller, Get, HttpCode, + Inject, Param, ParseIntPipe, Post, @@ -21,10 +23,24 @@ import { import { PostsService } from './posts.service'; import { FirebaseAuthGuard } from '../firebase/firebase-auth.guard'; import { getDisplayNameByUserId } from './utils'; +import { Cache } from 'cache-manager'; @Controller('posts') export class PostsController { - constructor(private readonly service: PostsService) {} + constructor( + private readonly service: PostsService, + @Inject(CACHE_MANAGER) private cacheManager: Cache, + ) {} + + private async resolveUserName(userId: string): Promise { + const key = `username-${userId}`; + let userName: string = await this.cacheManager.get(key); + if (userName === null) { + userName = await getDisplayNameByUserId(userId); + await this.cacheManager.set(key, userName); + } + return userName; + } @Get() @UseGuards(FirebaseAuthGuard) @@ -49,7 +65,7 @@ export class PostsController { ...parentPost, likes: (parentPost.likes || []).length, likedByMe: (parentPost.likes || []).includes(userId), - userName: await getDisplayNameByUserId(parentPost.userId), + userName: await this.resolveUserName(parentPost.userId), comments: await this.service.countAllForParentId(parentPost.id), }); } @@ -58,7 +74,7 @@ export class PostsController { ...results[p], likes: (results[p].likes || []).length, likedByMe: (results[p].likes || []).includes(userId), - userName: await getDisplayNameByUserId(results[p].userId), + userName: await this.resolveUserName(results[p].userId), comments: await this.service.countAllForParentId(results[p].id), }); } @@ -194,7 +210,7 @@ export class PostsController { const post = await this.service.get(id); - const userName = await getDisplayNameByUserId(post.userId); + const userName = await this.resolveUserName(post.userId); const comments = post.parentId ? await this.service.countAllForParentId(post.parentId) : 0; diff --git a/backend/yarn.lock b/backend/yarn.lock index 1f18fe2..4caf608 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -1052,6 +1052,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/cache-manager@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/cache-manager/-/cache-manager-4.0.1.tgz#96107bfc3d35100fc6b65fd9b0ea0bcc4ff5118c" + integrity sha512-w4Gm7qg4ohvk0k4CLhOoqnMohWEyeyAOTovPgkguhuDCfVEV1wN/HWEd1XzB1S9/NV9pUcZcc498qU4E15ck6A== + "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -1637,6 +1642,11 @@ async-retry@^1.3.3: dependencies: retry "0.13.1" +async@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1828,6 +1838,15 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cache-manager@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-4.1.0.tgz#aa986421f1c975a862d6de88edb9ab1d30f4bd39" + integrity sha512-ZGM6dLxrP65bfOZmcviWMadUOCICqpLs92+P/S5tj8onz+k+tB7Gr+SAgOUHCQtfm2gYEQDHiKeul4+tYPOJ8A== + dependencies: + async "3.2.3" + lodash.clonedeep "^4.5.0" + lru-cache "^7.10.1" + call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -3984,6 +4003,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.10.1: + version "7.14.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f" + integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ== + lru-cache@~4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"