/** * 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 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 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, 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: '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; 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, 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: '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: '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;