Seems good?

This commit is contained in:
zkldi
2021-06-26 13:32:09 +01:00
parent 3d57c93722
commit 126edd89e4
4 changed files with 138 additions and 15 deletions
+15 -11
View File
@@ -9,10 +9,11 @@ import {
Playtypes,
SongDocument,
AnyChartDocument,
PrivateUserDocument,
integer,
} from "tachi-common";
import { EscapeStringRegexp } from "../../utils/misc";
import { OMIT_PRIVATE_USER_RETURNS } from "../../utils/user";
import { GetOnlineCutoff, OMIT_PRIVATE_USER_RETURNS } from "../../utils/user";
import { CONF_INFO } from "../setup/config";
const logger = CreateLogCtx(__filename);
@@ -125,18 +126,21 @@ export function SearchSessions(
* aren't allowed spaces in their name. In short, $text is very
* poor at actually matching usernames.
*/
export function SearchUsersRegExp(search: string) {
export function SearchUsersRegExp(search: string, matchOnline = false) {
const regexEsc = EscapeStringRegexp(search.toLowerCase());
return db.users.find(
{
usernameLowercase: { $regex: new RegExp(regexEsc, "u") },
},
{
limit: 25,
projection: OMIT_PRIVATE_USER_RETURNS,
}
);
const matchQuery: FilterQuery<PrivateUserDocument> = {
usernameLowercase: { $regex: new RegExp(regexEsc, "u") },
};
if (matchOnline) {
matchQuery.lastSeen = { $gt: GetOnlineCutoff() };
}
return db.users.find(matchQuery, {
limit: 25,
projection: OMIT_PRIVATE_USER_RETURNS,
});
}
/**
@@ -0,0 +1,77 @@
import t from "tap";
import db from "../../../../../external/mongo/db";
import mockApi from "../../../../../test-utils/mock-api";
import ResetDBState from "../../../../../test-utils/resets";
import { PrivateUserDocument, PublicUserDocument } from "tachi-common";
import { CloseAllConnections } from "../../../../../test-utils/close-connections";
t.test("GET /api/v1/users", (t) => {
t.beforeEach(ResetDBState);
t.test("Should search users if search param is set.", async (t) => {
const res = await mockApi.get("/api/v1/users?search=zkldi");
t.equal(res.body.body.length, 1);
t.equal(res.body.body[0].username, "test_zkldi");
const res2 = await mockApi.get("/api/v1/users?search=nothing");
t.equal(res2.body.body.length, 0);
const res3 = await mockApi.get("/api/v1/users?search=ZklDI");
t.equal(res3.body.body.length, 1);
t.equal(res3.body.body[0].username, "test_zkldi");
t.end();
});
t.test("Should sanitise input for regex stuff.", async (t) => {
const res = await mockApi.get("/api/v1/users?search=.*");
t.equal(res.body.body.length, 0);
t.end();
});
t.test("Should restrict returns to only online users if online is set.", async (t) => {
const res = await mockApi.get("/api/v1/users?online=true");
t.equal(res.body.body.length, 0);
await db.users.insert({
usernameLowercase: "online_dude",
lastSeen: Date.now(),
id: 2,
} as PrivateUserDocument);
const res2 = await mockApi.get("/api/v1/users?online=true");
t.equal(res2.body.body.length, 1);
t.equal(res2.body.body[0].usernameLowercase, "online_dude");
t.end();
});
t.test("Should return users sorted by lastSeen if no arguments are passed.", async (t) => {
await db.users.insert({
usernameLowercase: "online_dude",
lastSeen: Date.now(),
id: 2,
} as PrivateUserDocument);
const res = await mockApi.get("/api/v1/users");
t.equal(res.body.body.length, 2);
t.strictSame(
res.body.body.map((e: PublicUserDocument) => e.usernameLowercase),
["online_dude", "test_zkldi"]
);
t.end();
});
t.end();
});
t.teardown(CloseAllConnections);
@@ -1,17 +1,49 @@
import { Router } from "express";
import db from "../../../../../external/mongo/db";
import { SearchUsersRegExp } from "../../../../../lib/search/search";
import { IsString } from "../../../../../utils/misc";
import { GetOnlineCutoff, OMIT_PRIVATE_USER_RETURNS } from "../../../../../utils/user";
import userIDRouter from "./_userID/router";
const router: Router = Router({ mergeParams: true });
/**
* List Users.
* @param online - Only return online users.
* @param username - Return users similar to this username.
* Search users.
*
* @param online - Restrict returned users to those who are online.
* @param search - Search for users where their name contains this string. If not present, returns
* users sorted by last appearance.
*
* @name GET /api/v1/users
*/
router.get("/", async (req, res) => {
// i dont know yet.
const onlyOnline = !!req.query.online;
let users;
if (req.query.search) {
if (!IsString(req.query.search)) {
return res.status(400).json({
success: false,
description: `Search parameter was invalid.`,
});
}
users = await SearchUsersRegExp(req.query.search!, onlyOnline);
} else {
const query = onlyOnline ? { lastSeen: { $gt: GetOnlineCutoff() } } : {};
users = await db.users.find(query, {
sort: { lastSeen: -1 },
limit: 100,
projection: OMIT_PRIVATE_USER_RETURNS,
});
}
return res.status(200).json({
success: true,
description: `Returned ${users.length} users.`,
body: users,
});
});
router.use("/:userID", userIDRouter);
+10
View File
@@ -176,3 +176,13 @@ export async function GetUsersRanking(stats: UserGameStats) {
outOf: aggRes[0].outOf as integer,
};
}
const FIVE_MINUTES = 1000 * 60 * 5;
/**
* Returns the cutoff point for "being online" in tachi. This means the user
* has made any page request in the past 5 minutes.
*/
export function GetOnlineCutoff() {
return Date.now() - FIVE_MINUTES;
}