var express = require('express') var router = express.Router() const DB = require("../mongoDB.js"); const Profile = require("../def/profile.js"); const Notifications = require("./../notifications.js"); const { getSessionId, getUserId, getProfileId } = require("./../utils/sessionUtils.js"); DB.getDB.then((DB) => { const profileBelongsToUser = async (profileid, userid) => { const profile = await DB.getProfileCache(profileid); if (!profile) return false; return profile.userid === String(userid); } /** * @swagger * tags: * name: Profiles * description: User profile management */ /** * @swagger * components: * schemas: * Profile: * type: object * properties: * userid: * type: string * description: The ID of the user associated with the profile. * profile: * type: object * properties: * firstName: * type: string * lastName: * type: string * photo: * type: string * location: * type: string * language: * type: string * status: * type: string * description: * type: string * data: * type: object * description: Additional custom data for the profile. * username: * type: string * following: * type: array * items: * type: string * description: List of profile IDs this profile is following. * lastUpdate: * type: string * format: date-time * newsFeedCache: * type: array * items: * type: object * notifications: * type: array * items: * type: object * isGroup: * type: boolean * isCourse: * type: boolean * isPrivate: * type: boolean * isChat: * type: boolean * subscribed: * type: object * description: Users subscribed to this group (if isGroup is true). * pending: * type: object * description: Pending subscription requests for private groups. */ /** * @swagger * /user/mine: * get: * summary: Get all profiles for the logged-in user * tags: [Profiles] * security: * - cookieAuth: [] * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * profiles: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/mine", async (req, res) => { let userid = getUserId(req); let profiles = await DB.getUserProfiles(userid); return res.json({ status: "ok", profiles }); }); /** * @swagger * /user/new: * get: * summary: (DEPRECATED) Create a new profile * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: query * name: content * required: true * schema: * type: object * $ref: '#/components/schemas/Profile' * responses: * 200: * description: OK * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' */ router.get("/new", async (req, res) => { //Deprecated please use route post("/") let profile = { userid: getUserId(req), ...req.query.content }; let profileObj = new Profile(profile); let r = await DB.newProfile(profileObj); return res.json({ status: "ok", ...profileObj.toObj() }); }); /** * @swagger * /user: * post: * summary: Create a new profile * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' * responses: * 200: * description: OK * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' */ router.post("/", async (req, res) => { let profile = { userid: getUserId(req), ...req.body.content }; try { let profileObj = new Profile(profile); let r = await DB.newProfile(profileObj); return res.json({ status: "ok", ...profileObj.toObj() }); } catch (error) { console.error("Error creating profile", error); return res.json({ status: error, }); } }); /** * @swagger * /user/invite: * post: * summary: Invite a new user by email * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * name: * type: string * email: * type: string * format: email * responses: * 200: * description: OK * 400: * description: Bad request */ router.post("/invite", async (req, res) => { try { const userid = getUserId(req); let { name, email } = req.body; // Destructuring for clarity // Validate required fields if (!name || !email) { return res.status(400).json({ status: "Name and email are required" }); } // Validate email format email = email.trim().toLowerCase() const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return res.status(400).json({ status: "Invalid email format" }); } // Create new invitation, this returns a string if failed let r = await DB.newInvitation(userid, name, email); if (r instanceof String) { // Handle failure response from DB.newInvitation return res.status(400).json({ status: r, message: `Failed to send invitation: ${r}` }); } // Handle response from DB.newInvitation // Send email invitation let senderProfile = await DB.getProfile(getProfileId(req)); Notifications.youHaveAnInvitation(name, email, senderProfile); return res.status(200).json({ status: "ok", message: `Invitation sent to ${name} (${email})` }); } catch (error) { console.error("Error during invitation process:", error); return res.status(500).json({ status: "error", message: "Something went wrong, please try again later" }); } }); /** * @swagger * /user/invite/{email}: * get: * summary: Get invitation details for an email * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: email * required: true * schema: * type: string * format: email * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * invitation: * $ref: '#/components/schemas/Profile' * */ router.get("/invite/:email", async (req, res) => { const userid = getUserId(req); const email = req.params.email; //validate email? if (!email) return res.json({ status: "provide valid email" }); let r = await DB.getInvitation(email); if (!r) return res.json({ status: "no invitation found with that email" }); let isUserAlreadyRegistered = await DB.getUser(email); if (isUserAlreadyRegistered && isUserAlreadyRegistered._id) return res.json({ status: "This user is already registered" }); return res.json({ status: "ok", ...r }); }); /** * @swagger * /user/groups: * get: * summary: Get a list of all groups * tags: [Profiles] * security: * - cookieAuth: [] * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * groups: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/groups", async (req, res) => { let groups = await DB.getGroups(); return res.json({ status: "ok", groups }); }); /** * @swagger * /user/groups/following: * get: * summary: Get a list of groups the current profile is following * tags: [Profiles] * security: * - cookieAuth: [] * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * groups: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/groups/following", async (req, res) => { const profileId = getProfileId(req); let groups = await DB.getFollowingGroups(profileId); return res.json({ status: "ok", groups }); }); /** * @swagger * /user/groups: * post: * summary: Create a new group * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' * responses: * 200: * description: OK * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' */ router.post("/groups", async (req, res) => { let profile = { userid: getUserId(req), isGroup: true, ...req.body }; let profileObj = new Profile(profile); DB.newProfile(profileObj) return res.json({ status: "ok", ...profileObj.toObj() }); }); /** * @swagger * /user/courses: * get: * summary: Get a list of all courses * tags: [Profiles] * security: * - cookieAuth: [] * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * groups: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/courses", async (req, res) => { let groups = await DB.getCourses(); return res.json({ status: "ok", groups }); }); /** * @swagger * /user/groups/accept: * post: * summary: Accept a request to join a private group * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * groupid: * type: string * profileid: * type: string * responses: * 200: * description: OK */ router.post("/groups/accept", async (req, res) => { //This function should be called to accept the join request //of an user that attempt to join a private group. const groupid = getProfileId(req); //It needs to have this profile context const groupidBody = req.body.groupid ? DB.ObjectID(req.body.groupid) : undefined; if (groupidBody && groupid != groupidBody && !DB.isOwnerOfGroup(groupid, groupidBody)) { return res.json({ status: "Only group owner can accept new subscribers" }); } const profileAcepted = DB.ObjectID(req.body.profileid); DB.acceptGroupJoinReq(profileAcepted, groupidBody || groupid); //Send Notification to accepted user Notifications.yourGroupRequestAccepted(profileAcepted, groupidBody || groupid) return res.json({ status: "ok" }); }); /** * @swagger * /user/groups/reject: * post: * summary: Reject a request to join a private group * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * groupid: * type: string * profileid: * type: string * responses: * 200: * description: OK */ router.post("/groups/reject", async (req, res) => { //This function should be called to reject the join request //of an user that attempt to join a private group. const groupid = getProfileId(req); //It needs to have this profile context const groupidBody = req.body.groupid ? DB.ObjectID(req.body.groupid) : undefined; if (groupidBody && groupid != groupidBody && !DB.isOwnerOfGroup(groupid, groupidBody)) { return res.json({ status: "Only group owner can reject new subscribers" }); } const profileAcepted = DB.ObjectID(req.body.profileid); DB.rejectGroupJoinReq(profileAcepted, groupidBody || groupid); //Add Notification to rejected user //Notifications.yourGroupHasARequest(profileAcepted, groupidBody || groupid) return res.json({ status: "ok" }); }); /** * @swagger * /user/groups/search: * get: * summary: Search for groups * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: query * name: query * required: true * schema: * type: string * - in: query * name: courses * schema: * type: boolean * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * groups: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/groups/search", async (req, res) => { let query = req.query.query; let coursesB = req.query.courses ? true : false; let groups = await DB.searchGroups(query, coursesB); return res.json({ status: "ok", groups }); }); /** * @swagger * /user/groups/{id}: * get: * summary: Get details for a specific group * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * groups: * $ref: '#/components/schemas/Profile' */ router.get("/groups/:id", async (req, res) => { const groupid = req.params.id; let groups = await DB.getGroup(groupid); return res.json({ status: "ok", groups }); }); /** * @swagger * /user/groups/{id}/subscribe: * get: * summary: Subscribe to a group * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK */ router.get("/groups/:id/subscribe", async (req, res) => { const groupid = req.params.id; const profileid = getProfileId(req); const isPrivate = await DB.isGroupPrivate(groupid); DB.subscribeToGroup(profileid, groupid, isPrivate); //Add notification to group owner if (isPrivate) Notifications.yourGroupHasARequest(profileid, groupid) return res.json({ status: "ok" }); }); /** * @swagger * /user/groups/{id}/unsubscribe: * get: * summary: Unsubscribe from a group * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK */ router.get("/groups/:id/unsubscribe", async (req, res) => { const groupid = req.params.id; const profileid = getProfileId(req); DB.unsubscribeToGroup(profileid, groupid); //Add notification to group owner return res.json({ status: "ok" }); }); /** * @swagger * /user/search: * get: * summary: Search for profiles * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: query * name: query * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string * profiles: * type: array * items: * $ref: '#/components/schemas/Profile' */ router.get("/search", async (req, res) => { let query = req.query.query; let profiles = await DB.searchProfile(query); return res.json({ status: "ok", profiles }); }); /** * @swagger * /user/setData: * post: * summary: Set custom data for a profile * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * key: * type: string * value: * type: object * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string */ router.post("/setData", (req, res) => { const key = req.body.key; const value = req.body.value; const profileid = getProfileId(req); DB.setData(profileid, key, value); return res.json({ status: "ok", }); }); /** * @swagger * /user/myProfile: * post: * summary: Update the current user's profile * tags: [Profiles] * security: * - cookieAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * profile: * $ref: '#/components/schemas/Profile' * data: * type: object * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string */ router.post("/myProfile", async (req, res) => { try { let profile = { userid: getUserId(req), profile: req.body.profile, data: req.body.data }; let profileObj = new Profile(profile); //validates profile const updateRes = await DB.updateProfile(getProfileId(req), profileObj); if (!updateRes || !updateRes.matchedCount) { return res.status(400).json({ status: "Could not update profile" }); } return res.json({ status: "ok" }); } catch (error) { console.error("Error updating myProfile", error); return res.status(500).json({ status: "Internal server error" }); } }); /** * @swagger * /user/{id}: * get: * summary: Get a specific profile by ID * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * $ref: '#/components/schemas/Profile' */ router.get("/:id", async (req, res) => { try { let profileId = req.params.id; let profile = await DB.getProfile(profileId); if (!profile || !profile._id) { return res.status(404).json({ status: "Profile not found", }); } return res.json({ status: "ok", ...profile }); } catch (error) { console.error("Error loading profile", error); return res.status(500).json({ status: "Internal server error" }); } }); /** * @swagger * /user/{id}: * delete: * summary: Delete a profile * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string */ router.delete("/:id", async (req, res) => { const profileId = req.params.id; const userid = getUserId(req); if (!await profileBelongsToUser(profileId, userid)) return res.json({ status: "This profile is not yours." }); await DB.removeProfile(profileId); return res.json({ status: "ok" }); }); /** * @swagger * /user/{id}/follow: * get: * summary: Follow a profile * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string */ router.get("/:id/follow", async (req, res) => { let followProfileId = req.params.id; const profileid = getProfileId(req); DB.followProfile(profileid, followProfileId); //Add notification to user return res.json({ status: "ok" }); }); /** * @swagger * /user/{id}/unfollow: * get: * summary: Unfollow a profile * tags: [Profiles] * security: * - cookieAuth: [] * parameters: * - in: path * name: id * required: true * schema: * type: string * responses: * 200: * description: OK * content: * application/json: * schema: * type: object * properties: * status: * type: string */ router.get("/:id/unfollow", async (req, res) => { let followProfileId = req.params.id; const profileid = getProfileId(req); DB.unfollowProfile(profileid, followProfileId); //Add notification to user return res.json({ status: "ok" }); }); }); module.exports = router