diff --git a/package.json b/package.json index 7bd65f3..c012ce3 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "class-validator": "^0.14.2", "deepmerge": "^4.3.1", "lodash": "^4.17.21", + "meilisearch": "^0.50.0", "mysql2": "^3.14.1", "reflect-metadata": "^0.1.13", "rimraf": "^5.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb39beb..64448e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + meilisearch: + specifier: ^0.50.0 + version: 0.50.0 mysql2: specifier: ^3.14.1 version: 3.14.1 @@ -2744,6 +2747,9 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + meilisearch@0.50.0: + resolution: {integrity: sha512-9IzIkobvnuS18Eg4dq/eJB9W+eXqeLZjNRgq/kKMswSmVYYSQsXqGgSuCA0JkF+o5RwJlwIsieQee6rh313VhA==} + memfs@3.5.3: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} @@ -7316,6 +7322,8 @@ snapshots: media-typer@0.3.0: optional: true + meilisearch@0.50.0: {} + memfs@3.5.3: dependencies: fs-monkey: 1.0.6 diff --git a/src/app.module.ts b/src/app.module.ts index 83f58c6..82d42ba 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,9 @@ import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { AppInterceptor } from '@/modules/core/providers/app.interceptor'; +import { MEILI_CONFIG } from '@/modules/meilisearch/meili.config'; +import { MeiliModule } from '@/modules/meilisearch/meili.module'; + import { content, database } from './config'; import { DEFAULT_VALIDATION_CONFIG } from './modules/content/constants'; @@ -18,6 +21,7 @@ import { DatabaseModule } from './modules/database/database.module'; ContentModule.forRoot(content), CoreModule.forRoot(), DatabaseModule.forRoot(database), + MeiliModule.forRoot(MEILI_CONFIG), ], providers: [ { diff --git a/src/modules/meilisearch/meili.config.ts b/src/modules/meilisearch/meili.config.ts new file mode 100644 index 0000000..a5b05ae --- /dev/null +++ b/src/modules/meilisearch/meili.config.ts @@ -0,0 +1,9 @@ +import { MeiliConfig } from '@/modules/meilisearch/types'; + +export const MEILI_CONFIG = (): MeiliConfig => [ + { + name: 'default', + host: 'http://localhost:7700', + apiKey: 'masterKey', + }, +]; diff --git a/src/modules/meilisearch/meili.module.ts b/src/modules/meilisearch/meili.module.ts new file mode 100644 index 0000000..49202ea --- /dev/null +++ b/src/modules/meilisearch/meili.module.ts @@ -0,0 +1,28 @@ +import { DynamicModule, Module } from '@nestjs/common'; + +import { MeiliService } from '@/modules/meilisearch/meili.service'; +import { MeiliConfig } from '@/modules/meilisearch/types'; +import { createMeiliOptions } from '@/modules/meilisearch/utils'; + +@Module({}) +export class MeiliModule { + static forRoot(configRegister: () => MeiliConfig): DynamicModule { + return { + global: true, + module: MeiliModule, + providers: [ + { + provide: MeiliService, + useFactory: async () => { + const service = new MeiliService( + await createMeiliOptions(configRegister()), + ); + await service.createClients(); + return service; + }, + }, + ], + exports: [MeiliService], + }; + } +} diff --git a/src/modules/meilisearch/meili.service.ts b/src/modules/meilisearch/meili.service.ts new file mode 100644 index 0000000..4b3850d --- /dev/null +++ b/src/modules/meilisearch/meili.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; + +import { isNil } from 'lodash'; +import { MeiliSearch } from 'meilisearch'; + +import { MeiliConfig } from '@/modules/meilisearch/types'; + +@Injectable() +export class MeiliService { + protected options: MeiliConfig; + + protected clients: Map = new Map(); + + constructor(options: MeiliConfig) { + this.options = options; + } + + getOptions() { + return this.options; + } + + async createClients() { + for (const option of this.options) { + this.clients.set(option.name, new MeiliSearch(option)); + } + } + + getClient(name?: string): MeiliSearch { + let key = 'default'; + if (!isNil(name)) { + key = name; + } + if (!this.clients.has(key)) { + throw new Error(`No client found for ${name}`); + } + return this.clients.get(key); + } + + getClients(): Map { + return this.clients; + } +} diff --git a/src/modules/meilisearch/types.ts b/src/modules/meilisearch/types.ts new file mode 100644 index 0000000..4c6f05c --- /dev/null +++ b/src/modules/meilisearch/types.ts @@ -0,0 +1,5 @@ +import { Config } from 'meilisearch'; + +export type MeiliConfig = MeiliOption[]; + +export type MeiliOption = Config & { name: string }; diff --git a/src/modules/meilisearch/utils.ts b/src/modules/meilisearch/utils.ts new file mode 100644 index 0000000..282425d --- /dev/null +++ b/src/modules/meilisearch/utils.ts @@ -0,0 +1,18 @@ +import { MeiliConfig } from '@/modules/meilisearch/types'; + +export const createMeiliOptions = async (config: MeiliConfig): Promise => { + if (config.length < 0) { + return config; + } + let options: MeiliConfig = [...config]; + const names = options.map(({ name }) => name); + if (!names.includes('default')) { + options[0].name = 'default'; + } else if (names.filter((name) => name === 'default').length > 0) { + options = options.reduce( + (o, n) => (o.map(({ name }) => name).includes('default') ? o : [...o, n]), + [], + ); + } + return options; +};