diff --git a/docker-compose.yaml b/docker-compose.yaml index 8942c85..70eb528 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -76,6 +76,27 @@ services: networks: - social-network + elasticsearch: + container_name: social-es + image: docker.elastic.co/elasticsearch/elasticsearch:7.11.0 + environment: + - node.name=elasticsearch + - cluster.name=es-docker-cluster + - discovery.seed_hosts=elasticsearch + - cluster.initial_master_nodes=elasticsearch + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + ulimits: + memlock: + soft: -1 + hard: -1 + ports: + - 9200:9200 + volumes: + - /data/elasticsearch/ + networks: + - social-network + networks: social-network: driver: bridge diff --git a/package.json b/package.json index a754dbf..63f979d 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ "author": "trananh22112001@gmail.com", "license": "ISC", "dependencies": { + "@elastic/elasticsearch": "^7.11.0", "@nestjs/common": "^7.0.5", "@nestjs/config": "^2.2.0", "@nestjs/core": "^7.0.5", "@nestjs/cqrs": "^9.0.1", + "@nestjs/elasticsearch": "^7.1", "@nestjs/platform-express": "^7.0.5", "@nestjs/swagger": "^4.4.0", "@nestjs/testing": "^7.0.5", diff --git a/src/article/application/events/event.module.ts b/src/article/application/events/event.module.ts index c79c4d7..1f277e2 100644 --- a/src/article/application/events/event.module.ts +++ b/src/article/application/events/event.module.ts @@ -6,6 +6,7 @@ import { UserEntity } from "../../../user/core/entities/user.entity"; import { ArticleEntity, BlockEntity } from "../../core"; import { CommentEntity } from "../../core/entities/comment.entity"; import { EventHandlers } from "."; +import { ElasticSearchModule } from "../../../elastic-search/elastic-search.module"; @Module({ imports: [ @@ -14,6 +15,7 @@ import { EventHandlers } from "."; READ_CONNECTION ), CqrsModule, + ElasticSearchModule, ], providers: [...EventHandlers], controllers: [], diff --git a/src/article/application/events/handlers/index.ts b/src/article/application/events/handlers/index.ts index e1a19dd..cee276a 100644 --- a/src/article/application/events/handlers/index.ts +++ b/src/article/application/events/handlers/index.ts @@ -4,5 +4,7 @@ export * from "./article-deleted.handler"; export * from "./article-favorited.handler"; export * from "./article-unfavorited.handler"; +export * from "./indexing-article.handler"; + export * from "./comment-created.handler"; export * from "./comment-deleted.handler"; diff --git a/src/article/application/events/handlers/indexing-article.handler.ts b/src/article/application/events/handlers/indexing-article.handler.ts new file mode 100644 index 0000000..8130a74 --- /dev/null +++ b/src/article/application/events/handlers/indexing-article.handler.ts @@ -0,0 +1,19 @@ +import { IEventHandler } from "@nestjs/cqrs"; +import { EventsHandler } from "@nestjs/cqrs/dist/decorators/events-handler.decorator"; +import { ArticleEntity } from "../../../core"; +import { SearchService } from "../../../../elastic-search/elastic-search.service"; + +export class IndexingArticleEvent { + constructor(public readonly articles: ArticleEntity[]) {} +} + +@EventsHandler(IndexingArticleEvent) +export class IndexingArticleEventHandler + implements IEventHandler +{ + constructor(private readonly elasticSearch: SearchService) {} + + async handle({ articles }: IndexingArticleEvent) { + await this.elasticSearch.bulkIndexArticles(articles); + } +} diff --git a/src/article/application/events/index.ts b/src/article/application/events/index.ts index b22e778..4e55602 100644 --- a/src/article/application/events/index.ts +++ b/src/article/application/events/index.ts @@ -6,6 +6,7 @@ import { ArticleUpdatedEventHandler, CommentCreatedEventHandler, CommentDeletedEventHandler, + IndexingArticleEventHandler, } from "./handlers"; export * from "./handlers"; @@ -19,4 +20,6 @@ export const EventHandlers = [ ArticleUnFavoritedEventHandler, CommentCreatedEventHandler, CommentDeletedEventHandler, + + IndexingArticleEventHandler, ]; diff --git a/src/article/application/projections/elastic-search-article.projection.ts b/src/article/application/projections/elastic-search-article.projection.ts new file mode 100644 index 0000000..209f7bb --- /dev/null +++ b/src/article/application/projections/elastic-search-article.projection.ts @@ -0,0 +1,31 @@ +import { Injectable } from "@nestjs/common"; +import { EventBus } from "@nestjs/cqrs"; +import { ConsumerService } from "../../../rabbitmq/consumer.service"; +import { ES_ARTICLE_QUEUE } from "../../../rabbitmq/rabbitmq.constants"; +import { IMessage, IProjection } from "../../core"; +import { MessageType } from "../../core/enums/article.enum"; +import { IndexingArticleEvent } from "../events"; + +@Injectable() +export class ElasticSearchArticleProjection implements IProjection { + constructor( + private readonly consumer: ConsumerService, + private readonly eventBus: EventBus + ) {} + + async handle() { + await this.consumer.consume(ES_ARTICLE_QUEUE, (msg: IMessage) => { + this.handleMessage(msg); + }); + } + + private async handleMessage({ type, payload }: IMessage) { + switch (type) { + case MessageType.INDEXING_ARTICLE: + this.eventBus.publish(new IndexingArticleEvent(payload.articles)); + break; + default: + break; + } + } +} diff --git a/src/article/application/projections/index.ts b/src/article/application/projections/index.ts index 7064b22..799b525 100644 --- a/src/article/application/projections/index.ts +++ b/src/article/application/projections/index.ts @@ -1 +1,2 @@ export * from "./article.projection"; +export * from "./elastic-search-article.projection"; diff --git a/src/article/application/queries/handlers/index.ts b/src/article/application/queries/handlers/index.ts index fd47847..a63665f 100644 --- a/src/article/application/queries/handlers/index.ts +++ b/src/article/application/queries/handlers/index.ts @@ -3,3 +3,5 @@ export * from "./find-feed-article.handler"; export * from "./find-one-article.handler"; export * from "./find-comments.handler"; + +export * from "./search-article.handler"; diff --git a/src/article/application/queries/handlers/indexing-article.handler.ts b/src/article/application/queries/handlers/indexing-article.handler.ts new file mode 100644 index 0000000..9e5c750 --- /dev/null +++ b/src/article/application/queries/handlers/indexing-article.handler.ts @@ -0,0 +1,49 @@ +import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { READ_CONNECTION } from "../../../../configs"; +import { PublisherService } from "../../../../rabbitmq/publisher.service"; +import { ES_ARTICLE_QUEUE } from "../../../../rabbitmq/rabbitmq.constants"; +import { ArticleEntity, MessageType } from "../../../core"; + +export class IndexingArticleQuery { + constructor() {} +} + +@QueryHandler(IndexingArticleQuery) +export class IndexingArticleQueryHandler + implements IQueryHandler +{ + constructor( + @InjectRepository(ArticleEntity, READ_CONNECTION) + private readonly articleRepository: Repository, + + private readonly publisher: PublisherService + ) {} + + async execute(_: IndexingArticleQuery): Promise { + const articles = await this.articleRepository.find({ + relations: ["author", "blocks"], + }); + + const chunkArticles = this.chunkArray(articles, 100); + + chunkArticles.forEach((articles: ArticleEntity[]) => { + this.publisher.publish(ES_ARTICLE_QUEUE, { + type: MessageType.INDEXING_ARTICLE, + payload: { articles }, + }); + }); + } + + private chunkArray(array: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; + + for (let i = 0; i < array.length; i += chunkSize) { + const chunk = array.slice(i, i + chunkSize); + chunks.push(chunk); + } + + return chunks; + } +} diff --git a/src/article/application/queries/handlers/search-article.handler.ts b/src/article/application/queries/handlers/search-article.handler.ts new file mode 100644 index 0000000..171cc65 --- /dev/null +++ b/src/article/application/queries/handlers/search-article.handler.ts @@ -0,0 +1,32 @@ +import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; +import { ElasticsearchService } from "@nestjs/elasticsearch"; + +import { SearchArticleQuery } from "../impl"; +import { IArticleSearchResult } from "../../../core"; + +@QueryHandler(SearchArticleQuery) +export class SearchArticleQueryHandler + implements IQueryHandler +{ + constructor(private readonly elasticsearchService: ElasticsearchService) {} + + async execute({ query }: SearchArticleQuery): Promise { + const response = + await this.elasticsearchService.search({ + index: "articles", // TODO: replace by constant + body: { + from: query.limit || 0, + size: query.offset || 10, + query: { + multi_match: { + query: query.search, + fields: ["title", "description", "author", "blocks"], + }, + }, + }, + }); + const hits = response.body.hits.hits; + const articleEs = hits.map((item) => item._source); + return articleEs; + } +} diff --git a/src/article/application/queries/impl/index.ts b/src/article/application/queries/impl/index.ts index a261536..c9576d2 100644 --- a/src/article/application/queries/impl/index.ts +++ b/src/article/application/queries/impl/index.ts @@ -2,4 +2,8 @@ export * from "./find-all.article.query"; export * from "./find-feed-article.query"; export * from "./find-one-article.query"; +export * from "./search-article.query"; + export * from "./find-comments.query"; + +export * from "./search-article.query"; diff --git a/src/article/application/queries/impl/search-article.query.ts b/src/article/application/queries/impl/search-article.query.ts new file mode 100644 index 0000000..d3f60a1 --- /dev/null +++ b/src/article/application/queries/impl/search-article.query.ts @@ -0,0 +1,5 @@ +import { SearchArticleDto } from "../../../core/dto"; + +export class SearchArticleQuery { + constructor(public readonly query: SearchArticleDto) {} +} diff --git a/src/article/application/queries/index.ts b/src/article/application/queries/index.ts index 64680be..f5b6ba7 100644 --- a/src/article/application/queries/index.ts +++ b/src/article/application/queries/index.ts @@ -1,8 +1,10 @@ +import { IndexingArticleQueryHandler } from "./handlers/indexing-article.handler"; import { FindAllArticleQueryHandler, FindCommentQueryHandler, FindFeedArticleQueryHandler, FindOneArticleQueryHandler, + SearchArticleQueryHandler, } from "./handlers"; export * from "./handlers"; @@ -13,4 +15,6 @@ export const QueryHandlers = [ FindFeedArticleQueryHandler, FindOneArticleQueryHandler, FindCommentQueryHandler, + IndexingArticleQueryHandler, + SearchArticleQueryHandler, ]; diff --git a/src/article/application/queries/query.module.ts b/src/article/application/queries/query.module.ts index 3f62b2a..a7f65c8 100644 --- a/src/article/application/queries/query.module.ts +++ b/src/article/application/queries/query.module.ts @@ -10,6 +10,8 @@ import { UserModule } from "../../../user/user.module"; import { ArticleEntity } from "../../core"; import { CommentEntity } from "../../core/entities/comment.entity"; import { ArticleService } from "../services/article.service"; +import { ElasticSearchModule } from "../../../elastic-search/elastic-search.module"; +import { RabbitMqModule } from "../../../rabbitmq/rabbitmq.module"; @Module({ imports: [ @@ -19,6 +21,8 @@ import { ArticleService } from "../services/article.service"; ), UserModule, CqrsModule, + ElasticSearchModule, + RabbitMqModule, ], providers: [ArticleService, ...QueryHandlers], controllers: [], diff --git a/src/article/article.module.ts b/src/article/article.module.ts index d14e523..47b70d7 100644 --- a/src/article/article.module.ts +++ b/src/article/article.module.ts @@ -16,6 +16,7 @@ import { EventModule } from "./application/events/event.module"; import { QueryModule } from "./application/queries/query.module"; import { ArticleService } from "./application/services/article.service"; import { ArticleController } from "./presentation/article.controller"; +import { ElasticSearchArticleProjection } from "./application/projections/elastic-search-article.projection"; @Module({ imports: [ @@ -27,7 +28,11 @@ import { ArticleController } from "./presentation/article.controller"; RabbitMqModule, RedisModule, ], - providers: [ArticleService, ArticleProjection], + providers: [ + ArticleService, + ArticleProjection, + ElasticSearchArticleProjection, + ], controllers: [ArticleController], }) export class ArticleModule implements NestModule { diff --git a/src/article/core/dto/index.ts b/src/article/core/dto/index.ts index 23b3038..eb22ce7 100644 --- a/src/article/core/dto/index.ts +++ b/src/article/core/dto/index.ts @@ -2,3 +2,4 @@ export { CreateArticleDto } from "./create-article.dto"; export { CreateCommentDto } from "./create-comment"; export * from "./article-query"; export * from "./block.dto"; +export * from "./search-article"; diff --git a/src/article/core/dto/search-article.ts b/src/article/core/dto/search-article.ts new file mode 100644 index 0000000..948f107 --- /dev/null +++ b/src/article/core/dto/search-article.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class SearchArticleDto { + @ApiProperty({ required: false }) + limit?: number; + + @ApiProperty({ required: false }) + offset?: number; + + @ApiProperty({ required: true }) + search: string; +} diff --git a/src/article/core/enums/article.enum.ts b/src/article/core/enums/article.enum.ts index 82e977e..e394ee1 100644 --- a/src/article/core/enums/article.enum.ts +++ b/src/article/core/enums/article.enum.ts @@ -6,4 +6,6 @@ export enum MessageType { ARTICLE_UNFAVORITED = "ARTICLE_UNFAVORITED", COMMENT_CREATED = "COMMENT_CREATED", COMMENT_DELETED = "COMMENT_DELETED", + + INDEXING_ARTICLE = "INDEXING_ARTICLE", } diff --git a/src/article/core/interfaces/article.interface.ts b/src/article/core/interfaces/article.interface.ts index 0d0cac3..09ec4e6 100644 --- a/src/article/core/interfaces/article.interface.ts +++ b/src/article/core/interfaces/article.interface.ts @@ -1,7 +1,7 @@ -import { BlockEntity } from "../entities/block.entity"; -import { IBlock } from "./block.interface"; import { ProfileData } from "../../../profile/core/interfaces/profile.interface"; import { IUser } from "../../../user/core/interfaces/user.interface"; +import { BlockEntity } from "../entities/block.entity"; +import { IBlock } from "./block.interface"; // export interface Comment { // id: number; @@ -63,3 +63,20 @@ export interface IComment { article?: IArticle; author?: IUser | ProfileData; } + +interface IArticleElasticSearch { + id: string; + title: string; + description: string; + author: string; + blockText: string; +} + +export interface IArticleSearchResult { + hits: { + total: number; + hits: Array<{ + _source: ArticleData; + }>; + }; +} diff --git a/src/article/core/interfaces/projection.interface.ts b/src/article/core/interfaces/projection.interface.ts index d9468d4..400f062 100644 --- a/src/article/core/interfaces/projection.interface.ts +++ b/src/article/core/interfaces/projection.interface.ts @@ -7,6 +7,7 @@ export interface IMessage { type: MessageType; payload: { article?: ArticleEntity; + articles?: ArticleEntity[]; user?: UserEntity; slug?: string; userId?: number; diff --git a/src/article/presentation/article.controller.ts b/src/article/presentation/article.controller.ts index cab8905..1c899b8 100644 --- a/src/article/presentation/article.controller.ts +++ b/src/article/presentation/article.controller.ts @@ -27,18 +27,21 @@ import { UnFavoriteArticleCommand, UpdateArticleCommand, } from "../application/commands"; -import { ArticleRO, ArticlesRO, CommentsRO } from "../core"; -import { - ArticleFilters, - CreateArticleDto, - CreateCommentDto, -} from "../core/dto"; import { FindAllArticleQuery, FindCommentQuery, FindFeedArticleQuery, FindOneArticleQuery, + SearchArticleQuery, } from "../application/queries"; +import { IndexingArticleQuery } from "../application/queries/handlers/indexing-article.handler"; +import { ArticleRO, ArticlesRO, CommentsRO } from "../core"; +import { + ArticleFilters, + CreateArticleDto, + CreateCommentDto, + SearchArticleDto, +} from "../core/dto"; @ApiBearerAuth() @ApiTags("articles") @@ -49,6 +52,28 @@ export class ArticleController { private readonly queryBus: QueryBus ) {} + @ApiOperation({ summary: "Unfavorite article" }) + @ApiResponse({ + status: 201, + description: "The article has been successfully unfavorited.", + }) + @ApiResponse({ status: 403, description: "Forbidden." }) + @Get("index") + async index() { + return this.queryBus.execute(new IndexingArticleQuery()); + } + + @ApiOperation({ summary: "Unfavorite article" }) + @ApiResponse({ + status: 201, + description: "The article has been successfully unfavorited.", + }) + @ApiResponse({ status: 403, description: "Forbidden." }) + @Get("search") + async search(@Query() query: SearchArticleDto) { + return this.queryBus.execute(new SearchArticleQuery(query)); + } + @ApiOperation({ summary: "Get all articles" }) @ApiResponse({ status: 200, description: "Return all articles." }) @Get() diff --git a/src/elastic-search/elastic-search.module.ts b/src/elastic-search/elastic-search.module.ts new file mode 100644 index 0000000..6438c2c --- /dev/null +++ b/src/elastic-search/elastic-search.module.ts @@ -0,0 +1,20 @@ +import { Module } from "@nestjs/common"; +import { ElasticsearchModule as EsModule } from "@nestjs/elasticsearch"; +import { SearchService } from "./elastic-search.service"; + +@Module({ + imports: [ + EsModule.register({ + cloud: { + id: process.env.ELASTICSEARCH_CLOUD_ID, + }, + auth: { + username: process.env.ELASTICSEARCH_USERNAME, + password: process.env.ELASTICSEARCH_PASSWORD, + }, + }), + ], + providers: [SearchService], + exports: [EsModule, SearchService], +}) +export class ElasticSearchModule {} diff --git a/src/elastic-search/elastic-search.service.ts b/src/elastic-search/elastic-search.service.ts new file mode 100644 index 0000000..56841cf --- /dev/null +++ b/src/elastic-search/elastic-search.service.ts @@ -0,0 +1,102 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { ElasticsearchService } from "@nestjs/elasticsearch"; +import { ArticleEntity, IArticleSearchResult } from "../article/core"; + +@Injectable() +export class SearchService { + private readonly articleIndexes = "articles"; + private readonly articleType = "article"; + + constructor(private readonly elasticsearchService: ElasticsearchService) {} + + async bulkIndexArticles(articles: ArticleEntity[]) { + const body = articles + .map((article) => [ + { index: { _index: this.articleIndexes, _id: article.id } }, + { + id: article.id, + slug: article.slug, + description: article.description, + blocks: article.blocks.map((block) => block.data.text), + tagList: article.tagList, + createdAt: article.created, + updatedAt: article.updated, + favoritesCount: article.favoriteCount, + author: { + username: article.author?.username, + bio: article.author?.bio, + image: article.author?.image, + }, + }, + ]) + .reduce((acc, val) => acc.concat(val), []); + + try { + const { body: bulkResponse } = await this.elasticsearchService.bulk({ + refresh: true, // Refresh the index after the bulk operation (for testing purposes) + body, + }); + + if (bulkResponse.errors) { + // Handle errors in bulk indexing, if any + const erroredDocuments = []; + // Iterate through the items and collect errors, if any + bulkResponse.items.forEach((action, i) => { + const operation = Object.keys(action)[0]; + if (action[operation].error) { + erroredDocuments.push({ + index: i, + id: action[operation]._id, + error: action[operation].error, + }); + } + }); + Logger.error("Bulk indexing errors:", erroredDocuments.toString()); + } else { + Logger.log("Bulk indexing successful"); + } + } catch (error) { + Logger.error("Error during bulk indexing:", error); + throw error; + } + } + + async indexArticle(article: ArticleEntity) { + return this.elasticsearchService.index({ + index: this.articleIndexes, + body: { + id: article.id, + slug: article.slug, + description: article.description, + blocks: article.blocks.map((block) => block.data.text), + tagList: article.tagList, + createdAt: article.created, + updatedAt: article.updated, + favoritesCount: article.favoriteCount, + author: { + username: article.author?.username, + bio: article.author?.bio, + image: article.author?.image, + }, + }, + type: this.articleType, + }); + } + + async searchArticles(search: string) { + const response = + await this.elasticsearchService.search({ + index: this.articleIndexes, + body: { + query: { + multi_match: { + query: search, + fields: ["title", "description", "author", "blocks.data.text"], + }, + }, + }, + }); + const hits = response.body.hits.hits; + return hits.map((item) => item._source); + } +} diff --git a/src/main.ts b/src/main.ts index 753cd4e..81f7cfb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,16 +6,23 @@ import { json, urlencoded } from "express"; import * as os from "os"; import { ApplicationModule } from "./app.module"; -import { ArticleProjection } from "./article/application/projections"; -import { ProfileProjection } from "./profile/application/projections"; +import { + ArticleProjection, + ElasticSearchArticleProjection, +} from "./article/application/projections"; import { UserProjection } from "./user/application/projections"; +import { ProfileProjection } from "./profile/application/projections"; async function executeProjection(app: INestApplication) { const articleProjection = app.get(ArticleProjection); + const elasticSearchArticleProjection = app.get( + ElasticSearchArticleProjection + ); const userProjection = app.get(UserProjection); const profileProjection = app.get(ProfileProjection); await articleProjection.handle(); + await elasticSearchArticleProjection.handle(); await userProjection.handle(); await profileProjection.handle(); } diff --git a/src/rabbitmq/publisher.service.ts b/src/rabbitmq/publisher.service.ts index 08fb2ac..8eb6a22 100644 --- a/src/rabbitmq/publisher.service.ts +++ b/src/rabbitmq/publisher.service.ts @@ -5,6 +5,8 @@ import { ARTICLE_DL_ROUTE_KEY, ARTICLE_QUEUE, ARTICLE_ROUTE_KEY, + ES_ARTICLE_QUEUE, + ES_ARTICLE_ROUTE_KEY, PROFILE_DL_ROUTE_KEY, PROFILE_QUEUE, PROFILE_ROUTE_KEY, @@ -95,6 +97,25 @@ export class PublisherService { `Message type: ${message.type} sent to exchange ${RABBIT_EXCHANGE} with route key ${PROFILE_ROUTE_KEY}` ); break; + case ES_ARTICLE_QUEUE: + await this.channel.bindQueue( + ES_ARTICLE_QUEUE, + RABBIT_EXCHANGE, + ES_ARTICLE_ROUTE_KEY + ); + this.channel.publish( + RABBIT_EXCHANGE, + ES_ARTICLE_ROUTE_KEY, + Buffer.from(JSON.stringify(message)), + { + persistent: true, + } + ); + + Logger.log( + `Message type: ${message.type} sent to exchange ${RABBIT_EXCHANGE} with route key ${ES_ARTICLE_ROUTE_KEY}` + ); + break; default: break; } diff --git a/src/rabbitmq/rabbitmq.constants.ts b/src/rabbitmq/rabbitmq.constants.ts index 63cf24c..a9f958e 100644 --- a/src/rabbitmq/rabbitmq.constants.ts +++ b/src/rabbitmq/rabbitmq.constants.ts @@ -4,6 +4,7 @@ export const RABBIT_MQ_CONNECTION = "amqp://localhost:15672/"; export const RABBIT_EXCHANGE = "SOCIAL"; export const ARTICLE_ROUTE_KEY = "ARTICLE_ROUTE_KEY"; +export const ES_ARTICLE_ROUTE_KEY = "ES_ARTICLE_ROUTE_KEY"; export const USER_ROUTE_KEY = "USER_ROUTE_KEY"; export const PROFILE_ROUTE_KEY = "PROFILECLE_ROUTE_KEY"; @@ -11,6 +12,9 @@ export const ARTICLE_QUEUE = "ARTICLE_QUEUE"; export const USER_QUEUE = "USER_QUEUE"; export const PROFILE_QUEUE = "PROFILE_QUEUE"; +// elastic +export const ES_ARTICLE_QUEUE = "ES_ARTICLE_QUEUE"; + // dead letter export const RABBIT_DL_EXCHANGE = "SOCIAL_DL"; diff --git a/yarn.lock b/yarn.lock index 7bd1875..dd58cba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -293,6 +293,16 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@elastic/elasticsearch@^7.11.0": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@elastic/elasticsearch/-/elasticsearch-7.17.12.tgz#b37f15c8e6a1952ad899e5a9ac3ad0824bf1d0a4" + integrity sha512-iypJDSzlnA1dXcw6H77qy0pH1Et8Nrw/TA6UHA7ECjKwZ9iBxZKS3WgOX0KgtL6GWdmTQWhGEha7k+ELVHygjQ== + dependencies: + debug "^4.3.1" + hpagent "^0.1.1" + ms "^2.1.3" + secure-json-parse "^2.4.0" + "@ioredis/commands@^1.1.1": version "1.2.0" resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" @@ -562,6 +572,11 @@ dependencies: uuid "8.3.2" +"@nestjs/elasticsearch@^7.1": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/elasticsearch/-/elasticsearch-7.1.0.tgz#ba77a11d419b9aa542252a83b6490ef8c458fed4" + integrity sha512-3ixmu9MkTh0DS+LKAKcWHLyf/1DPQTXoy+aVClVI14DJQU208oHR3V0e9klApC+GXCYW+BDhNReh4HRyekjTrw== + "@nestjs/mapped-types@0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-0.4.1.tgz#e7fe038f0bdda7b8f858fa79ca8516b8f9069b1a" @@ -2685,6 +2700,11 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +hpagent@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-0.1.2.tgz#cab39c66d4df2d4377dbd212295d878deb9bdaa9" + integrity sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ== + html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -4020,7 +4040,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: +ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5048,6 +5068,11 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" +secure-json-parse@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"