diff --git a/api/v1/db/UserManager.js b/api/v1/db/UserManager.js index 166e146..133d93e 100644 --- a/api/v1/db/UserManager.js +++ b/api/v1/db/UserManager.js @@ -285,6 +285,8 @@ class UserManager { lastGuidelinesRead: current_time, privateProfile: false, allowFollowingView: false, + profileHideFollowing: false, + profileHideFollowers: false, is_studio, }); @@ -2059,7 +2061,8 @@ class UserManager { }, { $match: { - "follower.banned": false + "follower.banned": false, + "follower.profileHideFollowing": { $ne: true } } }, { @@ -2147,10 +2150,21 @@ class UserManager { */ async getFollowerCount(username) { const result = await this.users.findOne({username: username}); - + return result.followers; } + /** + * Get the amount of people a user is following + * @param {string} username Username of the user + * @returns {Promise} Amount of people the user is following + */ + async getFollowingCount(username) { + const result = await this.users.findOne({ username: username }); + + return result.following; + } + /** * Send a message * @param {string} receiver ID of the person receiving the message @@ -3511,6 +3525,22 @@ class UserManager { await this.users.updateOne({ username: username }, { $set: { allowFollowingView: toggle } }); } + async setProfileHideFollowing(username, toggle) { + await this.users.updateOne({ username: username }, { $set: { profileHideFollowing: toggle } }); + } + async setProfileHideFollowers(username, toggle) { + await this.users.updateOne({ username: username }, { $set: { profileHideFollowers: toggle } }); + } + + async getProfileHideFollowing(username) { + const result = await this.users.findOne({ username: username }); + return result.profileHideFollowing || false; + } + async getProfileHideFollowers(username) { + const result = await this.users.findOne({ username: username }); + return result.profileHideFollowers || false; + } + /** * Temporarily ban a user * @param {string} username Username of the user diff --git a/api/v1/routes/users/meta/following/getFollowers.js b/api/v1/routes/users/meta/following/getFollowers.js index c31c0a1..12d0cf2 100644 --- a/api/v1/routes/users/meta/following/getFollowers.js +++ b/api/v1/routes/users/meta/following/getFollowers.js @@ -14,20 +14,59 @@ module.exports = (app, utils) => { app.get("/api/v1/users/meta/getfollowers", async function (req, res) { const packet = req.query; - const username = (String(packet.username)).toLowerCase(); + // meant for getting the data + const target = (String(packet.target)).toLowerCase(); const page = utils.handle_page(packet.page); - if (!username) { + if (!target) { utils.error(res, 400, "Missing username"); return; } - - if (!await utils.UserManager.existsByUsername(username)) { + if (!await utils.UserManager.existsByUsername(target)) { utils.error(res, 404, "User not found"); return; } - const followers = await utils.UserManager.getFollowers(username, page, Number(utils.env.PageSize)); + // incase the user hides followers, check if we are the user/a mod + const token = packet.token || ""; + + const login = await utils.UserManager.loginWithToken(token); + let username = login.username; + let loggedIn = login.success; + + const target_data = await utils.UserManager.getUserData(target); + const user_data = await utils.UserManager.getUserData(username); + const isMod = loggedIn ? (user_data.moderator || user_data.admin) : false; + + // if not logged in, or we arent a mod and the current acc is not the target + if (!loggedIn || (!isMod && (username !== target))) { + if (target_data.privateProfile) { + // if its a private profile, error if we arent logged in, the account doesnt let following view their profile, or we arent being followed by them + if (!loggedIn || !target_data.allowFollowingView) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + + const usernameID = await utils.UserManager.getIDByUsername(username); + const isFollowing = await utils.UserManager.isFollowing(target_data.id, usernameID); + if (!isFollowing) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + } + if (await utils.UserManager.getProfileHideFollowers(target)) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "Hidden" }); + return; + } + } + + const followers = await utils.UserManager.getFollowers(target, page, Number(utils.env.PageSize)); res.status(200); res.header("Content-Type", 'application/json'); diff --git a/api/v1/routes/users/meta/following/getFollowing.js b/api/v1/routes/users/meta/following/getFollowing.js new file mode 100644 index 0000000..f75d787 --- /dev/null +++ b/api/v1/routes/users/meta/following/getFollowing.js @@ -0,0 +1,75 @@ +const UserManager = require("../../../../db/UserManager"); + +/** + * @typedef {Object} Utils + * @property {UserManager} UserManager + */ + +/** + * + * @param {any} app Express app + * @param {Utils} utils Utils + */ +module.exports = (app, utils) => { + app.get("/api/v1/users/meta/getfollowing", async function (req, res) { + const packet = req.query; + + // meant for getting the data + const target = (String(packet.target)).toLowerCase(); + const page = utils.handle_page(packet.page); + + if (!target) { + utils.error(res, 400, "Missing username"); + return; + } + if (!await utils.UserManager.existsByUsername(target)) { + utils.error(res, 404, "User not found"); + return; + } + + // incase the user hides following, check if we are the user/a mod + const token = packet.token || ""; + + const login = await utils.UserManager.loginWithToken(token); + let username = login.username; + let loggedIn = login.success; + + const target_data = await utils.UserManager.getUserData(target); + const user_data = await utils.UserManager.getUserData(username); + const isMod = loggedIn ? (user_data.moderator || user_data.admin) : false; + + // if not logged in, or we arent a mod and the current acc is not the target + if (!loggedIn || (!isMod && (username !== target))) { + if (target_data.privateProfile) { + // if its a private profile, error if we arent logged in, the account doesnt let following view their profile, or we arent being followed by them + if (!loggedIn || !target_data.allowFollowingView) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + + const usernameID = await utils.UserManager.getIDByUsername(username); + const isFollowing = await utils.UserManager.isFollowing(target_data.id, usernameID); + if (!isFollowing) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + } + if (await utils.UserManager.getProfileHideFollowing(target)) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "Hidden" }); + return; + } + } + + const following = await utils.UserManager.getFollowing(target, page, Number(utils.env.PageSize)); + + res.status(200); + res.header("Content-Type", 'application/json'); + res.send(following); + }); +} \ No newline at end of file diff --git a/api/v1/routes/users/meta/following/isFollowing.js b/api/v1/routes/users/meta/following/isFollowing.js index bbca6c6..6213475 100644 --- a/api/v1/routes/users/meta/following/isFollowing.js +++ b/api/v1/routes/users/meta/following/isFollowing.js @@ -17,6 +17,7 @@ module.exports = (app, utils) => { const username = (String(packet.username)).toLowerCase(); const target = (String(packet.target)).toLowerCase(); + // check that these accs exist if (!username || !target) { utils.error(res, 400, "Missing username or target"); return; @@ -26,17 +27,84 @@ module.exports = (app, utils) => { utils.error(res, 404, "NotFound"); return; } - if (!await utils.UserManager.existsByUsername(target)) { utils.error(res, 404, "NotFound"); return; } + // incase the users have some privacy setting, check if we are a mod + const token = packet.token || ""; + const login = await utils.UserManager.loginWithToken(token); + let fetcherUsername = login.username; + let loggedIn = login.success; + + // get all of the data for this req + const target_data = await utils.UserManager.getUserData(target); + const user_data = await utils.UserManager.getUserData(username); + const fetcher_data = await utils.UserManager.getUserData(fetcherUsername); + const fetcherIsMod = loggedIn ? (fetcher_data.moderator || fetcher_data.admin) : false; + const usernameID = await utils.UserManager.getIDByUsername(username); const targetID = await utils.UserManager.getIDByUsername(target); + const fetcherID = loggedIn ? await utils.UserManager.getIDByUsername(fetcherUsername) : null; - const isFollowing = await utils.UserManager.isFollowing(usernameID, targetID); + // if not logged in, or we arent a mod + if (!loggedIn || !fetcherIsMod) { + // this is a bit complicated: + // we need to see if the user has a private profile/if we can see that profile. if we can, we need to be able to see who they are following. + // we need to see if the target has a private profile/if we can see that profile. if we can, we need to be able to see their followers. + // see if user is private + if (user_data.privateProfile) { + if (!loggedIn || !user_data.allowFollowingView) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + // people the user follows can see the user's profile + const isFollowing = await utils.UserManager.isFollowing(usernameID, fetcherID); + if (!isFollowing) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + } + // see if user hides their following + if (await utils.UserManager.getProfileHideFollowing(username)) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "Hidden" }); + return; + } + // see if target is private + if (target_data.privateProfile) { + if (!loggedIn || !target_data.allowFollowingView) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + // people the target follows can see the target's profile + const isFollowing = await utils.UserManager.isFollowing(targetID, fetcherID); + if (!isFollowing) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "PrivateProfile" }); + return; + } + } + // see if target hides their followers + if (await utils.UserManager.getProfileHideFollowers(target)) { + res.status(403); + res.header("Content-Type", "application/json"); + res.json({ "error": "Hidden" }); + return; + } + } + // user doesnt hide who they follow, and target doesnt hide their followers + const isFollowing = await utils.UserManager.isFollowing(usernameID, targetID); res.status(200); res.header("Content-Type", 'application/json'); res.send({ following: isFollowing }); diff --git a/api/v1/routes/users/meta/privateFollowSettings.js b/api/v1/routes/users/meta/privateFollowSettings.js new file mode 100644 index 0000000..ac74dfd --- /dev/null +++ b/api/v1/routes/users/meta/privateFollowSettings.js @@ -0,0 +1,29 @@ +module.exports = (app, utils) => { + app.post('/api/v1/users/privateFollowSettings', utils.cors(), async function (req, res) { + const packet = req.body; + + const username = String(packet.username).toLowerCase(); + const token = packet.token; + + const login = await utils.UserManager.loginWithToken(token); + if (!login.success) { + utils.error(res, 401, "Reauthenticate"); + return; + } + + const profileHideFollowing = packet.profileHideFollowing; + const profileHideFollowers = false; // NOTE: Actually, hiding your follower list doesnt really make sense... + + if (typeof profileHideFollowing !== "boolean" || typeof profileHideFollowers !== "boolean") { + utils.error(res, 400, "InvalidBody") + return; + } + + await utils.UserManager.setProfileHideFollowing(username, profileHideFollowing); + await utils.UserManager.setProfileHideFollowers(username, profileHideFollowers); + + res.status(200); + res.header("Content-Type", "application/json"); + res.send({ success: true }); + }); +} \ No newline at end of file diff --git a/api/v1/routes/users/profile.js b/api/v1/routes/users/profile.js index d199be6..8e0f7ec 100644 --- a/api/v1/routes/users/profile.js +++ b/api/v1/routes/users/profile.js @@ -41,6 +41,7 @@ module.exports = (app, utils) => { const privateProfile = target_data.privateProfile; const canFollowingSeeProfile = target_data.allowFollowingView; + const canSeeFollowing = !target_data.profileHideFollowing; let user = { success: false, @@ -54,9 +55,11 @@ module.exports = (app, utils) => { myFeaturedProject: "", myFeaturedProjectTitle: "", followers: 0, + following: 0, canrankup: false, privateProfile, canFollowingSeeProfile, + canSeeFollowing, isFollowing: false, }; @@ -74,13 +77,17 @@ module.exports = (app, utils) => { return; } - if (username !== target && ( - !(user.isFollowing && canFollowingSeeProfile) && !isMod - )) { - res.status(200); - res.header("Content-Type", "application/json"); - res.send(user); - return; + if (username !== target) { + const usernameID = await utils.UserManager.getIDByUsername(username); + const isTargetFollowingUser = await utils.UserManager.isFollowing(targetID, usernameID); + + // if we arent a mod, then error if followers cant see the profile or if they can but you arent a follower + if (!isMod && (!canFollowingSeeProfile || (canFollowingSeeProfile && !isTargetFollowingUser))) { + res.status(200); + res.header("Content-Type", "application/json"); + res.send(user); + return; + } } } @@ -98,6 +105,8 @@ module.exports = (app, utils) => { || (badges.length > 0); // or we have a badge const followers = await utils.UserManager.getFollowerCount(target); + const following = await utils.UserManager.getFollowingCount(target); + const canActuallySeeFollowing = canSeeFollowing || (!canSeeFollowing && ((loggedIn && username === target) || isMod)) const myFeaturedProject = await utils.UserManager.getFeaturedProject(target); const myFeaturedProjectTitle = await utils.UserManager.getFeaturedProjectTitle(target); @@ -116,9 +125,11 @@ module.exports = (app, utils) => { myFeaturedProject, myFeaturedProjectTitle, followers: followers, + following: canActuallySeeFollowing ? following : 0, canrankup: canRequestRankUp && rank !== 1, privateProfile, canFollowingSeeProfile, + canSeeFollowing, isFollowing: user.isFollowing, };