Add meilisearch

This commit is contained in:
2023-10-27 15:05:18 +02:00
parent e733c6acc8
commit cc4b69ca50
14 changed files with 160 additions and 12229 deletions

View File

@@ -20,3 +20,7 @@ API_KEYS=SCOROTEST,ROBOTO,SCORO
API_KEY_SCORO_TEST=SCOROTEST
API_KEY_ROBOT=ROBOTO
API_KEY_SCORO=SCORO
MEILI_HTTP_ADDR="http://meilisearch:7700"
MEILI_MASTER_KEY="ghvjkgisbgkbgskegblfqbgjkebbhgwkjfb"
# vi: ft=sh

12203
back/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@
"fs": "^0.0.1-security",
"jsdom": "^22.1.0",
"json-logger-service": "^9.0.1",
"meilisearch": "^0.35.0",
"node-fetch": "^2.6.12",
"nodemailer": "^6.9.5",
"opensheetmusicdisplay": "^1.8.4",

View File

@@ -2,9 +2,10 @@ import { Module } from "@nestjs/common";
import { PrismaModule } from "src/prisma/prisma.module";
import { ArtistController } from "./artist.controller";
import { ArtistService } from "./artist.service";
import { SearchModule } from 'src/search/search.module';
@Module({
imports: [PrismaModule],
imports: [PrismaModule, SearchModule],
controllers: [ArtistController],
providers: [ArtistService],
})

View File

