Files
EMI-Backend/routes/profile.js
T
2026-02-20 22:15:13 -05:00

961 lines
26 KiB
JavaScript

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"
});
}
});
router.post("/notifications/viewed", async (req, res) => {
try {
const profileid = getProfileId(req);
const result = await DB.markNotificationsViewed(profileid);
if (!result) {
return res.status(400).json({
status: "Could not update notifications"
});
}
return res.json({
status: "ok"
});
} catch (error) {
console.error("Error marking notifications as viewed", 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