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 {
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<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,
): Promise<Genre[] | null> {
try {
const ret = await this.searchService.genreByGuess(
@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<Song[] | null> {
return await this.searchService.searchSong(
query,
req.user?.id,
mapInclude(include, req, GenreController.includableFields),
artistId,
mapInclude(include, req, SongController.includableFields),
skip,
take,
);
if (!ret.length) throw new NotFoundException();
else return ret;
} catch (error) {
throw new InternalServerErrorException();
}
}
@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<Artist[] | null> {
try {
const ret = await this.searchService.artistByGuess(
return await this.searchService.searchArtists(
query,
req.user?.id,
mapInclude(include, req, ArtistController.includableFields),
skip,
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 { 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<Song[]> {
async searchSong(
query: string,
artistId?: number,
include?: Prisma.SongInclude,
skip?: number,
take?: number,
): Promise<Song[]> {
if (query.length === 0) {
return await this.prisma.song.findMany({
where: {
artistId,
},
});
}
return (await this.search
.index('songs')
.search(query, { filter: `artistId = ${artistId}` })) as any;
}
async genreByGuess(
query: string,
userID: number,
include?: Prisma.GenreInclude,
): Promise<Genre[]> {
return this.prisma.genre.findMany({
where: {
name: { contains: query, mode: "insensitive" },
},
take,
skip,
include,
});
}
const ids = (
await this.search.index("songs").search(query, {
limit: take,
offset: skip,
...(artistId ? { filter: `artistId = ${artistId}` } : {}),
})
).hits.map((x) => x.id);
async artistByGuess(
return (
await this.prisma.song.findMany({
where: {
id: { in: ids },
},
include,
})
).sort((x) => ids.indexOf(x.id));
}
async searchArtists(
query: string,
userID: number,
include?: Prisma.ArtistInclude,
skip?: number,
take?: number,
): Promise<Artist[]> {
if (query.length === 0) {
return this.prisma.artist.findMany({
where: {
name: { contains: query, mode: "insensitive" },
},
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));
}
}

View File

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

View File

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

View File

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