Files
spotlightcam/backend/src/routes/dashboard.js

362 lines
9.3 KiB
JavaScript
Raw Normal View History

/**
* Dashboard Routes
* Aggregated data for user dashboard
*/
const express = require('express');
const router = express.Router();
const { authenticate } = require('../middleware/auth');
const { PrismaClient } = require('@prisma/client');
const { getEventsOnlineCounts } = require('../socket');
const { MATCH_STATUS } = require('../constants');
const prisma = new PrismaClient();
/**
* GET /api/dashboard
* Get dashboard data for authenticated user
*
* Returns:
* - activeEvents: Events user is checked in to
* - activeMatches: Accepted matches with video/rating status
* - matchRequests: Pending incoming/outgoing match requests
*/
router.get('/', authenticate, async (req, res, next) => {
try {
const userId = req.user.id;
// 1. Get active events (user is participant)
const eventParticipants = await prisma.eventParticipant.findMany({
where: {
userId: userId,
},
include: {
event: true,
},
orderBy: {
event: {
startDate: 'asc', // Upcoming events first
},
},
});
// Get online counts for all events (safely handle if socket not initialized)
let onlineCounts = {};
try {
const eventIds = eventParticipants.map(ep => ep.event.id);
onlineCounts = getEventsOnlineCounts(eventIds);
} catch (_) {
// Socket may not be initialized (e.g., during tests)
}
// Get user's heats for each event
const activeEvents = await Promise.all(
eventParticipants.map(async (ep) => {
const heats = await prisma.eventUserHeat.findMany({
where: {
userId: userId,
eventId: ep.event.id,
},
include: {
division: {
select: {
id: true,
name: true,
abbreviation: true,
},
},
competitionType: {
select: {
id: true,
name: true,
abbreviation: true,
},
},
},
});
return {
id: ep.event.id,
slug: ep.event.slug,
name: ep.event.name,
location: ep.event.location,
startDate: ep.event.startDate,
endDate: ep.event.endDate,
participantsCount: ep.event.participantsCount,
onlineCount: onlineCounts[ep.event.id] || 0,
myHeats: heats.map((h) => ({
id: h.id,
competitionType: h.competitionType,
division: h.division,
heatNumber: h.heatNumber,
role: h.role,
})),
};
})
);
// 2. Get active matches (accepted status)
const matches = await prisma.match.findMany({
where: {
OR: [
{ user1Id: userId },
{ user2Id: userId },
],
status: MATCH_STATUS.ACCEPTED,
},
include: {
user1: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true,
},
},
user2: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true,
},
},
event: {
select: {
id: true,
name: true,
slug: true,
},
},
room: {
include: {
messages: {
orderBy: {
createdAt: 'desc',
},
take: 50, // Get recent messages to check for videos
},
},
},
ratings: true,
},
});
const activeMatches = matches.map((match) => {
// Determine partner (the other user)
const isUser1 = match.user1Id === userId;
const partner = isUser1 ? match.user2 : match.user1;
const partnerId = partner.id;
// Check video exchange status from messages
const myVideoMessages = match.room?.messages.filter(
(m) => m.userId === userId && m.content.includes('📹 Video sent:')
) || [];
const partnerVideoMessages = match.room?.messages.filter(
(m) => m.userId === partnerId && m.content.includes('📹 Video sent:')
) || [];
const videoExchange = {
sentByMe: myVideoMessages.length > 0,
receivedFromPartner: partnerVideoMessages.length > 0,
lastVideoTimestamp: myVideoMessages.length > 0 || partnerVideoMessages.length > 0
? new Date(Math.max(
...[...myVideoMessages, ...partnerVideoMessages].map(m => m.createdAt.getTime())
))
: null,
};
// Check rating status
const myRating = match.ratings.find((r) => r.raterId === userId);
const partnerRating = match.ratings.find((r) => r.raterId === partnerId);
const ratings = {
ratedByMe: !!myRating,
ratedByPartner: !!partnerRating,
};
// Get last message timestamp
const lastMessage = match.room?.messages[0];
const lastMessageAt = lastMessage ? lastMessage.createdAt : match.createdAt;
// Calculate unread count
const myLastReadAt = isUser1 ? match.user1LastReadAt : match.user2LastReadAt;
const unreadCount = myLastReadAt
? (match.room?.messages || []).filter(
(m) => m.userId !== userId && new Date(m.createdAt) > new Date(myLastReadAt)
).length
: (match.room?.messages || []).filter((m) => m.userId !== userId).length;
return {
id: match.id,
slug: match.slug,
partner: {
id: partner.id,
username: partner.username,
firstName: partner.firstName,
lastName: partner.lastName,
avatar: partner.avatar,
},
event: {
id: match.event.id,
name: match.event.name,
slug: match.event.slug,
},
videoExchange,
ratings,
unreadCount,
lastMessageAt,
status: match.status,
};
});
// Sort by lastMessageAt (most recent first)
activeMatches.sort((a, b) => new Date(b.lastMessageAt) - new Date(a.lastMessageAt));
// 3. Get match requests
// Incoming: user is user2, status is pending
const incomingRequests = await prisma.match.findMany({
where: {
user2Id: userId,
status: MATCH_STATUS.PENDING,
},
include: {
user1: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true,
},
},
event: {
select: {
id: true,
name: true,
slug: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
// Get requester heats for incoming requests
const incoming = await Promise.all(
incomingRequests.map(async (req) => {
const requesterHeats = await prisma.eventUserHeat.findMany({
where: {
userId: req.user1Id,
eventId: req.eventId,
},
include: {
division: {
select: {
name: true,
abbreviation: true,
},
},
competitionType: {
select: {
name: true,
abbreviation: true,
},
},
},
});
return {
id: req.id,
slug: req.slug,
requester: {
id: req.user1.id,
username: req.user1.username,
firstName: req.user1.firstName,
lastName: req.user1.lastName,
avatar: req.user1.avatar,
},
event: {
id: req.event.id,
name: req.event.name,
slug: req.event.slug,
},
requesterHeats: requesterHeats.map((h) => ({
competitionType: h.competitionType,
division: h.division,
heatNumber: h.heatNumber,
role: h.role,
})),
createdAt: req.createdAt,
};
})
);
// Outgoing: user is user1, status is pending
const outgoingRequests = await prisma.match.findMany({
where: {
user1Id: userId,
status: MATCH_STATUS.PENDING,
},
include: {
user2: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true,
},
},
event: {
select: {
id: true,
name: true,
slug: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
});
const outgoing = outgoingRequests.map((req) => ({
id: req.id,
slug: req.slug,
recipient: {
id: req.user2.id,
username: req.user2.username,
firstName: req.user2.firstName,
lastName: req.user2.lastName,
avatar: req.user2.avatar,
},
event: {
id: req.event.id,
name: req.event.name,
slug: req.event.slug,
},
createdAt: req.createdAt,
}));
res.json({
success: true,
data: {
activeEvents,
activeMatches,
matchRequests: {
incoming,
outgoing,
},
},
});
} catch (error) {
next(error);
}
});
module.exports = router;