Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 207 additions & 29 deletions shatter-backend/src/controllers/participant_connections_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,28 @@ import { ParticipantConnection } from "../models/participant_connection_model";
*/
export async function createParticipantConnection(req: Request, res: Response) {
try {
const requiredFields = ["_eventId", "primaryParticipantId", "secondaryParticipantId"];
const requiredFields = [
"_eventId",
"primaryParticipantId",
"secondaryParticipantId",
];
if (!check_req_fields(req, requiredFields)) {
return res.status(400).json({ error: "Missing required fields" });
}

const { _eventId, primaryParticipantId, secondaryParticipantId, description } = req.body;
const {
_eventId,
primaryParticipantId,
secondaryParticipantId,
description,
} = req.body;

// Validate ObjectId format before hitting the DB
const idsToValidate = { _eventId, primaryParticipantId, secondaryParticipantId };
const idsToValidate = {
_eventId,
primaryParticipantId,
secondaryParticipantId,
};
for (const [key, value] of Object.entries(idsToValidate)) {
if (!Types.ObjectId.isValid(value)) {
return res.status(400).json({ error: `Invalid ${key}` });
Expand All @@ -48,15 +61,25 @@ export async function createParticipantConnection(req: Request, res: Response) {

// Ensure both participants exist AND belong to the event
const [primaryParticipant, secondaryParticipant] = await Promise.all([
Participant.findOne({ _id: primaryParticipantId, eventId: _eventId }).select("_id"),
Participant.findOne({ _id: secondaryParticipantId, eventId: _eventId }).select("_id"),
Participant.findOne({
_id: primaryParticipantId,
eventId: _eventId,
}).select("_id"),
Participant.findOne({
_id: secondaryParticipantId,
eventId: _eventId,
}).select("_id"),
]);

if (!primaryParticipant) {
return res.status(404).json({ error: "Primary participant not found for this event" });
return res
.status(404)
.json({ error: "Primary participant not found for this event" });
}
if (!secondaryParticipant) {
return res.status(404).json({ error: "Secondary participant not found for this event" });
return res
.status(404)
.json({ error: "Secondary participant not found for this event" });
}

// Prevent duplicates with exact same (_eventId, primaryParticipantId, secondaryParticipantId)
Expand All @@ -68,7 +91,8 @@ export async function createParticipantConnection(req: Request, res: Response) {

if (existing) {
return res.status(409).json({
error: "ParticipantConnection already exists for this event and participants",
error:
"ParticipantConnection already exists for this event and participants",
existingConnection: existing,
});
}
Expand Down Expand Up @@ -111,14 +135,22 @@ export async function createParticipantConnection(req: Request, res: Response) {
* @returns 409 - ParticipantConnection already exists for this event and participants
* @returns 500 - Internal server error
*/
export async function createParticipantConnectionByEmails(req: Request, res: Response) {
export async function createParticipantConnectionByEmails(
req: Request,
res: Response,
) {
try {
const requiredFields = ["_eventId", "primaryUserEmail", "secondaryUserEmail"];
const requiredFields = [
"_eventId",
"primaryUserEmail",
"secondaryUserEmail",
];
if (!check_req_fields(req, requiredFields)) {
return res.status(400).json({ error: "Missing required fields" });
}

const { _eventId, primaryUserEmail, secondaryUserEmail, description } = req.body;
const { _eventId, primaryUserEmail, secondaryUserEmail, description } =
req.body;

if (!Types.ObjectId.isValid(_eventId)) {
return res.status(400).json({ error: "Invalid _eventId" });
Expand All @@ -136,7 +168,9 @@ export async function createParticipantConnectionByEmails(req: Request, res: Res
}

if (primaryEmail === secondaryEmail) {
return res.status(400).json({ error: "primaryUserEmail and secondaryUserEmail must be different" });
return res.status(400).json({
error: "primaryUserEmail and secondaryUserEmail must be different",
});
}

// Find users by email
Expand All @@ -145,20 +179,32 @@ export async function createParticipantConnectionByEmails(req: Request, res: Res
User.findOne({ email: secondaryEmail }).select("_id"),
]);

if (!primaryUser) return res.status(404).json({ error: "Primary user not found" });
if (!secondaryUser) return res.status(404).json({ error: "Secondary user not found" });
if (!primaryUser)
return res.status(404).json({ error: "Primary user not found" });
if (!secondaryUser)
return res.status(404).json({ error: "Secondary user not found" });

// Map User -> Participant (for the event)
const [primaryParticipant, secondaryParticipant] = await Promise.all([
Participant.findOne({ eventId: _eventId, userId: primaryUser._id }).select("_id"),
Participant.findOne({ eventId: _eventId, userId: secondaryUser._id }).select("_id"),
Participant.findOne({
eventId: _eventId,
userId: primaryUser._id,
}).select("_id"),
Participant.findOne({
eventId: _eventId,
userId: secondaryUser._id,
}).select("_id"),
]);

if (!primaryParticipant) {
return res.status(404).json({ error: "Primary participant not found for this event (by user email)" });
return res.status(404).json({
error: "Primary participant not found for this event (by user email)",
});
}
if (!secondaryParticipant) {
return res.status(404).json({ error: "Secondary participant not found for this event (by user email)" });
return res.status(404).json({
error: "Secondary participant not found for this event (by user email)",
});
}

// Prevent duplicates with exact same (_eventId, primaryParticipantId, secondaryParticipantId)
Expand All @@ -170,7 +216,8 @@ export async function createParticipantConnectionByEmails(req: Request, res: Res

if (existing) {
return res.status(409).json({
error: "ParticipantConnection already exists for this event and participants",
error:
"ParticipantConnection already exists for this event and participants",
existingConnection: existing,
});
}
Expand Down Expand Up @@ -219,7 +266,9 @@ export async function deleteParticipantConnection(req: Request, res: Response) {
});

if (!deleted) {
return res.status(404).json({ error: "ParticipantConnection not found for this event" });
return res
.status(404)
.json({ error: "ParticipantConnection not found for this event" });
}

return res.status(200).json({
Expand All @@ -231,15 +280,26 @@ export async function deleteParticipantConnection(req: Request, res: Response) {
}
}

export async function getConnectionsByParticipantAndEvent(req: Request, res: Response) {
export async function getConnectionsByParticipantAndEvent(
req: Request,
res: Response,
) {
try {
const { eventId, participantId } = req.query;

if (!eventId || typeof eventId !== "string" || !Types.ObjectId.isValid(eventId)) {
if (
!eventId ||
typeof eventId !== "string" ||
!Types.ObjectId.isValid(eventId)
) {
return res.status(400).json({ error: "Invalid eventId" });
}

if (!participantId || typeof participantId !== "string" || !Types.ObjectId.isValid(participantId)) {
if (
!participantId ||
typeof participantId !== "string" ||
!Types.ObjectId.isValid(participantId)
) {
return res.status(400).json({ error: "Invalid participantId" });
}

Expand All @@ -257,11 +317,18 @@ export async function getConnectionsByParticipantAndEvent(req: Request, res: Res
}
}

export async function getConnectionsByUserEmailAndEvent(req: Request, res: Response) {
export async function getConnectionsByUserEmailAndEvent(
req: Request,
res: Response,
) {
try {
const { eventId, userEmail } = req.query;

if (!eventId || typeof eventId !== "string" || !Types.ObjectId.isValid(eventId)) {
if (
!eventId ||
typeof eventId !== "string" ||
!Types.ObjectId.isValid(eventId)
) {
return res.status(400).json({ error: "Invalid eventId" });
}

Expand All @@ -276,22 +343,133 @@ export async function getConnectionsByUserEmailAndEvent(req: Request, res: Respo
return res.status(400).json({ error: "Invalid userEmail" });
}

const participant = await Participant.findOne({ eventId, userId: user._id }).select("_id");
const participant = await Participant.findOne({
eventId,
userId: user._id,
}).select("_id");

if (!participant) {
return res.status(404).json({ error: "Participant not found for this event (by user email)" });
return res.status(404).json({
error: "Participant not found for this event (by user email)",
});
}

const connections = await ParticipantConnection.find({
_eventId: eventId,
$or: [
{ primaryParticipantId: participant._id },
{ secondaryParticipantId: participant._id }
{ secondaryParticipantId: participant._id },
],
});

return res.status(200).json(connections);
} catch (_error) {
return res.status(500).json({ error: "Internal server error" });
}
}
}

/**
* GET /api/participantConnections/getParticipantConnections/connected-users
*
* Get all users connected with a given participant in an event,
* including the description of the connection.
*
* @param req.query.eventId - MongoDB ObjectId of the event (required)
* @param req.query.participantId - MongoDB ObjectId of the participant (required)
*
* @returns 200 - Array of connected users with connection descriptions
* @returns 400 - Missing/invalid params
* @returns 404 - Participant not found or no connections
* @returns 500 - Internal server error
*/
export async function getConnectedUsersInfo(req: Request, res: Response) {
try {
const { eventId, participantId } = req.query;

if (
!eventId ||
typeof eventId !== "string" ||
!Types.ObjectId.isValid(eventId)
) {
return res.status(400).json({ error: "Invalid eventId" });
}

if (
!participantId ||
typeof participantId !== "string" ||
!Types.ObjectId.isValid(participantId)
) {
return res.status(400).json({ error: "Invalid participantId" });
}

const connections = await ParticipantConnection.find({
_eventId: eventId,
$or: [
{ primaryParticipantId: participantId },
{ secondaryParticipantId: participantId },
],
});

if (!connections.length) {
return res
.status(404)
.json({ error: "No connections found for this participant" });
}

const connectedMap = connections.map((conn) => {
const otherParticipantId =
conn.primaryParticipantId.toString() === participantId
? conn.secondaryParticipantId
: conn.primaryParticipantId;
return {
participantId: otherParticipantId,
description: conn.description || null,
};
});

// Remove duplicate connections for the same participant
const uniqueMap = Array.from(
new Map(
connectedMap.map((item) => [item.participantId.toString(), item]),
).values(),
);

const participantIds = uniqueMap.map((item) => item.participantId);

const participants = await Participant.find({
_id: { $in: participantIds },
}).select("userId name");
const userIds = participants.map((p) => p.userId);

const users = await User.find({ _id: { $in: userIds } }).select(
"name email linkedinUrl bio profilePhoto socialLinks",
);

const result = uniqueMap
.map((item) => {
const participant = participants.find(
(p) => p._id && p._id.toString() === item.participantId.toString(),
);

if (!participant || !participant.userId) return null;

const user = users.find(
(u) => u._id.toString() === participant.userId.toString(),
);

if (!user) return null;

return {
user,
participantId: participant._id,
participantName: participant.name,
connectionDescription: item.description,
};
})
.filter(Boolean);

return res.status(200).json(result);
} catch (_error) {
return res.status(500).json({ error: "Internal server error" });
}
}
2 changes: 1 addition & 1 deletion shatter-backend/src/models/participant_model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Schema, model, Document } from "mongoose";

export interface IParticipant extends Document {
userId: Schema.Types.ObjectId | null;
userId: Schema.Types.ObjectId;
name: string;
eventId: Schema.Types.ObjectId;
}
Expand Down
4 changes: 4 additions & 0 deletions shatter-backend/src/routes/participant_connections_routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createParticipantConnection,
createParticipantConnectionByEmails,
deleteParticipantConnection,
getConnectedUsersInfo,
getConnectionsByParticipantAndEvent,
getConnectionsByUserEmailAndEvent,
} from "../controllers/participant_connections_controller";
Expand Down Expand Up @@ -57,4 +58,7 @@ router.get("/getByParticipantAndEvent", authMiddleware, getConnectionsByParticip
// GET /api/participantConnections/getByUserEmailAndEvent
router.get("/getByUserEmailAndEvent", authMiddleware, getConnectionsByUserEmailAndEvent);

// Get all user's information that connected with the participant
router.get("/getParticipantConnections/connected-users", authMiddleware, getConnectedUsersInfo);

export default router;