@@ -1,15 +1,21 @@
import { Injectable } from "@nestjs/common";
import { Prisma, Artist } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { Injectable } from '@nestjs/common';
import { Prisma, Artist } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { MeiliService } from 'src/search/meilisearch.service';
@Injectable()
export class ArtistService {
constructor(private prisma: PrismaService) {}
constructor(
private prisma: PrismaService,
private search: MeiliService,
) {}
async create(data: Prisma.ArtistCreateInput): Promise<Artist> {
return this.prisma.artist.create({
const ret = await this.prisma.artist.create({
data,
});
await this.search.index('artists').addDocuments([ret]);
return ret;
}
async get(
@@ -42,8 +48,10 @@ export class ArtistService {
}
async delete(where: Prisma.ArtistWhereUniqueInput): Promise<Artist> {
return this.prisma.artist.delete({
const ret = await this.prisma.artist.delete({
where,
});
await this.search.index('artists').deleteDocument(ret.id);
return ret
}
}

View File

@@ -0,0 +1,29 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import MeiliSearch, { DocumentOptions, Settings } from 'meilisearch';
@Injectable()
export class MeiliService extends MeiliSearch implements OnModuleInit {
constructor() {
super({
host: process.env.MEILI_ADDR || 'http://meilisearch:7700',
apiKey: process.env.MEILI_MASTER_KEY,
});
}
async definedIndex(uid: string, opts: Settings) {
let task = await this.createIndex(uid, { primaryKey: 'id' });
await this.waitForTask(task.taskUid);
task = await this.index(uid).updateSettings(opts);
await this.waitForTask(task.taskUid);
}
async onModuleInit() {
await this.definedIndex('songs', {
searchableAttributes: ['name', 'artist'],
filterableAttributes: ['artistId', 'genreId'],
});
await this.definedIndex('artists', {
searchableAttributes: ['name'],
});
}
}

View File

@@ -25,27 +25,23 @@ import { SongController } from "src/song/song.controller";
import { GenreController } from "src/genre/genre.controller";
import { ArtistController } from "src/artist/artist.controller";
@ApiTags("search")
@Controller("search")
@ApiTags('search')
@Controller('search')
@UseGuards(JwtAuthGuard)
export class SearchController {
constructor(private readonly searchService: SearchService) {}
@Get("songs/:query")
@ApiOkResponse({ type: _Song, isArray: true })
@ApiOperation({ description: "Search a song" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@UseGuards(JwtAuthGuard)
@ApiOperation({ description: 'Search a song' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
async searchSong(
@Request() req: any,
@Query("include") include: string,
@Param("query") query: string,
@Param('query') query: string,
@Param('artistId') artistId: number,
): Promise<Song[] | null> {
try {
const ret = await this.searchService.songByGuess(
query,
req.user?.id,
mapInclude(include, req, SongController.includableFields),
);
const ret = await this.searchService.searchSong(query, artistId);
if (!ret.length) throw new NotFoundException();
else return ret;
} catch (error) {

View File

@@ -4,11 +4,12 @@ import { SearchController } from "./search.controller";
import { HistoryModule } from "src/history/history.module";
import { PrismaModule } from "src/prisma/prisma.module";
import { SongService } from "src/song/song.service";
import { MeiliService } from "./meilisearch.service";
@Module({
imports: [PrismaModule, HistoryModule],
controllers: [SearchController],
providers: [SearchService, SongService],
exports: [SearchService],
providers: [SearchService, SongService, MeiliService],
exports: [SearchService, MeiliService],
})
export class SearchModule {}

View File

@@ -1,27 +1,29 @@
import { Injectable } from "@nestjs/common";
import { Artist, Prisma, Song, Genre } from "@prisma/client";
import { HistoryService } from "src/history/history.service";
import { PrismaService } from "src/prisma/prisma.service";
import { Injectable } from '@nestjs/common';
import { Artist, Prisma, Song, Genre } from '@prisma/client';
import { HistoryService } from 'src/history/history.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { MeiliService } from './meilisearch.service';
@Injectable()
export class SearchService {
constructor(
private prisma: PrismaService,
private history: HistoryService,
private search: MeiliService,
) {}
async songByGuess(
query: string,
userID: number,
include?: Prisma.SongInclude,
): Promise<Song[]> {
return this.prisma.song.findMany({
async searchSong(query: string, artistId?: number): Promise<Song[]> {
if (query.length === 0) {
return await this.prisma.song.findMany({
where: {
name: { contains: query, mode: "insensitive" },
artistId,
},
include,
});
}
return (await this.search
.index('songs')
.search(query, { filter: `artistId = ${artistId}` })) as any;
}
async genreByGuess(
query: string,

View File

@@ -1,11 +1,12 @@
import { Module } from "@nestjs/common";
import { SongService } from "./song.service";
import { SongController } from "./song.controller";
import { PrismaModule } from "src/prisma/prisma.module";
import { HistoryModule } from "src/history/history.module";
import { Module } from '@nestjs/common';
import { SongService } from './song.service';
import { SongController } from './song.controller';
import { PrismaModule } from 'src/prisma/prisma.module';
import { HistoryModule } from 'src/history/history.module';
import { SearchModule } from 'src/search/search.module';
@Module({
imports: [PrismaModule, HistoryModule],
imports: [PrismaModule, HistoryModule, SearchModule],
providers: [SongService],
controllers: [SongController],
})

View File

@@ -1,13 +1,17 @@
import { Injectable } from "@nestjs/common";
import { Prisma, Song } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { MeiliService } from "src/search/meilisearch.service";
import { generateSongAssets } from "src/assetsgenerator/generateImages_browserless";
@Injectable()
export class SongService {
// number is the song id
private assetCreationTasks: Map<number, Promise<void>>;
constructor(private prisma: PrismaService) {
constructor(
private prisma: PrismaService,
private search: MeiliService,
) {
this.assetCreationTasks = new Map();
}
@@ -34,9 +38,19 @@ export class SongService {
}
async createSong(data: Prisma.SongCreateInput): Promise<Song> {
return this.prisma.song.create({
const song = await this.prisma.song.create({
data,
});
// Inculde the name of the artist in the song document to make search easier.
const artist = song.artistId
? await this.prisma.artist.findFirst({
where: { id: song.artistId },
})
: null;
await this.search
.index("songs")
.addDocuments([{ ...song, artist: artist?.name }]);
return song;
}
async song(
@@ -69,8 +83,10 @@ export class SongService {
}
async deleteSong(where: Prisma.SongWhereUniqueInput): Promise<Song> {
return this.prisma.song.delete({
const ret = await this.prisma.song.delete({
where,
});
await this.search.index("songs").deleteDocument(ret.id);
return ret;
}
}

View File

@@ -3,6 +3,7 @@ networks:
volumes:
scoro_logs:
meilisearch:
services:
@@ -86,3 +87,12 @@ services:
- "./front/nginx.conf.template.dev:/etc/nginx/templates/default.conf.template:ro"
ports:
- "4567:4567"
meilisearch:
image: getmeili/meilisearch:v1.4
ports:
- "7000:7000"
volumes:
- meilisearch:/meili_data
env_file:
- .env

View File

@@ -3,6 +3,8 @@ networks:
volumes:
scoro_logs:
meilisearch:
db:
services:
back:
@@ -53,3 +55,12 @@ services:
- "back"
env_file:
- .env
meilisearch:
image: getmeili/meilisearch:v1.4
ports:
- "7000:7000"
volumes:
- meilisearch:/meili_data
env_file:
- .env

View File

@@ -5,6 +5,7 @@ networks:
volumes:
db:
scoro_logs:
meilisearch:
services:
@@ -59,3 +60,12 @@ services:
- "back"
env_file:
- .env
meilisearch:
image: getmeili/meilisearch:v1.4
ports:
- "7000:7000"
volumes:
- meilisearch:/meili_data
env_file:
- .env