23: cache

This commit is contained in:
Phuoc Nguyen
2025-05-23 14:55:22 +07:00
parent a6cd959531
commit d03e502f2f
7 changed files with 142 additions and 9 deletions

86
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"@elastic/elasticsearch": "^9.0.2", "@elastic/elasticsearch": "^9.0.2",
"@hapi/joi": "^17.1.1", "@hapi/joi": "^17.1.1",
"@neondatabase/serverless": "^1.0.0", "@neondatabase/serverless": "^1.0.0",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
@@ -29,6 +30,7 @@
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"cache-manager": "^6.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
@@ -2008,6 +2010,39 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@keyv/serialize": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz",
"integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3"
}
},
"node_modules/@keyv/serialize/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/@lukeed/csprng": { "node_modules/@lukeed/csprng": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
@@ -2407,6 +2442,19 @@
"node": ">=19.0.0" "node": ">=19.0.0"
} }
}, },
"node_modules/@nestjs/cache-manager": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.0.1.tgz",
"integrity": "sha512-4UxTnR0fsmKL5YDalU2eLFVnL+OBebWUpX+hEduKGncrVKH4PPNoiRn1kXyOCjmzb0UvWgqubpssNouc8e0MCw==",
"license": "MIT",
"peerDependencies": {
"@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0",
"@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0",
"cache-manager": ">=6",
"keyv": ">=5",
"rxjs": "^7.8.1"
}
},
"node_modules/@nestjs/cli": { "node_modules/@nestjs/cli": {
"version": "11.0.7", "version": "11.0.7",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.7.tgz", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.7.tgz",
@@ -5818,6 +5866,15 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/cache-manager": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-6.4.3.tgz",
"integrity": "sha512-VV5eq/QQ5rIVix7/aICO4JyvSeEv9eIQuKL5iFwgM2BrcYoE0A/D1mNsAHJAsB0WEbNdBlKkn6Tjz6fKzh/cKQ==",
"license": "MIT",
"dependencies": {
"keyv": "^5.3.3"
}
},
"node_modules/cacheable-lookup": { "node_modules/cacheable-lookup": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
@@ -5847,6 +5904,16 @@
"node": ">=14.16" "node": ">=14.16"
} }
}, },
"node_modules/cacheable-request/node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -7664,6 +7731,16 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/flat-cache/node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/flatbuffers": { "node_modules/flatbuffers": {
"version": "24.12.23", "version": "24.12.23",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.12.23.tgz", "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.12.23.tgz",
@@ -9613,13 +9690,12 @@
} }
}, },
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"json-buffer": "3.0.1" "@keyv/serialize": "^1.0.3"
} }
}, },
"node_modules/kind-of": { "node_modules/kind-of": {

View File

@@ -23,6 +23,7 @@
"@elastic/elasticsearch": "^9.0.2", "@elastic/elasticsearch": "^9.0.2",
"@hapi/joi": "^17.1.1", "@hapi/joi": "^17.1.1",
"@neondatabase/serverless": "^1.0.0", "@neondatabase/serverless": "^1.0.0",
"@nestjs/cache-manager": "^3.0.1",
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
@@ -40,6 +41,7 @@
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"cache-manager": "^6.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",

View File

@@ -0,0 +1,19 @@
import { CACHE_KEY_METADATA, CacheInterceptor, } from '@nestjs/cache-manager';
import {ExecutionContext, Injectable} from "@nestjs/common";
@Injectable()
export class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const cacheKey = this.reflector.get(
CACHE_KEY_METADATA,
context.getHandler(),
);
if (cacheKey) {
const request = context.switchToHttp().getRequest();
return `${cacheKey}-${request._parsedUrl.query}`;
}
return super.trackBy(context);
}
}

View File

