From a6a96d6a1e4f99fa2a62fc63b6638109490fca02 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 27 Oct 2023 23:01:36 +0200 Subject: [PATCH] Implement search controler --- back/src/search/search.controller.ts | 73 +++++++++-------------- back/src/search/search.service.ts | 86 ++++++++++++++++++---------- docker-compose.dev.yml | 2 - docker-compose.prod.yml | 2 - docker-compose.yml | 2 +- 5 files changed, 83 insertions(+), 82 deletions(-) diff --git a/back/src/search/search.controller.ts b/back/src/search/search.controller.ts index 21197d1..884aac4 100644 --- a/back/src/search/search.controller.ts +++ b/back/src/search/search.controller.ts @@ -1,9 +1,11 @@ import { Controller, + DefaultValuePipe, Get, InternalServerErrorException, NotFoundException, Param, + ParseIntPipe, Query, Request, UseGuards, @@ -25,51 +27,31 @@ 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' }) + @ApiOperation({ description: "Search a song" }) + @ApiUnauthorizedResponse({ description: "Invalid token" }) async searchSong( @Request() req: any, - @Param('query') query: string, - @Param('artistId') artistId: number, - ): Promise { - try { - const ret = await this.searchService.searchSong(query, artistId); - if (!ret.length) throw new NotFoundException(); - else return ret; - } catch (error) { - throw new InternalServerErrorException(); - } - } - - @Get("genres/:query") - @UseGuards(JwtAuthGuard) - @ApiUnauthorizedResponse({ description: "Invalid token" }) - @ApiOkResponse({ type: _Genre, isArray: true }) - @ApiOperation({ description: "Search a genre" }) - async searchGenre( - @Request() req: any, - @Query("include") include: string, @Param("query") query: string, - ): Promise { - try { - const ret = await this.searchService.genreByGuess( - query, - req.user?.id, - mapInclude(include, req, GenreController.includableFields), - ); - if (!ret.length) throw new NotFoundException(); - else return ret; - } catch (error) { - throw new InternalServerErrorException(); - } + @Param("artistId") artistId: number, + @Query("include") include: string, + @Query("skip", new DefaultValuePipe(0), ParseIntPipe) skip: number, + @Query("take", new DefaultValuePipe(20), ParseIntPipe) take: number, + ): Promise { + return await this.searchService.searchSong( + query, + artistId, + mapInclude(include, req, SongController.includableFields), + skip, + take, + ); } @Get("artists/:query") @@ -81,17 +63,14 @@ export class SearchController { @Request() req: any, @Query("include") include: string, @Param("query") query: string, + @Query("skip", new DefaultValuePipe(0), ParseIntPipe) skip: number, + @Query("take", new DefaultValuePipe(20), ParseIntPipe) take: number, ): Promise { - try { - const ret = await this.searchService.artistByGuess( - query, - req.user?.id, - mapInclude(include, req, ArtistController.includableFields), - ); - if (!ret.length) throw new NotFoundException(); - else return ret; - } catch (error) { - throw new InternalServerErrorException(); - } + return await this.searchService.searchArtists( + query, + mapInclude(include, req, ArtistController.includableFields), + skip, + take, + ); } } diff --git a/back/src/search/search.service.ts b/back/src/search/search.service.ts index 7be54f8..5899966 100644 --- a/back/src/search/search.service.ts +++ b/back/src/search/search.service.ts @@ -1,8 +1,8 @@ -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'; +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 { @@ -12,42 +12,68 @@ export class SearchService { private search: MeiliService, ) {} - async searchSong(query: string, artistId?: number): Promise { + async searchSong( + query: string, + artistId?: number, + include?: Prisma.SongInclude, + skip?: number, + take?: number, + ): Promise { if (query.length === 0) { return await this.prisma.song.findMany({ where: { artistId, }, + take, + skip, + include, }); } - return (await this.search - .index('songs') - .search(query, { filter: `artistId = ${artistId}` })) as any; + const ids = ( + await this.search.index("songs").search(query, { + limit: take, + offset: skip, + ...(artistId ? { filter: `artistId = ${artistId}` } : {}), + }) + ).hits.map((x) => x.id); + + return ( + await this.prisma.song.findMany({ + where: { + id: { in: ids }, + }, + include, + }) + ).sort((x) => ids.indexOf(x.id)); } - async genreByGuess( + async searchArtists( query: string, - userID: number, - include?: Prisma.GenreInclude, - ): Promise { - return this.prisma.genre.findMany({ - where: { - name: { contains: query, mode: "insensitive" }, - }, - include, - }); - } - - async artistByGuess( - query: string, - userID: number, include?: Prisma.ArtistInclude, + skip?: number, + take?: number, ): Promise { - return this.prisma.artist.findMany({ - where: { - name: { contains: query, mode: "insensitive" }, - }, - include, - }); + if (query.length === 0) { + return this.prisma.artist.findMany({ + take, + skip, + include, + }); + } + const ids = ( + await this.search.index("artists").search(query, { + limit: take, + offset: skip, + }) + ).hits.map((x) => x.id); + + return ( + await this.prisma.artist.findMany({ + where: { + id: { in: ids }, + }, + include, + }) + ).sort((x) => ids.indexOf(x.id)); } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8dd06c6..9f14f14 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -90,8 +90,6 @@ services: meilisearch: image: getmeili/meilisearch:v1.4 - ports: - - "7000:7000" volumes: - meilisearch:/meili_data env_file: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3e22181..f2ab822 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -58,8 +58,6 @@ services: meilisearch: image: getmeili/meilisearch:v1.4 - ports: - - "7000:7000" volumes: - meilisearch:/meili_data env_file: diff --git a/docker-compose.yml b/docker-compose.yml index bd396cb..0cddfc4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,7 +64,7 @@ services: meilisearch: image: getmeili/meilisearch:v1.4 ports: - - "7000:7000" + - "7700:7700" volumes: - meilisearch:/meili_data env_file: