Implement search controler

This commit is contained in:
2023-10-27 23:01:36 +02:00
parent cc4b69ca50
commit a6a96d6a1e
5 changed files with 83 additions and 82 deletions

View File

@@ -1,9 +1,11 @@
import { import {
Controller, Controller,
DefaultValuePipe,
Get, Get,
InternalServerErrorException, InternalServerErrorException,
NotFoundException, NotFoundException,
Param, Param,
ParseIntPipe,
Query, Query,
Request, Request,
UseGuards, UseGuards,
@@ -25,51 +27,31 @@ import { SongController } from "src/song/song.controller";
import { GenreController } from "src/genre/genre.controller"; import { GenreController } from "src/genre/genre.controller";
import { ArtistController } from "src/artist/artist.controller"; import { ArtistController } from "src/artist/artist.controller";
@ApiTags('search') @ApiTags("search")
@Controller('search') @Controller("search")
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
export class SearchController { export class SearchController {
constructor(private readonly searchService: SearchService) {} constructor(private readonly searchService: SearchService) {}
@Get("songs/:query") @Get("songs/:query")
@ApiOkResponse({ type: _Song, isArray: true }) @ApiOkResponse({ type: _Song, isArray: true })
@ApiOperation({ description: 'Search a song' }) @ApiOperation({ description: "Search a song" })
@ApiUnauthorizedResponse({ description: 'Invalid token' }) @ApiUnauthorizedResponse({ description: "Invalid token" })
async searchSong( async searchSong(
@Request() req: any, @Request() req: any,
@Param('query') query: string,
@Param('artistId') artistId: number,
): Promise<Song[] | null> {
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, @Param("query") query: string,
): Promise<Genre[] | null> { @Param("artistId") artistId: number,
try { @Query("include") include: string,
const ret = await this.searchService.genreByGuess( @Query("skip", new DefaultValuePipe(0), ParseIntPipe) skip: number,
query, @Query("take", new DefaultValuePipe(20), ParseIntPipe) take: number,
req.user?.id, ): Promise<Song[] | null> {
mapInclude(include, req, GenreController.includableFields), return await this.searchService.searchSong(
); query,
if (!ret.length) throw new NotFoundException(); artistId,
else return ret; mapInclude(include, req, SongController.includableFields),
} catch (error) { skip,
throw new InternalServerErrorException(); take,
} );
} }
@Get("artists/:query") @Get("artists/:query")
@@ -81,17 +63,14 @@ export class SearchController {
@Request() req: any, @Request() req: any,
@Query("include") include: string, @Query("include") include: string,
@Param("query") query: string, @Param("query") query: string,
@Query("skip", new DefaultValuePipe(0), ParseIntPipe) skip: number,
@Query("take", new DefaultValuePipe(20), ParseIntPipe) take: number,
): Promise<Artist[] | null> { ): Promise<Artist[] | null> {
try { return await this.searchService.searchArtists(
const ret = await this.searchService.artistByGuess( query,
query, mapInclude(include, req, ArtistController.includableFields),
req.user?.id, skip,
mapInclude(include, req, ArtistController.includableFields), take,
); );
if (!ret.length) throw new NotFoundException();
else return ret;
} catch (error) {
throw new InternalServerErrorException();
}
} }
} }

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from "@nestjs/common";
import { Artist, Prisma, Song, Genre } from '@prisma/client'; import { Artist, Prisma, Song, Genre } from "@prisma/client";
import { HistoryService } from 'src/history/history.service'; import { HistoryService } from "src/history/history.service";
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from "src/prisma/prisma.service";
import { MeiliService } from './meilisearch.service'; import { MeiliService } from "./meilisearch.service";
@Injectable() @Injectable()
export class SearchService { export class SearchService {
@@ -12,42 +12,68 @@ export class SearchService {
private search: MeiliService, private search: MeiliService,
) {} ) {}
async searchSong(query: string, artistId?: number): Promise<Song[]> { async searchSong(
query: string,
artistId?: number,
include?: Prisma.SongInclude,
skip?: number,
take?: number,
): Promise<Song[]> {
if (query.length === 0) { if (query.length === 0) {
return await this.prisma.song.findMany({ return await this.prisma.song.findMany({
where: { where: {
artistId, artistId,
}, },
take,
skip,
include,
}); });
} }
return (await this.search const ids = (
.index('songs') await this.search.index("songs").search(query, {
.search(query, { filter: `artistId = ${artistId}` })) as any; 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, query: string,
userID: number,
include?: Prisma.GenreInclude,
): Promise<Genre[]> {
return this.prisma.genre.findMany({
where: {
name: { contains: query, mode: "insensitive" },
},
include,
});
}
async artistByGuess(
query: string,
userID: number,
include?: Prisma.ArtistInclude, include?: Prisma.ArtistInclude,
skip?: number,
take?: number,
): Promise<Artist[]> { ): Promise<Artist[]> {
return this.prisma.artist.findMany({ if (query.length === 0) {
where: { return this.prisma.artist.findMany({
name: { contains: query, mode: "insensitive" }, take,
}, skip,
include, 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));
} }
} }

View File

@@ -90,8 +90,6 @@ services:
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.4 image: getmeili/meilisearch:v1.4
ports:
- "7000:7000"
volumes: volumes:
- meilisearch:/meili_data - meilisearch:/meili_data
env_file: env_file:

View File

@@ -58,8 +58,6 @@ services:
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.4 image: getmeili/meilisearch:v1.4
ports:
- "7000:7000"
volumes: volumes:
- meilisearch:/meili_data - meilisearch:/meili_data
env_file: env_file:

View File

@@ -64,7 +64,7 @@ services:
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.4 image: getmeili/meilisearch:v1.4
ports: ports:
- "7000:7000" - "7700:7700"
volumes: volumes:
- meilisearch:/meili_data - meilisearch:/meili_data
env_file: env_file: