mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-02-26 15:54:35 +08:00
* Fixed typo
* Created method for adding theme in the UserDAO:
* Created function for checking if custom theme object is valid
* Exported the isThemeValid function
* Added controller for adding customTheme
* Created route for adding custom theme
* Created rateLimit for adding customTheme
* Fixed typo
* Fixed incorrect color length
* Added method for removing, getting and editing custom themes on the backend
* Moved validations from controllers to routes and some aesthetic changes in the user dao
* Started working on frontend and some minor changes in the backend
- Commandline support for custom themes
- Allow user to shift to their custom theme using Shift-click
- Updated the backend to be compatible with some changes
- Create a new custom theme for users with old system to prevent their custom theme loss
* Fixed custom theme type in ape and now new custom theme is created if user clicks the custom tab and doesn't already have one
* Fixed ape type issue
* Format html file
* Fixed wrong tab being active
* Created new custom theme edit section
* Fixed bug where user theme would have impact on icons with custom theme
* Update customThemes API
* Updated the custom theme sharing option to work with multiple custom themes
* Started working on the UI for custom theme buttons
* Added DOM event for clicking custom theme buttons
* Updated the updateActiveButton to work with multiple custom themes
* Removed favorite button for themes and fixed bug where double theme buttons were being added
* Fixed bug where preset theme buttons were not appearing if user has applied custom theme on website load and refreshed
* Moved DOM event for sharing custom theme to more appropriate place
* Integrated the save custom theme button with the changes
* Fixed bug with custom theme tab buttons and theme buttons
* Fixed commented div
* Replaced 'sds' with a meaningful message for custom theme buttons
* Integrated the delete button for deleting custom themes and fixed bug where id of newly added custom theme was not set properly
* Integrated the add button and name field for custom themes editing
* Added addCustomThemeWrapper element
I added it before but seems like vscode and other editors can't handle large files
* Removed some debug statements
* Removed some more debug statements
* Used parial types for custom theme. Thanks Bruce
* Removed unnecssary try catch blocks. Thanks Bruce
* Rephrased custom theme API messages
* Set new theme fields explicitly to prevent validtion failures and rephrased API message
* Replaced let with const
* Replaced let with const for _id
* Replaced let with const and used nullish coalescing
* Improved code quality in User DAO
* Strict equality in user DAO
* Moved validation scheme to a variables at the top of file
* Fixed bugs with strict equality checks
* Renamed themeId to themeID for consistency
* Made customThemes a required type in db to remove unnecessary undefined checks
* Uncommented GET API endpoint
* Prevent colorId being updated on custom theme name chnage
* Removed debug log
* Added loader on api calls
* Commenced shift from customThemeIndex to customThemeId
* Added required to themeColors schema
* Temp fix for validation fail for customThemeId
* Changed default value of customThemeId back to ''
* Temp fix for validation fail for customThemeId
* Fixed minor bug
* Fixed bug where account-controller would pass undefined to ThemeController.set
* Created methods in db.ts for adding, deleting and editing custom themes. Created new interface for raw custom themes and renamed ape methods
* Removed repeating code in account-controller
* Removed repeating code in theme-picker
* Removed setThemes in config
* Fixed minor bug
* Removed repeating code in user DAO
* Made custom themes available to registered users only
* Fixed minor bug
* Removed debug log and updated custom theme commands before showing list
* Added popup for confirming custom theme deletion
* Added custom option for random theme
* Minor improvement
* Workaround for local config firing before firebase initalization
* Removed debug log and created workaround for migration
* Added legacy customTheme config field
* Replaced workaround
* Changed put to patch
* Changed put to patch
* Added customTheme field back
* Integrated customTheme into to feature
* Added notifications for users when they access custom theme cmd option without being logged in
* Removed debug logs and comments
* Replaced literals with constant. Thanks Bruception
* Fixed wrong querySelector parameters and reset custom theme colors after deleting a custom theme
* added notification on save
* duplicating object instead of referencing
* Added return type on function
* Fixed wrong notification code
* spreading default config instead of referencing
* added index, psas, configs, presets
* camel_case
* added ape keys, leaderboards, results, quotes
* Modified setCustomTheme
* Modified setCustomThemeId
* Added tip for random themes settings
* Modified setCustomThemeId
* Now load custom theme before account loading
* Added custom theme compatibility for non-logged in users to theme-controller
* Now update tabs and buttons on custom theme config value change and modified boolean checks to use customTheme instead of customThemeId
* Fixed bug
* Refactoring in theme-controller.ts
* Enable custom theme support in commandline for logged out users
* More refactoring in theme-controller.ts
* Added custom theme compatibility for logged out users
* Removed double events in settings.ts and now turn on custom theme upon applying
* Fixed bug and recursive call
* Fixed bug
* Fixed random theme custom option
* Fix jquery wrong syntax
* Readded notification upon custom theme edit
* One notification upon error only
* Change notification type
* New custom themes now have default colors
* Notification on custom theme edit for non-logged in users
* Refresh buttons upon settings load
* missing gitignore
* updated message
* updated message
* setting config to unchanged when logging in to avoid issues with applying db config
* reverted some over complicated code, excessive auth checks
* removed customthemeid from config
* not setting custom theme id
* removed all customthemeid references
* removed commented code
* removed name field
* added edit button
* unused file
* removed popup
* removed add button, removed text
* removed duplicate code
* added simple popup checkbox support
* whitespace
* added custom theme popups
* removed warning when no custom themes were found
* removed add button click handler
* added function to save custom theme
* saving current theme not default
* removed custom theme id from default config
* not creating new theme by default, just applying
* reacting to customThemeColors save
* unnecessary function call
* removed unused code
* small refactor
* spacing
* unnecessary code
* turned off warnings for non null asertion
* showing theme name when randomising customs
* Revert "turned off warnings for non null asertion"
This reverts commit 433e1dc767
.
* optional with default instead
* fixed custom theme colors always loaded on page load
* fixed custom theme buttons not showing up
* fixed various loading issues
* fixed custom theme edit styles
* showing custom in footer, removed unused code
* savaing custom theme colors
fixed typos
* changing theme
* updated custom theme buttons styling
* scaling custom theme buttons on hover
* not updating settings on theme event
* fixed quote id
* only showing custom themes when logged in
* updating save button text depending on auth state
* fixed double notification when trying to save too many custom themes
* fixed custom theme saving when signed out
* removed user check from db
* fixed exception when signed out user tried to open the custom themes command line
* ignoring file when compiling
* typo
* avoiding href errors
* setting href to an existing file
this fixes firefox custom themes not working
* better hex color regex
* spacing
* renamed function
* typo
* destructuring request
* removed unused function
* removed unused code
* removed unused code
* type fix
* removed non capturing group
* saving colors to config before saving custom theme
* encoding in base64
* added handler that can load themes in the old and new format from the url
Co-authored-by: Rizwan Mustafa <rizwanmustafa0000@gmail.com>
Co-authored-by: Rizwan Mustafa <69350358+rizwanmustafa@users.noreply.github.com>
390 lines
11 KiB
JavaScript
390 lines
11 KiB
JavaScript
import _ from "lodash";
|
|
import { isUsernameValid } from "../utils/validation";
|
|
import { updateUserEmail } from "../utils/auth";
|
|
import { checkAndUpdatePb } from "../utils/pb";
|
|
import db from "../init/db";
|
|
import MonkeyError from "../utils/error";
|
|
import { ObjectId } from "mongodb";
|
|
|
|
class UsersDAO {
|
|
static async addUser(name, email, uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (user)
|
|
throw new MonkeyError(409, "User document already exists", "addUser");
|
|
return await db
|
|
.collection("users")
|
|
.insertOne({ name, email, uid, addedAt: Date.now() });
|
|
}
|
|
|
|
static async deleteUser(uid) {
|
|
return await db.collection("users").deleteOne({ uid });
|
|
}
|
|
|
|
static async updateName(uid, name) {
|
|
if (!this.isNameAvailable(name))
|
|
throw new MonkeyError(409, "Username already taken", name);
|
|
let user = await db.collection("users").findOne({ uid });
|
|
if (
|
|
Date.now() - user.lastNameChange < 2592000000 &&
|
|
isUsernameValid(user.name)
|
|
) {
|
|
throw new MonkeyError(409, "You can change your name once every 30 days");
|
|
}
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { name, lastNameChange: Date.now() } });
|
|
}
|
|
|
|
static async clearPb(uid) {
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { personalBests: {}, lbPersonalBests: {} } });
|
|
}
|
|
|
|
static async isNameAvailable(name) {
|
|
const nameDocs = await db
|
|
.collection("users")
|
|
.find({ name })
|
|
.collation({ locale: "en", strength: 1 })
|
|
.limit(1)
|
|
.toArray();
|
|
if (nameDocs.length !== 0) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static async updateQuoteRatings(uid, quoteRatings) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user)
|
|
throw new MonkeyError(404, "User not found", "updateQuoteRatings");
|
|
await db.collection("users").updateOne({ uid }, { $set: { quoteRatings } });
|
|
return true;
|
|
}
|
|
|
|
static async updateEmail(uid, email) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "update email");
|
|
await updateUserEmail(uid, email);
|
|
await db.collection("users").updateOne({ uid }, { $set: { email } });
|
|
return true;
|
|
}
|
|
|
|
static async getUser(uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "get user");
|
|
return user;
|
|
}
|
|
|
|
static async isDiscordIdAvailable(discordId) {
|
|
const user = await db.collection("users").findOne({ discordId });
|
|
return _.isNil(user);
|
|
}
|
|
|
|
static async addTag(uid, name) {
|
|
const _id = new ObjectId();
|
|
await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $push: { tags: { _id, name } } });
|
|
return {
|
|
_id,
|
|
name,
|
|
};
|
|
}
|
|
|
|
static async getTags(uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
// if (!user) throw new MonkeyError(404, "User not found", "get tags");
|
|
return user?.tags ?? [];
|
|
}
|
|
|
|
static async editTag(uid, _id, name) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "edit tag");
|
|
if (
|
|
user.tags === undefined ||
|
|
user.tags.filter((t) => t._id == _id).length === 0
|
|
)
|
|
throw new MonkeyError(404, "Tag not found");
|
|
return await db.collection("users").updateOne(
|
|
{
|
|
uid: uid,
|
|
"tags._id": new ObjectId(_id),
|
|
},
|
|
{ $set: { "tags.$.name": name } }
|
|
);
|
|
}
|
|
|
|
static async removeTag(uid, _id) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "remove tag");
|
|
if (
|
|
user.tags === undefined ||
|
|
user.tags.filter((t) => t._id == _id).length === 0
|
|
)
|
|
throw new MonkeyError(404, "Tag not found");
|
|
return await db.collection("users").updateOne(
|
|
{
|
|
uid: uid,
|
|
"tags._id": new ObjectId(_id),
|
|
},
|
|
{ $pull: { tags: { _id: new ObjectId(_id) } } }
|
|
);
|
|
}
|
|
|
|
static async removeTagPb(uid, _id) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "remove tag pb");
|
|
if (
|
|
user.tags === undefined ||
|
|
user.tags.filter((t) => t._id == _id).length === 0
|
|
)
|
|
throw new MonkeyError(404, "Tag not found");
|
|
return await db.collection("users").updateOne(
|
|
{
|
|
uid: uid,
|
|
"tags._id": new ObjectId(_id),
|
|
},
|
|
{ $set: { "tags.$.personalBests": {} } }
|
|
);
|
|
}
|
|
|
|
static async updateLbMemory(uid, mode, mode2, language, rank) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "update lb memory");
|
|
if (user.lbMemory === undefined) user.lbMemory = {};
|
|
if (user.lbMemory[mode] === undefined) user.lbMemory[mode] = {};
|
|
if (user.lbMemory[mode][mode2] === undefined)
|
|
user.lbMemory[mode][mode2] = {};
|
|
user.lbMemory[mode][mode2][language] = rank;
|
|
return await db.collection("users").updateOne(
|
|
{ uid },
|
|
{
|
|
$set: { lbMemory: user.lbMemory },
|
|
}
|
|
);
|
|
}
|
|
|
|
static async checkIfPb(uid, user, result) {
|
|
const { mode, funbox } = result;
|
|
|
|
if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") {
|
|
return false;
|
|
}
|
|
|
|
if (mode === "quote") {
|
|
return false;
|
|
}
|
|
|
|
let lbpb = user.lbPersonalBests;
|
|
if (!lbpb) lbpb = {};
|
|
|
|
let pb = checkAndUpdatePb(user.personalBests, lbpb, result);
|
|
|
|
if (pb.isPb) {
|
|
await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { personalBests: pb.obj } });
|
|
if (pb.lbObj) {
|
|
await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { lbPersonalBests: pb.lbObj } });
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static async checkIfTagPb(uid, user, result) {
|
|
if (user.tags === undefined || user.tags.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const { mode, tags, funbox } = result;
|
|
|
|
if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") {
|
|
return [];
|
|
}
|
|
|
|
if (mode === "quote") {
|
|
return [];
|
|
}
|
|
|
|
let tagsToCheck = [];
|
|
user.tags.forEach((tag) => {
|
|
tags.forEach((resultTag) => {
|
|
if (resultTag == tag._id) {
|
|
tagsToCheck.push(tag);
|
|
}
|
|
});
|
|
});
|
|
|
|
let ret = [];
|
|
|
|
tagsToCheck.forEach(async (tag) => {
|
|
let tagpb = checkAndUpdatePb(tag.personalBests, undefined, result);
|
|
if (tagpb.isPb) {
|
|
ret.push(tag._id);
|
|
await db
|
|
.collection("users")
|
|
.updateOne(
|
|
{ uid, "tags._id": new ObjectId(tag._id) },
|
|
{ $set: { "tags.$.personalBests": tagpb.obj } }
|
|
);
|
|
}
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
static async resetPb(uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "reset pb");
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { personalBests: {} } });
|
|
}
|
|
|
|
static async updateTypingStats(uid, restartCount, timeTyping) {
|
|
return await db.collection("users").updateOne(
|
|
{ uid },
|
|
{
|
|
$inc: {
|
|
startedTests: restartCount + 1,
|
|
completedTests: 1,
|
|
timeTyping,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
static async linkDiscord(uid, discordId) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "link discord");
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { discordId } });
|
|
}
|
|
|
|
static async unlinkDiscord(uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "unlink discord");
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $set: { discordId: null } });
|
|
}
|
|
|
|
static async incrementBananas(uid, wpm) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user)
|
|
throw new MonkeyError(404, "User not found", "increment bananas");
|
|
|
|
let best60;
|
|
try {
|
|
best60 = Math.max(...user.personalBests.time[60].map((best) => best.wpm));
|
|
} catch (e) {
|
|
best60 = undefined;
|
|
}
|
|
|
|
if (best60 === undefined || wpm >= best60 - best60 * 0.25) {
|
|
//increment when no record found or wpm is within 25% of the record
|
|
return await db
|
|
.collection("users")
|
|
.updateOne({ uid }, { $inc: { bananas: 1 } });
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static themeDoesNotExist(customThemes, id) {
|
|
return (
|
|
(customThemes ?? []).filter((t) => t._id.toString() === id).length === 0
|
|
);
|
|
}
|
|
|
|
static async addTheme(uid, theme) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user) throw new MonkeyError(404, "User not found", "Add custom theme");
|
|
|
|
if ((user.customThemes ?? []).length >= 10)
|
|
throw new MonkeyError(409, "Too many custom themes");
|
|
|
|
const _id = new ObjectId();
|
|
await db.collection("users").updateOne(
|
|
{ uid },
|
|
{
|
|
$push: {
|
|
customThemes: {
|
|
_id,
|
|
name: theme.name,
|
|
colors: theme.colors,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
return {
|
|
_id,
|
|
name: theme.name,
|
|
};
|
|
}
|
|
|
|
static async removeTheme(uid, _id) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user)
|
|
throw new MonkeyError(404, "User not found", "Remove custom theme");
|
|
|
|
if (this.themeDoesNotExist(user.customThemes, _id))
|
|
throw new MonkeyError(404, "Custom theme not found");
|
|
|
|
return await db.collection("users").updateOne(
|
|
{
|
|
uid: uid,
|
|
"customThemes._id": new ObjectId(_id),
|
|
},
|
|
{ $pull: { customThemes: { _id: new ObjectId(_id) } } }
|
|
);
|
|
}
|
|
|
|
static async editTheme(uid, _id, theme) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user)
|
|
throw new MonkeyError(404, "User not found", "Edit custom theme");
|
|
|
|
if (this.themeDoesNotExist(user.customThemes, _id))
|
|
throw new MonkeyError(404, "Custom Theme not found");
|
|
|
|
return await db.collection("users").updateOne(
|
|
{
|
|
uid: uid,
|
|
"customThemes._id": new ObjectId(_id),
|
|
},
|
|
{
|
|
$set: {
|
|
"customThemes.$.name": theme.name,
|
|
"customThemes.$.colors": theme.colors,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
static async getThemes(uid) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (!user)
|
|
throw new MonkeyError(404, "User not found", "Get custom themes");
|
|
return user.customThemes ?? [];
|
|
}
|
|
|
|
static async getPersonalBests(uid, mode, mode2) {
|
|
const user = await db.collection("users").findOne({ uid });
|
|
if (mode2) {
|
|
return user?.personalBests?.[mode]?.[mode2];
|
|
} else {
|
|
return user?.personalBests?.[mode];
|
|
}
|
|
}
|
|
}
|
|
|
|
export default UsersDAO;
|