mirror of
				https://github.com/monkeytypegame/monkeytype.git
				synced 2025-10-31 03:08:29 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			404 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			404 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;
 |