aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/__tests__/api/controllers/user.spec.ts22
-rw-r--r--backend/__tests__/dal/user.spec.ts40
-rw-r--r--backend/src/api/controllers/quote.ts8
-rw-r--r--backend/src/api/controllers/result.ts2
-rw-r--r--backend/src/api/controllers/user.ts65
-rw-r--r--backend/src/dal/user.ts124
-rw-r--r--backend/src/types/types.d.ts2
7 files changed, 199 insertions, 64 deletions
diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts
index 4086ae5b7..c865f3431 100644
--- a/backend/__tests__/api/controllers/user.spec.ts
+++ b/backend/__tests__/api/controllers/user.spec.ts
@@ -195,7 +195,7 @@ describe("user controller test", () => {
});
});
describe("getTestActivity", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
afterAll(() => {
getUserMock.mockReset();
});
@@ -303,7 +303,7 @@ describe("user controller test", () => {
});
describe("toggle ban", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
const setBannedMock = vi.spyOn(UserDal, "setBanned");
const georgeUserBannedMock = vi.spyOn(GeorgeQueue, "userBanned");
const isAdminMock = vi.spyOn(AdminUuids, "isAdmin");
@@ -340,7 +340,10 @@ describe("user controller test", () => {
.expect(200);
//THEN
- expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban");
+ expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
expect(setBannedMock).toHaveBeenCalledWith(uid, true);
expect(georgeUserBannedMock).toHaveBeenCalledWith("discordId", true);
});
@@ -392,7 +395,10 @@ describe("user controller test", () => {
.expect(200);
//THEN
- expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban");
+ expect(getUserMock).toHaveBeenLastCalledWith(uid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
expect(setBannedMock).toHaveBeenCalledWith(uid, false);
expect(georgeUserBannedMock).toHaveBeenCalledWith("discordId", false);
});
@@ -425,7 +431,7 @@ describe("user controller test", () => {
});
describe("delete user", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
const deleteUserMock = vi.spyOn(UserDal, "deleteUser");
const firebaseDeleteUserMock = vi.spyOn(AuthUtils, "deleteUser");
const deleteAllApeKeysMock = vi.spyOn(ApeKeys, "deleteAllApeKeys");
@@ -537,7 +543,7 @@ describe("user controller test", () => {
});
});
describe("link discord", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
const isDiscordIdAvailableMock = vi.spyOn(UserDal, "isDiscordIdAvailable");
const isStateValidForUserMock = vi.spyOn(
DiscordUtils,
@@ -601,7 +607,7 @@ describe("user controller test", () => {
});
});
describe("getCurrentTestActivity", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
afterEach(() => {
getUserMock.mockReset();
@@ -635,7 +641,7 @@ describe("user controller test", () => {
});
});
describe("getStreak", () => {
- const getUserMock = vi.spyOn(UserDal, "getUser");
+ const getUserMock = vi.spyOn(UserDal, "getPartial");
afterEach(() => {
getUserMock.mockReset();
diff --git a/backend/__tests__/dal/user.spec.ts b/backend/__tests__/dal/user.spec.ts
index 33697319d..9c432e9f4 100644
--- a/backend/__tests__/dal/user.spec.ts
+++ b/backend/__tests__/dal/user.spec.ts
@@ -849,4 +849,44 @@ describe("UserDal", () => {
expect(year2024[93]).toEqual(2);
});
});
+ describe("getPartial", () => {
+ it("should throw for unknown user", async () => {
+ expect(async () =>
+ UserDAL.getPartial("1234", "stack", [])
+ ).rejects.toThrowError("User not found\nStack: stack");
+ });
+
+ it("should get streak", async () => {
+ //GIVEN
+ let user = await UserTestData.createUser({
+ streak: {
+ hourOffset: 1,
+ length: 5,
+ lastResultTimestamp: 4711,
+ maxLength: 23,
+ },
+ });
+
+ //WHEN
+ const partial = await UserDAL.getPartial(user.uid, "streak", ["streak"]);
+
+ //THEN
+ expect(partial).toStrictEqual({
+ _id: user._id,
+ streak: {
+ hourOffset: 1,
+ length: 5,
+ lastResultTimestamp: 4711,
+ maxLength: 23,
+ },
+ });
+ });
+ });
+ describe("updateEmail", () => {
+ it("throws for nonexisting user", async () => {
+ expect(async () =>
+ UserDAL.updateEmail(123, "[email protected]")
+ ).rejects.toThrowError("User not found\nStack: update email");
+ });
+ });
});
diff --git a/backend/src/api/controllers/quote.ts b/backend/src/api/controllers/quote.ts
index e69912fd1..94c210561 100644
--- a/backend/src/api/controllers/quote.ts
+++ b/backend/src/api/controllers/quote.ts
@@ -1,6 +1,6 @@
import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
-import { getUser, updateQuoteRatings } from "../../dal/user";
+import { getPartial, updateQuoteRatings } from "../../dal/user";
import * as ReportDAL from "../../dal/report";
import * as NewQuotesDAL from "../../dal/new-quotes";
import * as QuoteRatingsDAL from "../../dal/quote-ratings";
@@ -21,7 +21,7 @@ export async function getQuotes(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const quoteMod: boolean | undefined | string = (
- await getUser(uid, "get quotes")
+ await getPartial(uid, "get quotes", ["quoteMod"])
).quoteMod;
let quoteModString: string;
if (quoteMod === true) {
@@ -63,7 +63,7 @@ export async function approveQuote(
const { uid } = req.ctx.decodedToken;
const { quoteId, editText, editSource } = req.body;
- const { name } = await getUser(uid, "approve quote");
+ const { name } = await getPartial(uid, "approve quote", ["name"]);
if (!name) {
throw new MonkeyError(500, "Missing name field");
@@ -103,7 +103,7 @@ export async function submitRating(
const { uid } = req.ctx.decodedToken;
const { quoteId, rating, language } = req.body;
- const user = await getUser(uid, "submit rating");
+ const user = await getPartial(uid, "submit rating", ["quoteRatings"]);
const normalizedQuoteId = parseInt(quoteId as string, 10);
const normalizedRating = Math.round(parseInt(rating as string, 10));
diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts
index b9c697d7a..2eca6d360 100644
--- a/backend/src/api/controllers/result.ts
+++ b/backend/src/api/controllers/result.ts
@@ -157,7 +157,7 @@ export async function updateTags(
result.numbers = false;
}
- const user = await UserDAL.getUser(uid, "update tags");
+ const user = await UserDAL.getPartial(uid, "update tags", ["tags"]);
const tagPbs = await UserDAL.checkIfTagPb(uid, user, result);
return new MonkeyResponse("Result tags updated", {
tagPbs,
diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts
index 304cc8008..db574e5cf 100644
--- a/backend/src/api/controllers/user.ts
+++ b/backend/src/api/controllers/user.ts
@@ -91,7 +91,11 @@ export async function sendVerificationEmail(
throw new MonkeyError(400, "Email already verified");
}
- const userInfo = await UserDAL.getUser(uid, "request verification email");
+ const userInfo = await UserDAL.getPartial(uid, "request verification email", [
+ "uid",
+ "name",
+ "email",
+ ]);
if (userInfo.email !== email) {
throw new MonkeyError(
@@ -150,9 +154,10 @@ export async function sendForgotPasswordEmail(
try {
const uid = (await FirebaseAdmin().auth().getUserByEmail(email)).uid;
- const userInfo = await UserDAL.getUser(
+ const userInfo = await UserDAL.getPartial(
uid,
- "request forgot password email"
+ "request forgot password email",
+ ["name"]
);
const link = await FirebaseAdmin()
@@ -179,7 +184,12 @@ export async function deleteUser(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
- const userInfo = await UserDAL.getUser(uid, "delete user");
+ const userInfo = await UserDAL.getPartial(uid, "delete user", [
+ "banned",
+ "name",
+ "email",
+ "discordId",
+ ]);
if (userInfo.banned === true) {
await BlocklistDal.add(userInfo);
@@ -215,7 +225,12 @@ export async function resetUser(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
- const userInfo = await UserDAL.getUser(uid, "reset user");
+ const userInfo = await UserDAL.getPartial(uid, "reset user", [
+ "banned",
+ "discordId",
+ "email",
+ "name",
+ ]);
if (userInfo.banned) {
throw new MonkeyError(403, "Banned users cannot reset their account");
}
@@ -247,7 +262,12 @@ export async function updateName(
const { uid } = req.ctx.decodedToken;
const { name } = req.body;
- const user = await UserDAL.getUser(uid, "update name");
+ const user = await UserDAL.getPartial(uid, "update name", [
+ "name",
+ "banned",
+ "needsToChangeName",
+ "lastNameChange",
+ ]);
if (user.banned) {
throw new MonkeyError(403, "Banned users cannot change their name");
@@ -486,7 +506,10 @@ export async function linkDiscord(
throw new MonkeyError(403, "Invalid user token");
}
- const userInfo = await UserDAL.getUser(uid, "link discord");
+ const userInfo = await UserDAL.getPartial(uid, "link discord", [
+ "banned",
+ "discordId",
+ ]);
if (userInfo.banned) {
throw new MonkeyError(403, "Banned accounts cannot link with Discord");
}
@@ -538,7 +561,10 @@ export async function unlinkDiscord(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
- const userInfo = await UserDAL.getUser(uid, "unlink discord");
+ const userInfo = await UserDAL.getPartial(uid, "unlink discord", [
+ "banned",
+ "discordId",
+ ]);
if (userInfo.banned) {
throw new MonkeyError(403, "Banned accounts cannot unlink Discord");
@@ -822,7 +848,10 @@ export async function updateProfile(
const { uid } = req.ctx.decodedToken;
const { bio, keyboard, socialProfiles, selectedBadgeId } = req.body;
- const user = await UserDAL.getUser(uid, "update user profile");
+ const user = await UserDAL.getPartial(uid, "update user profile", [
+ "banned",
+ "inventory",
+ ]);
if (user.banned) {
throw new MonkeyError(403, "Banned users cannot update their profile");
@@ -908,7 +937,7 @@ export async function setStreakHourOffset(
const { uid } = req.ctx.decodedToken;
const { hourOffset } = req.body;
- const user = await UserDAL.getUser(uid, "update user profile");
+ const user = await UserDAL.getPartial(uid, "update user profile", ["streak"]);
if (
user.streak?.hourOffset !== undefined &&
@@ -927,7 +956,10 @@ export async function toggleBan(
): Promise<MonkeyResponse> {
const { uid } = req.body;
- const user = await UserDAL.getUser(uid, "toggle ban");
+ const user = await UserDAL.getPartial(uid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
const discordId = user.discordId;
const discordIdIsValid = discordId !== undefined && discordId !== "";
@@ -1036,7 +1068,10 @@ export async function getTestActivity(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
- const user = await UserDAL.getUser(uid, "testActivity");
+ const user = await UserDAL.getPartial(uid, "testActivity", [
+ "testActivity",
+ "premium",
+ ]);
const userHasPremium = await UserDAL.checkIfUserIsPremium(uid, user);
if (!premiumFeaturesEnabled) {
@@ -1063,7 +1098,9 @@ export async function getCurrentTestActivity(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
- const user = await UserDAL.getUser(uid, "current test activity");
+ const user = await UserDAL.getPartial(uid, "current test activity", [
+ "testActivity",
+ ]);
const data = generateCurrentTestActivity(user.testActivity);
return new MonkeyResponse("Current test activity data retrieved", data);
}
@@ -1073,7 +1110,7 @@ export async function getStreak(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
- const user = await UserDAL.getUser(uid, "streak");
+ const user = await UserDAL.getPartial(uid, "streak", ["streak"]);
return new MonkeyResponse("Streak data retrieved", user.streak);
}
diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts
index f3c47eb92..aa033c26f 100644
--- a/backend/src/dal/user.ts
+++ b/backend/src/dal/user.ts
@@ -179,8 +179,7 @@ export async function updateQuoteRatings(
uid: string,
quoteRatings: SharedTypes.UserQuoteRatings
): Promise<boolean> {
- await getUser(uid, "update quote ratings");
- await getUsersCollection().updateOne({ uid }, { $set: { quoteRatings } });
+ await updateUser("update quote ratings", { uid }, { $set: { quoteRatings } });
return true;
}
@@ -188,8 +187,8 @@ export async function updateEmail(
uid: string,
email: string
): Promise<boolean> {
- await getUser(uid, "update email"); // To make sure that the user exists
- await getUsersCollection().updateOne({ uid }, { $set: { email } });
+ await updateUser("update email", { uid }, { $set: { email } });
+
return true;
}
@@ -202,6 +201,26 @@ export async function getUser(
return user;
}
+/**
+ * Get user document only containing requested fields
+ * @param uid user id
+ * @param stack stack description used in the error
+ * @param fields list of fields
+ * @returns partial DBUser only containing requested fields
+ * @throws MonkeyError if user does not exist
+ */
+export async function getPartial<K extends MonkeyTypes.DBUserKeys>(
+ uid: string,
+ stack: string,
+ fields: K[]
+): Promise<Pick<MonkeyTypes.DBUser, K>> {
+ const projection = new Map(fields.map((it) => [it, 1]));
+ const results = await getUsersCollection().findOne({ uid }, { projection });
+ if (results === null) throw new MonkeyError(404, "User not found", stack);
+
+ return results;
+}
+
export async function findByName(
name: string
): Promise<MonkeyTypes.DBUser | undefined> {
@@ -247,7 +266,8 @@ export async function addResultFilterPreset(
): Promise<ObjectId> {
// ensure limit not reached
const filtersCount = (
- (await getUser(uid, "Add Result filter")).resultFilterPresets ?? []
+ (await getPartial(uid, "Add Result filter", ["resultFilterPresets"]))
+ .resultFilterPresets ?? []
).length;
if (filtersCount >= maxFiltersPerUser) {
@@ -269,7 +289,9 @@ export async function removeResultFilterPreset(
uid: string,
_id: string
): Promise<void> {
- const user = await getUser(uid, "remove result filter");
+ const user = await getPartial(uid, "remove result filter", [
+ "resultFilterPresets",
+ ]);
const filterId = new ObjectId(_id);
if (
user.resultFilterPresets === undefined ||
@@ -292,7 +314,7 @@ export async function addTag(
uid: string,
name: string
): Promise<MonkeyTypes.DBUserTag> {
- const user = await getUser(uid, "add tag");
+ const user = await getPartial(uid, "add tag", ["tags"]);
if ((user?.tags?.length ?? 0) >= 15) {
throw new MonkeyError(400, "You can only have up to 15 tags");
@@ -323,7 +345,7 @@ export async function addTag(
}
export async function getTags(uid: string): Promise<MonkeyTypes.DBUserTag[]> {
- const user = await getUser(uid, "get tags");
+ const user = await getPartial(uid, "get tags", ["tags"]);
return user.tags ?? [];
}
@@ -333,7 +355,7 @@ export async function editTag(
_id: string,
name: string
): Promise<void> {
- const user = await getUser(uid, "edit tag");
+ const user = await getPartial(uid, "edit tag", ["tags"]);
if (
user.tags === undefined ||
user.tags.filter((t) => t._id.toHexString() === _id).length === 0
@@ -350,7 +372,7 @@ export async function editTag(
}
export async function removeTag(uid: string, _id: string): Promise<void> {
- const user = await getUser(uid, "remove tag");
+ const user = await getPartial(uid, "remove tag", ["tags"]);
if (
user.tags === undefined ||
user.tags.filter((t) => t._id.toHexString() === _id).length === 0
@@ -367,7 +389,7 @@ export async function removeTag(uid: string, _id: string): Promise<void> {
}
export async function removeTagPb(uid: string, _id: string): Promise<void> {
- const user = await getUser(uid, "remove tag pb");
+ const user = await getPartial(uid, "remove tag pb", ["tags"]);
if (
user.tags === undefined ||
user.tags.filter((t) => t._id.toHexString() === _id).length === 0
@@ -400,7 +422,7 @@ export async function updateLbMemory(
language: string,
rank: number
): Promise<void> {
- const user = await getUser(uid, "update lb memory");
+ const user = await getPartial(uid, "update lb memory", ["lbMemory"]);
if (user.lbMemory === undefined) user.lbMemory = {};
if (user.lbMemory[mode] === undefined) user.lbMemory[mode] = {};
if (user.lbMemory[mode]?.[mode2] === undefined) {
@@ -419,7 +441,7 @@ export async function updateLbMemory(
export async function checkIfPb(
uid: string,
- user: MonkeyTypes.DBUser,
+ user: Pick<MonkeyTypes.DBUser, "personalBests" | "lbPersonalBests">,
result: Result
): Promise<boolean> {
const { mode } = result;
@@ -462,7 +484,7 @@ export async function checkIfPb(
export async function checkIfTagPb(
uid: string,
- user: MonkeyTypes.DBUser,
+ user: Pick<MonkeyTypes.DBUser, "tags">,
result: Result
): Promise<string[]> {
if (user.tags === undefined || user.tags.length === 0) {
@@ -511,8 +533,8 @@ export async function checkIfTagPb(
}
export async function resetPb(uid: string): Promise<void> {
- await getUser(uid, "reset pb");
- await getUsersCollection().updateOne(
+ await updateUser(
+ "reset pb",
{ uid },
{
$set: {
@@ -579,16 +601,15 @@ export async function linkDiscord(
}
export async function unlinkDiscord(uid: string): Promise<void> {
- await getUser(uid, "unlink discord");
-
- await getUsersCollection().updateOne(
+ await updateUser(
+ "unlink discord",
{ uid },
{ $unset: { discordId: "", discordAvatar: "" } }
);
}
export async function incrementBananas(uid: string, wpm): Promise<void> {
- const user = await getUser(uid, "increment bananas");
+ const user = await getPartial(uid, "increment bananas", ["personalBests"]);
let best60: number | undefined;
const personalBests60 = user.personalBests?.time["60"];
@@ -644,7 +665,7 @@ export async function addTheme(
uid: string,
theme
): Promise<{ _id: ObjectId; name: string }> {
- const user = await getUser(uid, "add theme");
+ const user = await getPartial(uid, "add theme", ["customThemes"]);
if ((user.customThemes ?? []).length >= 10) {
throw new MonkeyError(409, "Too many custom themes");
@@ -671,7 +692,7 @@ export async function addTheme(
}
export async function removeTheme(uid: string, _id): Promise<void> {
- const user = await getUser(uid, "remove theme");
+ const user = await getPartial(uid, "remove theme", ["customThemes"]);
if (themeDoesNotExist(user.customThemes, _id)) {
throw new MonkeyError(404, "Custom theme not found");
@@ -687,7 +708,7 @@ export async function removeTheme(uid: string, _id): Promise<void> {
}
export async function editTheme(uid: string, _id, theme): Promise<void> {
- const user = await getUser(uid, "edit theme");
+ const user = await getPartial(uid, "edit theme", ["customThemes"]);
if (themeDoesNotExist(user.customThemes, _id)) {
throw new MonkeyError(404, "Custom Theme not found");
@@ -710,7 +731,7 @@ export async function editTheme(uid: string, _id, theme): Promise<void> {
export async function getThemes(
uid: string
): Promise<MonkeyTypes.DBCustomTheme[]> {
- const user = await getUser(uid, "get themes");
+ const user = await getPartial(uid, "get themes", ["customThemes"]);
return user.customThemes ?? [];
}
@@ -719,7 +740,7 @@ export async function getPersonalBests(
mode: string,
mode2?: string
): Promise<SharedTypes.PersonalBest> {
- const user = await getUser(uid, "get personal bests");
+ const user = await getPartial(uid, "get personal bests", ["personalBests"]);
if (mode2 !== undefined) {
return user.personalBests?.[mode]?.[mode2];
@@ -731,7 +752,11 @@ export async function getPersonalBests(
export async function getStats(
uid: string
): Promise<Record<string, number | undefined>> {
- const user = await getUser(uid, "get stats");
+ const user = await getPartial(uid, "get stats", [
+ "startedTests",
+ "completedTests",
+ "timeTyping",
+ ]);
return {
startedTests: user.startedTests,
@@ -743,7 +768,7 @@ export async function getStats(
export async function getFavoriteQuotes(
uid
): Promise<MonkeyTypes.DBUser["favoriteQuotes"]> {
- const user = await getUser(uid, "get favorite quotes");
+ const user = await getPartial(uid, "get favorite quotes", ["favoriteQuotes"]);
return user.favoriteQuotes ?? {};
}
@@ -754,7 +779,7 @@ export async function addFavoriteQuote(
quoteId: string,
maxQuotes: number
): Promise<void> {
- const user = await getUser(uid, "add favorite quote");
+ const user = await getPartial(uid, "add favorite quote", ["favoriteQuotes"]);
if (user.favoriteQuotes) {
if (user.favoriteQuotes[language]?.includes(quoteId)) {
@@ -790,7 +815,9 @@ export async function removeFavoriteQuote(
language: string,
quoteId: string
): Promise<void> {
- const user = await getUser(uid, "remove favorite quote");
+ const user = await getPartial(uid, "remove favorite quote", [
+ "favoriteQuotes",
+ ]);
if (!user.favoriteQuotes?.[language]?.includes(quoteId)) {
return;
@@ -807,7 +834,10 @@ export async function recordAutoBanEvent(
maxCount: number,
maxHours: number
): Promise<boolean> {
- const user = await getUser(uid, "record auto ban event");
+ const user = await getPartial(uid, "record auto ban event", [
+ "banned",
+ "autoBanTimestamps",
+ ]);
let ret = false;
@@ -875,7 +905,7 @@ export async function updateProfile(
export async function getInbox(
uid: string
): Promise<MonkeyTypes.DBUser["inbox"]> {
- const user = await getUser(uid, "get inventory");
+ const user = await getPartial(uid, "get inbox", ["inbox"]);
return user.inbox ?? [];
}
@@ -984,7 +1014,7 @@ export async function updateInbox(
mailToRead: string[],
mailToDelete: string[]
): Promise<void> {
- const user = await getUser(uid, "update inbox");
+ const user = await getPartial(uid, "update inbox", ["inbox", "inventory"]);
const inbox = user.inbox ?? [];
@@ -1024,7 +1054,7 @@ export async function updateStreak(
uid: string,
timestamp: number
): Promise<number> {
- const user = await getUser(uid, "calculate streak");
+ const user = await getPartial(uid, "calculate streak", ["streak"]);
const streak: SharedTypes.UserStreak = {
lastResultTimestamp: user.streak?.lastResultTimestamp ?? 0,
length: user.streak?.length ?? 0,
@@ -1080,14 +1110,16 @@ export async function setBanned(uid: string, banned: boolean): Promise<void> {
export async function checkIfUserIsPremium(
uid: string,
- userInfoOverride?: MonkeyTypes.DBUser
+ userInfoOverride?: Pick<MonkeyTypes.DBUser, "premium">
): Promise<boolean> {
const premiumFeaturesEnabled = (await getCachedConfiguration(true)).users
.premium.enabled;
if (premiumFeaturesEnabled !== true) {
return false;
}
- const user = userInfoOverride ?? (await getUser(uid, "checkIfUserIsPremium"));
+ const user =
+ userInfoOverride ??
+ (await getPartial(uid, "checkIfUserIsPremium", ["premium"]));
const expirationDate = user.premium?.expirationTimestamp;
if (expirationDate === undefined) return false;
@@ -1098,9 +1130,10 @@ export async function checkIfUserIsPremium(
export async function logIpAddress(
uid: string,
ip: string,
- userInfoOverride?: MonkeyTypes.DBUser
+ userInfoOverride?: Pick<MonkeyTypes.DBUser, "ips">
): Promise<void> {
- const user = userInfoOverride ?? (await getUser(uid, "logIpAddress"));
+ const user =
+ userInfoOverride ?? (await getPartial(uid, "logIpAddress", ["ips"]));
const currentIps = user.ips ?? [];
const ipIndex = currentIps.indexOf(ip);
if (ipIndex !== -1) {
@@ -1112,3 +1145,20 @@ export async function logIpAddress(
}
await getUsersCollection().updateOne({ uid }, { $set: { ips: currentIps } });
}
+
+/**
+ * Update user document. Requires the user to exist
+ * @param stack stack description used in the error
+ * @param filter user filter
+ * @param update update document
+ * @throws MonkeyError if user does not exist
+ */
+async function updateUser(
+ stack: string,
+ filter: { uid: string },
+ update: UpdateFilter<MonkeyTypes.DBUser>
+): Promise<void> {
+ const result = await getUsersCollection().updateOne(filter, update);
+ if (result.matchedCount !== 1)
+ throw new MonkeyError(404, "User not found", stack);
+}
diff --git a/backend/src/types/types.d.ts b/backend/src/types/types.d.ts
index 97a519669..94d132f2e 100644
--- a/backend/src/types/types.d.ts
+++ b/backend/src/types/types.d.ts
@@ -42,6 +42,8 @@ declare namespace MonkeyTypes {
testActivity?: SharedTypes.CountByYearAndDay;
};
+ type DBUserKeys = keyof DBUser;
+
type DBCustomTheme = WithObjectId<SharedTypes.CustomTheme>;
type DBUserTag = WithObjectId<SharedTypes.UserTag>;