Implement search controler
This commit is contained in:
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user