Skip to content

Commit

Permalink
refactor: shared user types (#5080)
Browse files Browse the repository at this point in the history
* move user to shared definitions

this includes whatever user can have on it, tags, presets and so on

* profiles, mail and others

* fix logic

* yeet

* same as master for now

* tsc fixes

* remove comment

* fix tests

* chore: omit ips

* fix(language): remove some unnecessarily capitalised words in german 1k

* fix(typing): first space sometimes soft locking the website

* perf: speed up settings page loading

* fix: use selected typing speed unit on personal best popup (fehmer) (#5070)

* fix: Use selected typing speed unit on personal best popup

* refactor

* refactor

* test coverage

* use Format in more places

* Make config mockable

* dependency injection

* wip

* fix

* test

* touch

* fix(language): typos in russian_10k.json (kae) (#5082)

* Update russian_10k.json

- fixed typos
- removed duplicates

* - fixed extra typos

* remove duplicates

* fix(language): typos in russian_10k.json

* feat: add copy missed words to result screen (fehmer) (#5086)

* feat: Add copy missed words to result screen

* remove margin

* update icons

---------

Co-authored-by: Miodec <jack@monkeytype.com>

* impr(funbox): add 46 group languages to wikipedia funbox (RealCyGuy) (#5078)

* impr: provide all-time LB results during LB update (fehmer) (#5074)

Try to provide LB results during the LB update. There is a very small time-frame where
already running queries might fail during the update. For now we keep the 503 error in this
cases and monitor how often this happens on production.

* impr(funbox): add ` (grave accent, 96) and ~ (tilde, 126) to specials (#5073)

* impr: add testWords and wordsHistory to copy result stats (#5085)

* feat: add testWords and wordsHistory to copy result stats

* fix

* add fe ts dep

---------

Co-authored-by: Christian Fehmer <fehmer@users.noreply.github.com>
Co-authored-by: Andrey Kuznetsov <akuznetsov@outlook.com>
Co-authored-by: Cyrus Yip <cyruscmyip1@gmail.com>
Co-authored-by: fitzsim <fitzsim@fitzsim.org>
  • Loading branch information
5 people authored Feb 19, 2024
1 parent 06c50de commit 01790d8
Show file tree
Hide file tree
Showing 41 changed files with 516 additions and 410 deletions.
8 changes: 4 additions & 4 deletions backend/__tests__/dal/leaderboards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe("LeaderboardsDal", () => {
});
});

function expectedLbEntry(rank: number, user: MonkeyTypes.User, time: string) {
function expectedLbEntry(rank: number, user: MonkeyTypes.DBUser, time: string) {
const lbBest: SharedTypes.PersonalBest =
user.lbPersonalBests?.time[time].english;

Expand All @@ -178,13 +178,13 @@ function expectedLbEntry(rank: number, user: MonkeyTypes.User, time: string) {

async function createUser(
lbPersonalBests?: MonkeyTypes.LbPersonalBests,
userProperties?: Partial<MonkeyTypes.User>
): Promise<MonkeyTypes.User> {
userProperties?: Partial<MonkeyTypes.DBUser>
): Promise<MonkeyTypes.DBUser> {
const uid = new ObjectId().toHexString();
await UserDal.addUser("User " + uid, uid + "@example.com", uid);

await DB.getDb()
?.collection<MonkeyTypes.User>("users")
?.collection<MonkeyTypes.DBUser>("users")
.updateOne(
{ uid },
{
Expand Down
3 changes: 2 additions & 1 deletion backend/__tests__/dal/result.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ async function createDummyData(
timestamp: number,
tag?: string
): Promise<void> {
const dummyUser: MonkeyTypes.User = {
const dummyUser: MonkeyTypes.DBUser = {
_id: new ObjectId(),
uid,
addedAt: 0,
email: "test@example.com",
Expand Down
15 changes: 9 additions & 6 deletions backend/src/api/controllers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ export async function updateEmail(
}

function getRelevantUserInfo(
user: MonkeyTypes.User
): Partial<MonkeyTypes.User> {
user: MonkeyTypes.DBUser
): Partial<MonkeyTypes.DBUser> {
return _.omit(user, [
"bananas",
"lbPersonalBests",
Expand All @@ -336,7 +336,7 @@ export async function getUser(
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;

let userInfo: MonkeyTypes.User;
let userInfo: MonkeyTypes.DBUser;
try {
userInfo = await UserDAL.getUser(uid, "get user");
} catch (e) {
Expand Down Expand Up @@ -786,7 +786,7 @@ export async function getProfile(
details: profileDetails,
allTimeLbs: alltimelbs,
uid: user.uid,
};
} as SharedTypes.UserProfile;

return new MonkeyResponse("Profile retrieved", profileData);
}
Expand All @@ -811,10 +811,13 @@ export async function updateProfile(
}
});

const profileDetailsUpdates: Partial<MonkeyTypes.UserProfileDetails> = {
const profileDetailsUpdates: Partial<SharedTypes.UserProfileDetails> = {
bio: sanitizeString(bio),
keyboard: sanitizeString(keyboard),
socialProfiles: _.mapValues(socialProfiles, sanitizeString),
socialProfiles: _.mapValues(
socialProfiles,
sanitizeString
) as SharedTypes.UserProfileDetails["socialProfiles"],
};

await UserDAL.updateProfile(uid, profileDetailsUpdates, user.inventory);
Expand Down
5 changes: 4 additions & 1 deletion backend/src/api/routes/quotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ const router = Router();

const checkIfUserIsQuoteMod = checkUserPermissions({
criteria: (user) => {
return !!user.quoteMod;
return (
user.quoteMod === true ||
(typeof user.quoteMod === "string" && user.quoteMod !== "")
);
},
});

Expand Down
2 changes: 1 addition & 1 deletion backend/src/dal/leaderboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export async function update(
leaderboardUpdating[`${language}_${mode}_${mode2}`] = true;
const start1 = performance.now();
const lb = db
.collection<MonkeyTypes.User>("users")
.collection<MonkeyTypes.DBUser>("users")
.aggregate<SharedTypes.LeaderboardEntry>(
[
{
Expand Down
2 changes: 1 addition & 1 deletion backend/src/dal/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export async function addResult(
uid: string,
result: DBResult
): Promise<{ insertedId: ObjectId }> {
let user: MonkeyTypes.User | null = null;
let user: MonkeyTypes.DBUser | null = null;
try {
user = await getUser(uid, "add result");
} catch (e) {
Expand Down
62 changes: 33 additions & 29 deletions backend/src/dal/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ type Result = Omit<
>;

// Export for use in tests
export const getUsersCollection = (): Collection<WithId<MonkeyTypes.User>> =>
db.collection<MonkeyTypes.User>("users");
export const getUsersCollection = (): Collection<MonkeyTypes.DBUser> =>
db.collection<MonkeyTypes.DBUser>("users");

export async function addUser(
name: string,
email: string,
uid: string
): Promise<void> {
const newUserDocument: Partial<MonkeyTypes.User> = {
const newUserDocument: Partial<MonkeyTypes.DBUser> = {
name,
email,
uid,
Expand Down Expand Up @@ -170,7 +170,7 @@ export async function optOutOfLeaderboards(uid: string): Promise<void> {

export async function updateQuoteRatings(
uid: string,
quoteRatings: MonkeyTypes.UserQuoteRatings
quoteRatings: SharedTypes.UserQuoteRatings
): Promise<boolean> {
await getUser(uid, "update quote ratings");

Expand All @@ -191,13 +191,15 @@ export async function updateEmail(
export async function getUser(
uid: string,
stack: string
): Promise<MonkeyTypes.User> {
): Promise<MonkeyTypes.DBUser> {
const user = await getUsersCollection().findOne({ uid });
if (!user) throw new MonkeyError(404, "User not found", stack);
return user;
}

async function findByName(name: string): Promise<MonkeyTypes.User | undefined> {
async function findByName(
name: string
): Promise<MonkeyTypes.DBUser | undefined> {
return (
await getUsersCollection()
.find({ name })
Expand All @@ -220,7 +222,7 @@ export async function isNameAvailable(
export async function getUserByName(
name: string,
stack: string
): Promise<MonkeyTypes.User> {
): Promise<MonkeyTypes.DBUser> {
const user = await findByName(name);
if (!user) throw new MonkeyError(404, "User not found", stack);
return user;
Expand Down Expand Up @@ -284,7 +286,7 @@ export async function removeResultFilterPreset(
export async function addTag(
uid: string,
name: string
): Promise<MonkeyTypes.UserTag> {
): Promise<MonkeyTypes.DBUserTag> {
const user = await getUser(uid, "add tag");

if ((user?.tags?.length ?? 0) >= 15) {
Expand Down Expand Up @@ -315,7 +317,7 @@ export async function addTag(
return toPush;
}

export async function getTags(uid: string): Promise<MonkeyTypes.UserTag[]> {
export async function getTags(uid: string): Promise<MonkeyTypes.DBUserTag[]> {
const user = await getUser(uid, "get tags");

return user.tags ?? [];
Expand Down Expand Up @@ -396,9 +398,11 @@ export async function updateLbMemory(
const user = await getUser(uid, "update lb memory");
if (user.lbMemory === undefined) user.lbMemory = {};
if (user.lbMemory[mode] === undefined) user.lbMemory[mode] = {};
if (user.lbMemory[mode][mode2] === undefined) {
if (user.lbMemory[mode]?.[mode2] === undefined) {
//@ts-expect-error guarded above
user.lbMemory[mode][mode2] = {};
}
//@ts-expect-error guarded above
user.lbMemory[mode][mode2][language] = rank;
await getUsersCollection().updateOne(
{ uid },
Expand All @@ -410,7 +414,7 @@ export async function updateLbMemory(

export async function checkIfPb(
uid: string,
user: MonkeyTypes.User,
user: MonkeyTypes.DBUser,
result: Result
): Promise<boolean> {
const { mode } = result;
Expand Down Expand Up @@ -452,7 +456,7 @@ export async function checkIfPb(

export async function checkIfTagPb(
uid: string,
user: MonkeyTypes.User,
user: MonkeyTypes.DBUser,
result: Result
): Promise<string[]> {
if (user.tags === undefined || user.tags.length === 0) {
Expand All @@ -466,7 +470,7 @@ export async function checkIfTagPb(
return [];
}

const tagsToCheck: MonkeyTypes.UserTag[] = [];
const tagsToCheck: MonkeyTypes.DBUserTag[] = [];
user.tags.forEach((userTag) => {
for (const resultTag of resultTags ?? []) {
if (resultTag === userTag._id.toHexString()) {
Expand Down Expand Up @@ -553,7 +557,7 @@ export async function linkDiscord(
discordId: string,
discordAvatar?: string
): Promise<void> {
const updates: Partial<MonkeyTypes.User> = _.pickBy(
const updates: Partial<MonkeyTypes.DBUser> = _.pickBy(
{ discordId, discordAvatar },
_.identity
);
Expand Down Expand Up @@ -672,7 +676,7 @@ export async function editTheme(uid: string, _id, theme): Promise<void> {

export async function getThemes(
uid: string
): Promise<MonkeyTypes.CustomTheme[]> {
): Promise<MonkeyTypes.DBCustomTheme[]> {
const user = await getUser(uid, "get themes");
return user.customThemes ?? [];
}
Expand Down Expand Up @@ -705,7 +709,7 @@ export async function getStats(

export async function getFavoriteQuotes(
uid
): Promise<MonkeyTypes.User["favoriteQuotes"]> {
): Promise<MonkeyTypes.DBUser["favoriteQuotes"]> {
const user = await getUser(uid, "get favorite quotes");

return user.favoriteQuotes ?? {};
Expand Down Expand Up @@ -789,7 +793,7 @@ export async function recordAutoBanEvent(
recentAutoBanTimestamps.push(now);

//update user, ban if needed
const updateObj: Partial<MonkeyTypes.User> = {
const updateObj: Partial<MonkeyTypes.DBUser> = {
autoBanTimestamps: recentAutoBanTimestamps,
};
let banningUser = false;
Expand All @@ -810,8 +814,8 @@ export async function recordAutoBanEvent(

export async function updateProfile(
uid: string,
profileDetailUpdates: Partial<MonkeyTypes.UserProfileDetails>,
inventory?: MonkeyTypes.UserInventory
profileDetailUpdates: Partial<SharedTypes.UserProfileDetails>,
inventory?: SharedTypes.UserInventory
): Promise<void> {
const profileUpdates = _.omitBy(
flattenObjectDeep(profileDetailUpdates, "profileDetails"),
Expand All @@ -837,14 +841,14 @@ export async function updateProfile(

export async function getInbox(
uid: string
): Promise<MonkeyTypes.User["inbox"]> {
): Promise<MonkeyTypes.DBUser["inbox"]> {
const user = await getUser(uid, "get inventory");
return user.inbox ?? [];
}

type AddToInboxBulkEntry = {
uid: string;
mail: MonkeyTypes.MonkeyMail[];
mail: SharedTypes.MonkeyMail[];
};

export async function addToInboxBulk(
Expand Down Expand Up @@ -876,7 +880,7 @@ export async function addToInboxBulk(

export async function addToInbox(
uid: string,
mail: MonkeyTypes.MonkeyMail[],
mail: SharedTypes.MonkeyMail[],
inboxConfig: SharedTypes.Configuration["users"]["inbox"]
): Promise<void> {
const { enabled, maxMail } = inboxConfig;
Expand All @@ -902,11 +906,11 @@ export async function addToInbox(
}

function buildRewardUpdates(
rewards: MonkeyTypes.AllRewards[],
rewards: SharedTypes.AllRewards[],
inventoryIsNull = false
): UpdateFilter<WithId<MonkeyTypes.User>> {
): UpdateFilter<MonkeyTypes.DBUser> {
let totalXp = 0;
const newBadges: MonkeyTypes.Badge[] = [];
const newBadges: SharedTypes.Badge[] = [];

rewards.forEach((reward) => {
if (reward.type === "xp") {
Expand Down Expand Up @@ -954,7 +958,7 @@ export async function updateInbox(
const mailToReadSet = new Set(mailToRead);
const mailToDeleteSet = new Set(mailToDelete);

const allRewards: MonkeyTypes.AllRewards[] = [];
const allRewards: SharedTypes.AllRewards[] = [];

const newInbox = inbox
.filter((mail) => {
Expand Down Expand Up @@ -988,7 +992,7 @@ export async function updateStreak(
timestamp: number
): Promise<number> {
const user = await getUser(uid, "calculate streak");
const streak: MonkeyTypes.UserStreak = {
const streak: SharedTypes.UserStreak = {
lastResultTimestamp: user.streak?.lastResultTimestamp ?? 0,
length: user.streak?.length ?? 0,
maxLength: user.streak?.maxLength ?? 0,
Expand Down Expand Up @@ -1043,7 +1047,7 @@ export async function setBanned(uid: string, banned: boolean): Promise<void> {

export async function checkIfUserIsPremium(
uid: string,
userInfoOverride?: MonkeyTypes.User
userInfoOverride?: MonkeyTypes.DBUser
): Promise<boolean> {
const user = userInfoOverride ?? (await getUser(uid, "checkIfUserIsPremium"));
const expirationDate = user.premium?.expirationTimestamp;
Expand All @@ -1056,7 +1060,7 @@ export async function checkIfUserIsPremium(
export async function logIpAddress(
uid: string,
ip: string,
userInfoOverride?: MonkeyTypes.User
userInfoOverride?: MonkeyTypes.DBUser
): Promise<void> {
const user = userInfoOverride ?? (await getUser(uid, "logIpAddress"));
const currentIps = user.ips ?? [];
Expand Down
2 changes: 1 addition & 1 deletion backend/src/middlewares/api-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function checkIfUserIsAdmin(): RequestHandler {
* Note that this middleware must be used after authentication in the middleware stack.
*/
function checkUserPermissions(
options: ValidationOptions<MonkeyTypes.User>
options: ValidationOptions<MonkeyTypes.DBUser>
): RequestHandler {
const { criteria, invalidMessage = "You don't have permission to do this." } =
options;
Expand Down
Loading

0 comments on commit 01790d8

Please sign in to comment.