@@ -17,6 +17,8 @@ import {UpdatePostDto} from './dto/update-post.dto';
import JwtAuthenticationGuard from "../authentication/jwt-authentication.guard"; import JwtAuthenticationGuard from "../authentication/jwt-authentication.guard";
import RequestWithUser from "../authentication/requestWithUser.interface"; import RequestWithUser from "../authentication/requestWithUser.interface";
import {PaginationParams} from "../utils/types/paginationParams"; import {PaginationParams} from "../utils/types/paginationParams";
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
import {GET_POSTS_CACHE_KEY} from "./postsCacheKey.constant";
@Controller('posts') @Controller('posts')
@UseInterceptors(ClassSerializerInterceptor) @UseInterceptors(ClassSerializerInterceptor)
@@ -30,7 +32,9 @@ export class PostsController {
return this.postsService.createPost(post, req.user); return this.postsService.createPost(post, req.user);
} }
@UseInterceptors(CacheInterceptor)
@CacheKey(GET_POSTS_CACHE_KEY)
@CacheTTL(120)
@Get() @Get()
async getPosts( async getPosts(
@Query('search') search: string, @Query('search') search: string,

View File

@@ -6,10 +6,20 @@ import User from "../users/entities/user.entity";
import Post from "./entities/post.entity"; import Post from "./entities/post.entity";
import PostsSearchService from "./postsSearch.service"; import PostsSearchService from "./postsSearch.service";
import {SearchModule} from "../search/search.module"; import {SearchModule} from "../search/search.module";
import { CacheModule } from '@nestjs/cache-manager';
@Module({ @Module({
controllers: [PostsController], controllers: [PostsController],
imports: [TypeOrmModule.forFeature([Post]), SearchModule], imports: [
CacheModule.register(
{
ttl: 5,
max: 100
}
),
TypeOrmModule.forFeature([Post]),
SearchModule
],
providers: [PostsService, PostsSearchService], providers: [PostsService, PostsSearchService],
exports: [PostsService, PostsSearchService], exports: [PostsService, PostsSearchService],
}) })

View File

@@ -1,4 +1,4 @@
import {Injectable} from '@nestjs/common'; import {Inject, Injectable} from '@nestjs/common';
import {CreatePostDto} from './dto/create-post.dto'; import {CreatePostDto} from './dto/create-post.dto';
import {UpdatePostDto} from './dto/update-post.dto'; import {UpdatePostDto} from './dto/update-post.dto';
import User from "../users/entities/user.entity"; import User from "../users/entities/user.entity";
@@ -8,22 +8,39 @@ import Post from './entities/post.entity';
import {PostNotFoundException} from "./exception/postNotFound.exception"; import {PostNotFoundException} from "./exception/postNotFound.exception";
import PostsSearchService from "./postsSearch.service"; import PostsSearchService from "./postsSearch.service";
import {In} from "typeorm"; import {In} from "typeorm";
import { Cache } from 'cache-manager';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import {GET_POSTS_CACHE_KEY} from "./postsCacheKey.constant";
@Injectable() @Injectable()
export class PostsService { export class PostsService {
constructor( constructor(
@InjectRepository(Post) private repo: Repository<Post>, @InjectRepository(Post) private repo: Repository<Post>,
private postsSearchService: PostsSearchService private postsSearchService: PostsSearchService,
@Inject(CACHE_MANAGER) private cacheManager: Cache
) { ) {
} }
async clearCache() {
// const keys: string[] = this.cacheManager.stores.keys();
//
// await Promise.all(
// keys
// .filter((key) => key.startsWith(GET_POSTS_CACHE_KEY))
// .map((key) => this.cacheManager.del(key)) // note: 'del', not 'delete'
// );
}
async createPost(post: CreatePostDto, user: User) { async createPost(post: CreatePostDto, user: User) {
const newPost = this.repo.create({ const newPost = this.repo.create({
...post, ...post,
author: user author: user
}); });
await this.repo.save(newPost); await this.repo.save(newPost);
await this.postsSearchService.indexPost(newPost);
await this.clearCache();
return newPost; return newPost;
} }
@@ -91,6 +108,8 @@ export class PostsService {
}); });
if (updatedPost) { if (updatedPost) {
await this.postsSearchService.update(updatedPost);
await this.clearCache();
return updatedPost return updatedPost
} }
throw new PostNotFoundException(id); throw new PostNotFoundException(id);
@@ -114,6 +133,7 @@ export class PostsService {
}); });
if (updatedPost) { if (updatedPost) {
await this.postsSearchService.update(updatedPost); await this.postsSearchService.update(updatedPost);
await this.clearCache();
return updatedPost; return updatedPost;
} }
throw new PostNotFoundException(id); throw new PostNotFoundException(id);
@@ -125,5 +145,6 @@ export class PostsService {
throw new PostNotFoundException(id); throw new PostNotFoundException(id);
} }
await this.postsSearchService.remove(id); await this.postsSearchService.remove(id);
await this.clearCache();
} }
} }

View File

@@ -0,0 +1 @@
export const GET_POSTS_CACHE_KEY = 'GET_POSTS_CACHE';