feat: significantly improve global search

Fixes #713
This commit is contained in:
zkldi
2022-06-05 14:50:24 +01:00
parent a0af4b48d5
commit 647ca1409c
3 changed files with 88 additions and 3 deletions
+16
View File
@@ -184,6 +184,22 @@ export async function SearchGamesSongs(search: string, games: Array<Game>) {
return res.flat(1).sort((a, b) => b.__textScore - a.__textScore);
}
export async function SearchGamesSongsCharts(search: string, games: Array<Game>) {
const promises = [];
for (const game of games) {
promises.push(
SearchSpecificGameSongsAndCharts(game, search).then((e) => ({
game,
songs: e.songs,
charts: e.charts,
}))
);
}
return Promise.all(promises);
}
export async function SearchForChartHash(search: string) {
const results = (await Promise.all([
db.charts.bms.findOne({ $or: [{ "data.hashMD5": search }, { "data.hashSHA256": search }] }),
@@ -1,7 +1,17 @@
import { Router } from "express";
import { SearchAllGamesSongs, SearchForChartHash, SearchUsersRegExp } from "lib/search/search";
import { SYMBOL_TACHI_API_AUTH } from "lib/constants/tachi";
import {
SearchFolders,
SearchForChartHash,
SearchGamesSongsCharts,
SearchUsersRegExp,
} from "lib/search/search";
import { TachiConfig } from "lib/setup/config";
import { RequireBokutachi } from "server/middleware/type-require";
import { IsString } from "utils/misc";
import { GetAllUserRivals, GetUserPlayedGPTs } from "utils/user";
import type { FilterQuery } from "mongodb";
import type { FolderDocument, Game, PublicUserDocument, SongDocument } from "tachi-common";
const router: Router = Router({ mergeParams: true });
@@ -20,17 +30,56 @@ router.get("/", async (req, res) => {
});
}
const [users, songs] = await Promise.all([
const userID = req[SYMBOL_TACHI_API_AUTH].userID;
let filter: FilterQuery<FolderDocument & PublicUserDocument & SongDocument> = {};
let relevantGames: Array<Game> = TachiConfig.GAMES;
if (userID !== null) {
// if the requesting user exists, and they've set this param,
// only return info related to games they've played.
if (IsString(req.query.hasPlayedGame)) {
const gpts = await GetUserPlayedGPTs(userID);
// @hack This is a bit lazy. We should really be filtering the user stuff
// on GPTs with an $or query.
filter = {
game: { $in: gpts.map((e) => e.game) },
playtype: { $in: gpts.map((e) => e.playtype) },
};
relevantGames = gpts.map((e) => e.game);
}
}
const [users, songChartData, folders] = await Promise.all([
SearchUsersRegExp(req.query.search),
SearchAllGamesSongs(req.query.search),
SearchGamesSongsCharts(req.query.search, relevantGames),
SearchFolders(req.query.search, filter),
]);
const songs = songChartData.map((e) => e.songs.map((song) => ({ game: e.game, ...song })));
const charts = songChartData.map((e) => e.charts.map((chart) => ({ game: e.game, ...chart })));
// @ts-expect-error Handled below -- the field is added by the below for loop.
const usersWithRivalTag: Array<PublicUserDocument & { __isRival: boolean }> = users;
if (userID !== null) {
const rivals = await GetAllUserRivals(userID);
for (const user of usersWithRivalTag) {
user.__isRival = rivals.includes(user.id);
}
}
return res.status(200).json({
success: true,
description: `Searched everything.`,
body: {
users,
songs,
charts,
folders,
},
});
});
+20
View File
@@ -265,3 +265,23 @@ export async function IsRequesterAdmin(request: APITokenDocument) {
return user.authLevel === UserAuthLevels.ADMIN;
}
/**
* Return all the GPTs this userID has played.
*/
export async function GetUserPlayedGPTs(userID: integer) {
const gpts = (await db["game-stats"].find(
{ userID },
{ projection: { game: 1, playtype: 1 } }
)) as Array<Pick<UserGameStats, "game" | "playtype">>;
return gpts;
}
export async function GetAllUserRivals(userID: integer) {
const rivals = (await db["game-settings"].find({ userID }, { projection: { rivals: 1 } }))
.map((e) => e.rivals)
.flat();
return rivals;
}