add elastic search

This commit is contained in:
Phuoc Nguyen
2025-05-22 11:35:14 +07:00
parent 2b81da1f5e
commit efea3d5411
4 changed files with 130 additions and 37 deletions

View File

@@ -1,4 +1,16 @@
import {Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req} from '@nestjs/common'; import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
Req,
Query,
ClassSerializerInterceptor, UseInterceptors
} from '@nestjs/common';
import {PostsService} from './posts.service'; import {PostsService} from './posts.service';
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';
@@ -6,6 +18,7 @@ import JwtAuthenticationGuard from "../authentication/jwt-authentication.guard";
import RequestWithUser from "../authentication/requestWithUser.interface"; import RequestWithUser from "../authentication/requestWithUser.interface";
@Controller('posts') @Controller('posts')
@UseInterceptors(ClassSerializerInterceptor)
export class PostsController { export class PostsController {
constructor(private readonly postsService: PostsService) { constructor(private readonly postsService: PostsService) {
} }
@@ -16,6 +29,15 @@ export class PostsController {
return this.postsService.createPost(post, req.user); return this.postsService.createPost(post, req.user);
} }
@Get()
async getPosts(@Query('search') search: string) {
if (search) {
return this.postsService.searchForPosts(search);
}
return this.postsService.getAllPosts();
}
@Get() @Get()
findAll() { findAll() {
return this.postsService.findAll(); return this.postsService.findAll();

View File

@@ -6,12 +6,15 @@ import {InjectRepository} from "@nestjs/typeorm";
import {Repository} from "typeorm"; import {Repository} from "typeorm";
import Post from './entities/post.entity'; import Post from './entities/post.entity';
import {PostNotFoundException} from "./exception/postNotFound.exception"; import {PostNotFoundException} from "./exception/postNotFound.exception";
import PostsSearchService from "./postsSearch.service";
import {In} from "typeorm";
@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
) { ) {
} }
@@ -24,6 +27,19 @@ export class PostsService {
return newPost; return newPost;
} }
async searchForPosts(text: string) {
const results = await this.postsSearchService.search(text);
const ids = results.flatMap(result => result.hits.hits.map(hit => hit._source.id));
if (!ids.length) {
return [];
}
return this.repo
.find({
where: {id: In(ids)}
});
}
getAllPosts() { getAllPosts() {
return this.repo.find({relations: ['author']}); return this.repo.find({relations: ['author']});
@@ -64,11 +80,26 @@ export class PostsService {
return `This action returns a #${id} post`; return `This action returns a #${id} post`;
} }
update(id: number, updatePostDto: UpdatePostDto) { async update(id: number, post: UpdatePostDto) {
return `This action updates a #${id} post`; await this.repo.update(id, post);
const updatedPost = await this.repo.findOne({
where: {id},
relations: {
author: true,
}
});
if (updatedPost) {
await this.postsSearchService.update(updatedPost);
return updatedPost;
}
throw new PostNotFoundException(id);
} }
remove(id: number) { async remove(id: number) {
return `This action removes a #${id} post`; const deleteResponse = await this.repo.delete(id);
if (!deleteResponse.affected) {
throw new PostNotFoundException(id);
}
await this.postsSearchService.remove(id);
} }
} }

View File

@@ -1,42 +1,81 @@
import { Injectable } from '@nestjs/common'; import {Injectable} from '@nestjs/common';
import { ElasticsearchService } from '@nestjs/elasticsearch'; import {ElasticsearchService} from '@nestjs/elasticsearch';
import Post from "./entities/post.entity"; import Post from "./entities/post.entity";
import {PostSearchResult} from "./types/postSearchResult.interface"; import {PostSearchResult} from "./types/postSearchResult.interface";
import {PostSearchBody} from "./types/postSearchBody.interface"; import {PostSearchBody} from "./types/postSearchBody.interface";
@Injectable() @Injectable()
export default class PostsSearchService { export default class PostsSearchService {
index = 'posts' index = 'posts';
constructor( constructor(
private readonly elasticsearchService: ElasticsearchService private readonly elasticsearchService: ElasticsearchService
) {} ) {
}
async indexPost(post: Post) { async indexPost(post: Post) {
return this.elasticsearchService.index<PostSearchBody>({ return this.elasticsearchService.index<PostSearchBody>({
index: this.index, index: this.index,
body: { document: {
id: post.id, id: post.id,
title: post.title, title: post.title,
content: post.content, content: post.content,
authorId: post.author.id authorId: post.author.id
} }
}) });
} }
async search(text: string) { async search(text: string) {
const response = await this.elasticsearchService.search<PostSearchResult>({ const result = await this.elasticsearchService.search<PostSearchResult>({
index: this.index, index: this.index,
body: {
query: { query: {
multi_match: { multi_match: {
query: text, query: text,
fields: ['title', 'content'] fields: ['title', 'content'],
} },
} },
} });
})
const hits = body.hits.hits; const hits = result.hits.hits;
return hits.map((item) => item._source); return hits.map((item) => item._source);
} }
async remove(postId: number) {
await this.elasticsearchService.deleteByQuery({
index: this.index,
query: {
match: {
id: postId,
}
}
})
}
async update(post: Post) {
const newBody: PostSearchBody = {
id: post.id,
title: post.title,
content: post.content,
authorId: post.author.id
};
const script = Object.entries(newBody).reduce((result, [key, value]) => {
return `${result} ctx._source.${key}='${value}';`;
}, '');
await this.elasticsearchService.updateByQuery({
index: this.index,
query: {
match: {
id: post.id,
}
},
script: {
source: script
}
});
}
} }

View File

@@ -1,3 +1,4 @@
import {PostSearchBody} from "./postSearchBody.interface";
export interface PostSearchResult { export interface PostSearchResult {
hits: { hits: {