| 
					
				 | 
			
			
				@@ -1,6 +1,12 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import config from "config"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import oauth from "oauth"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import axios from "axios"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import bcrypt from "bcrypt"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import sha256 from "sha256"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import CoreClass from "../core"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const { OAuth2 } = oauth; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let UsersModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let MailModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let CacheModule; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -8,6 +14,10 @@ let DBModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let PlaylistsModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let WSModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let MediaModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+let UtilsModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+let ActivitiesModule; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const avatarColors = ["blue", "orange", "green", "purple", "teal"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	// eslint-disable-next-line require-jsdoc 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -27,6 +37,9 @@ class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		WSModule = this.moduleManager.modules.ws; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		CacheModule = this.moduleManager.modules.cache; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		MediaModule = this.moduleManager.modules.media; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		UtilsModule = this.moduleManager.modules.utils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		ActivitiesModule = this.moduleManager.modules.activities; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		PlaylistsModule = this.moduleManager.modules.playlists; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		this.userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		this.dataRequestModel = await DBModule.runJob("GET_MODEL", { modelName: "dataRequest" }); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -34,7 +47,36 @@ class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		this.playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		this.activityModel = await DBModule.runJob("GET_MODEL", { modelName: "activity" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		this.dataRequestEmail = await MailModule.runJob("GET_SCHEMA_ASYNC", { schemaName: "dataRequest" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.dataRequestEmailSchema = await MailModule.runJob("GET_SCHEMA_ASYNC", { schemaName: "dataRequest" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.verifyEmailSchema = await MailModule.runJob("GET_SCHEMA_ASYNC", { schemaName: "verifyEmail" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.sessionSchema = await CacheModule.runJob("GET_SCHEMA", { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			schemaName: "session" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.appUrl = `${config.get("url.secure") ? "https" : "http"}://${config.get("url.host")}`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.redirectUri = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			config.get("apis.github.redirect_uri").length > 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				? config.get("apis.github.redirect_uri") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				: `${this.appUrl}/backend/auth/github/authorize/callback`; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.oauth2 = new OAuth2( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			config.get("apis.github.client"), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			config.get("apis.github.secret"), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"https://github.com/", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"login/oauth/authorize", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"login/oauth/access_token", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// getOAuthAccessToken uses callbacks by default, so make a helper function to turn it into a promise instead 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		this.getOAuthAccessToken = (...args) => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			new Promise((resolve, reject) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				this.oauth2.getOAuthAccessToken(...args, (err, accessToken, refreshToken, results) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					if (err) reject(err); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					else resolve({ accessToken, refreshToken, results }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	/** 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -60,7 +102,7 @@ class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		if (config.get("sendDataRequestEmails")) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			const adminUsers = await UsersModule.userModel.find({ role: "admin" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			const to = adminUsers.map(adminUser => adminUser.email.address); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			await UsersModule.dataRequestEmail(to, userId, "remove"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			await UsersModule.dataRequestEmailSchema(to, userId, "remove"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		// Delete activities 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -87,8 +129,8 @@ class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		const likedPlaylist = await UsersModule.playlistModel.findOne({ createdBy: userId, type: "user-liked" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		const dislikedPlaylist = await UsersModule.playlistModel.findOne({ createdBy: userId, type: "user-disliked" }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		const songsToAdjustRatings = [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			...likedPlaylist.songs.map(({ mediaSource }) => mediaSource), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			...dislikedPlaylist.songs.map(({ mediaSource }) => mediaSource) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			...(likedPlaylist?.songs?.map(({ mediaSource }) => mediaSource) ?? []), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			...(dislikedPlaylist?.songs?.map(({ mediaSource }) => mediaSource) ?? []) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		// Delete playlists created by user 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -127,11 +169,455 @@ class _UsersModule extends CoreClass { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// EXAMPLE_JOB() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// 	return new Promise((resolve, reject) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// 		if (true) resolve({}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// 		else reject(new Error("Nothing changed.")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	// 	}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Tries to verify email from email verification token/code 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.code - email verification token/code 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async VERIFY_EMAIL(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { code } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!code) throw new Error("Invalid code."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const user = await UsersModule.userModel.findOne({ "email.verificationToken": code }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!user) throw new Error("User not found."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user.email.verified) throw new Error("This email is already verified."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.updateOne( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ "email.verificationToken": code }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				$set: { "email.verified": true }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				$unset: { "email.verificationToken": "" } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ runValidators: true } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Handles callback route being accessed, which has data from GitHub during the oauth process 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Will be used to either log the user in, register the user, or link the GitHub account to an existing account 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.code - code we need to use to get the access token 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.state - custom state we may have passed to GitHub during the first step 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.error - error code if an error occured 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.errorDescription - error description if an error occured 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async GITHUB_AUTHORIZE_CALLBACK(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { code, state, error, errorDescription } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (error) throw new Error(errorDescription); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// Tries to get access token. We don't use the refresh token currently 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { accessToken, /* refreshToken, */ results } = await UsersModule.getOAuthAccessToken(code, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			redirect_uri: UsersModule.redirectUri 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!accessToken) throw new Error(results.error_description); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const options = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			headers: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				"User-Agent": "request", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				Authorization: `token ${accessToken}` 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// Gets user data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const githubUserData = await axios.get("https://api.github.com/user", options); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (githubUserData.status !== 200) throw new Error(githubUserData.data.message); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!githubUserData.data.id) throw new Error("Something went wrong, no id."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// If we specified a state in the first step when we redirected the user to GitHub, it was to link a 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// GitHub account to an existing Musare account, so continue with a job specifically for linking the account 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (state) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			return UsersModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				"GITHUB_AUTHORIZE_CALLBACK_LINK", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				{ state, githubId: githubUserData.data.id, accessToken }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const user = await UsersModule.userModel.findOne({ "services.github.id": githubUserData.data.id }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		let userId; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// Refresh access token, though it's pretty useless as it'll probably expire and then be useless, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// and we don't use it afterwards at all 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			user.services.github.access_token = accessToken; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			await user.save(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			userId = user._id; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// Try to register the user. Will throw an error if it's unable to do so or any error occurs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			userId = await UsersModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				"GITHUB_AUTHORIZE_CALLBACK_REGISTER", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				{ githubUserData, accessToken }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// Create session for the userId gotten above, as the user existed or was successfully registered 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const sessionId = await UtilsModule.runJob("GUID", {}, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await CacheModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"HSET", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				table: "sessions", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				key: sessionId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				value: UsersModule.sessionSchema(sessionId, userId) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return { sessionId, userId, redirectUrl: UsersModule.appUrl }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Handles registering the user in the GitHub login/register/link callback/process 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.githubUserData - data we got from the /user API endpoint from GitHub 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.accessToken - access token for the GitHub user 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async GITHUB_AUTHORIZE_CALLBACK_REGISTER(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { githubUserData, accessToken } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		let user; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// Check if username already exists 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		user = await UsersModule.userModel.findOne({ username: new RegExp(`^${githubUserData.data.login}$`, "i") }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user) throw new Error(`An account with that username already exists.`); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// Get emails used for GitHub account 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const githubEmailsData = await axios.get("https://api.github.com/user/emails", { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			headers: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				"User-Agent": "request", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				Authorization: `token ${accessToken}` 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!Array.isArray(githubEmailsData.data)) throw new Error(githubEmailsData.message); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const primaryEmailAddress = githubEmailsData.data.find(emailAddress => emailAddress.primary)?.email; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!primaryEmailAddress) throw new Error("No primary email address found."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		user = await UsersModule.userModel.findOne({ "email.address": primaryEmailAddress }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user && Object.keys(JSON.parse(user.services.github)).length === 0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			throw new Error(`An account with that email address exists, but is not linked to GitHub.`); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user) throw new Error(`An account with that email address already exists.`); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const userId = await UtilsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"GENERATE_RANDOM_STRING", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				length: 12 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const gravatarUrl = await UtilsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_GRAVATAR", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				email: primaryEmailAddress 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const likedSongsPlaylist = await PlaylistsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_USER_PLAYLIST", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				displayName: "Liked Songs", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user-liked" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const dislikedSongsPlaylist = await PlaylistsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_USER_PLAYLIST", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				displayName: "Disliked Songs", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user-disliked" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		user = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			_id: userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			username: githubUserData.data.login, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			name: githubUserData.data.name, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			location: githubUserData.data.location, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			bio: githubUserData.data.bio, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			email: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				primaryEmailAddress, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				verificationToken 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			services: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				github: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					id: githubUserData.data.id, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					access_token: accessToken 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			avatar: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "gravatar", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				url: gravatarUrl 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			likedSongsPlaylist, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			dislikedSongsPlaylist 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.create(user); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.verifyEmailSchema(primaryEmailAddress, githubUserData.data.login, verificationToken); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await ActivitiesModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"ADD_ACTIVITY", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user__joined", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				payload: { message: "Welcome to Musare!" } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			userId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Job to attempt to link a GitHub user to a Musare account 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.state - state we passed to GitHub and got back from GitHub 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.githubId - GitHub user id 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.accessToken - GitHub user access token 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async GITHUB_AUTHORIZE_CALLBACK_LINK(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { state, githubId, accessToken } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		// State is currently the session id (SID), so check if that session (still) exists 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const session = await CacheModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"HGET", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				table: "sessions", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				key: state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!session) throw new Error("Invalid session."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const user = await UsersModule.userModel.findOne({ _id: session.userId }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!user) throw new Error("User not found."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user.services.github && user.services.github.id) throw new Error("Account already has GitHub linked."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { _id: userId } = user; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.updateOne( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ _id: userId }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				$set: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					"services.github": { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+						id: githubId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+						access_token: accessToken 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ runValidators: true } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await CacheModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"PUB", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				channel: "user.linkGithub", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				value: userId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await CacheModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"PUB", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				channel: "user.updated", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				value: { userId } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			redirectUrl: `${UsersModule.appUrl}/settings?tab=security` 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Attempts to register a user 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.email - email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.username - username 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.password - plaintext password 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.recaptcha - recaptcha, if recaptcha is enabled 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async REGISTER(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { username, password, recaptcha } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		let { email } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		email = email.toLowerCase().trim(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (config.get("registrationDisabled") === true) throw new Error("Registration is not allowed at this time."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (Array.isArray(config.get("experimental.registration_email_whitelist"))) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			const experimentalRegistrationEmailWhitelist = config.get("experimental.registration_email_whitelist"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			const anyRegexPassed = experimentalRegistrationEmailWhitelist.find(regex => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				const emailWhitelistRegex = new RegExp(regex); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				return emailWhitelistRegex.test(email); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			if (!anyRegexPassed) throw new Error("Your email is not allowed to register."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!DBModule.passwordValid(password)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			throw new Error("Invalid password. Check if it meets all the requirements."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (config.get("apis.recaptcha.enabled") === true) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			const recaptchaBody = await axios.post("https://www.google.com/recaptcha/api/siteverify", { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				data: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					secret: config.get("apis").recaptcha.secret, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					response: recaptcha 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			if (recaptchaBody.success !== true) throw new Error("Response from recaptcha was not successful."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		let user = await UsersModule.userModel.findOne({ username: new RegExp(`^${username}$`, "i") }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user) throw new Error("A user with that username already exists."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		user = await UsersModule.userModel.findOne({ "email.address": email }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user) throw new Error("A user with that email already exists."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const salt = await bcrypt.genSalt(10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const hash = await bcrypt.hash(sha256(password), salt); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const userId = await UtilsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"GENERATE_RANDOM_STRING", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				length: 12 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const gravatarUrl = await UtilsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_GRAVATAR", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const likedSongsPlaylist = await PlaylistsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_USER_PLAYLIST", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				displayName: "Liked Songs", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user-liked" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const dislikedSongsPlaylist = await PlaylistsModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"CREATE_USER_PLAYLIST", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				displayName: "Disliked Songs", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user-disliked" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		user = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			_id: userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			name: username, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			username, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			email: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				address: email, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				verificationToken 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			services: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				password: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					password: hash 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			avatar: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "initials", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				color: avatarColors[Math.floor(Math.random() * avatarColors.length)], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				url: gravatarUrl 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			likedSongsPlaylist, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			dislikedSongsPlaylist 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.create(user); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.verifyEmailSchema(email, username, verificationToken); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await ActivitiesModule.runJob( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			"ADD_ACTIVITY", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				userId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				type: "user__joined", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				payload: { message: "Welcome to Musare!" } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			this 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		return { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			userId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Attempts to update the email address of a user 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.userId - userId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.email - new email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async UPDATE_EMAIL(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { userId } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		let { email } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		email = email.toLowerCase().trim(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const user = await UsersModule.userModel.findOne({ _id: userId }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!user) throw new Error("User not found."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user.email.address === email) throw new Error("New email can't be the same as your the old email."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const existingUser = UsersModule.userModel.findOne({ "email.address": email }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (existingUser) throw new Error("That email is already in use."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const gravatarUrl = await UtilsModule.runJob("CREATE_GRAVATAR", { email }, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const verificationToken = await UtilsModule.runJob("GENERATE_RANDOM_STRING", { length: 64 }, this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.updateOne( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ _id: userId }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				$set: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					"avatar.url": gravatarUrl, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					"email.address": email, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					"email.verified": false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					"email.verificationToken": verificationToken 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			{ runValidators: true } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.verifyEmailSchema(email, user.username, verificationToken); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * Attempts to update the username of a user 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {object} payload - object that contains the payload 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.userId - userId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @param {string} payload.username - new username 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 * @returns {Promise} - returns a promise (resolve, reject) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	async UPDATE_USERNAME(payload) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const { userId, username } = payload; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const user = await UsersModule.userModel.findOne({ _id: userId }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (!user) throw new Error("User not found."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (user.username === username) throw new Error("New username can't be the same as the old username."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		const existingUser = UsersModule.userModel.findOne({ username: new RegExp(`^${username}$`, "i") }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (existingUser) throw new Error("That username is already in use."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		await UsersModule.userModel.updateOne({ _id: userId }, { $set: { username } }, { runValidators: true }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	// async EXAMPLE_JOB() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	// 	if (true) return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	// 	else throw new Error("Nothing changed."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	// } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 |