diff --git a/backend/src/routes/matches.js b/backend/src/routes/matches.js index d734ae9..60510e7 100644 --- a/backend/src/routes/matches.js +++ b/backend/src/routes/matches.js @@ -427,6 +427,17 @@ router.get('/:slug', authenticate, async (req, res, next) => { const partner = isUser1 ? match.user2 : match.user1; const isInitiator = match.user1Id === userId; + // Check if current user has already rated this match + const userRating = await prisma.rating.findUnique({ + where: { + matchId_raterId_ratedId: { + matchId: match.id, + raterId: userId, + ratedId: partner.id, + }, + }, + }); + res.json({ success: true, data: { @@ -443,6 +454,7 @@ router.get('/:slug', authenticate, async (req, res, next) => { status: match.status, roomId: match.roomId, isInitiator, + hasRated: !!userRating, createdAt: match.createdAt, }, }); @@ -701,4 +713,139 @@ router.delete('/:slug', authenticate, async (req, res, next) => { } }); +// POST /api/matches/:slug/ratings - Rate a partner after match +router.post('/:slug/ratings', authenticate, async (req, res, next) => { + try { + const { slug } = req.params; + const userId = req.user.id; + const { score, comment, wouldCollaborateAgain } = req.body; + + // Validation + if (!score || score < 1 || score > 5) { + return res.status(400).json({ + success: false, + error: 'Score must be between 1 and 5', + }); + } + + // Find match + const match = await prisma.match.findUnique({ + where: { slug }, + select: { + id: true, + user1Id: true, + user2Id: true, + status: true, + }, + }); + + if (!match) { + return res.status(404).json({ + success: false, + error: 'Match not found', + }); + } + + // Check authorization - user must be part of this match + if (match.user1Id !== userId && match.user2Id !== userId) { + return res.status(403).json({ + success: false, + error: 'You are not authorized to rate this match', + }); + } + + // Check if match is accepted + if (match.status !== 'accepted' && match.status !== 'completed') { + return res.status(400).json({ + success: false, + error: 'Match must be accepted before rating', + }); + } + + // Determine who is being rated (the other user in the match) + const ratedUserId = match.user1Id === userId ? match.user2Id : match.user1Id; + + // Check if user already rated this match + const existingRating = await prisma.rating.findUnique({ + where: { + matchId_raterId_ratedId: { + matchId: match.id, + raterId: userId, + ratedId: ratedUserId, + }, + }, + }); + + if (existingRating) { + return res.status(400).json({ + success: false, + error: 'You have already rated this match', + }); + } + + // Create rating + const rating = await prisma.rating.create({ + data: { + matchId: match.id, + raterId: userId, + ratedId: ratedUserId, + score, + comment: comment || null, + wouldCollaborateAgain: wouldCollaborateAgain || false, + }, + include: { + rater: { + select: { + id: true, + username: true, + firstName: true, + lastName: true, + }, + }, + rated: { + select: { + id: true, + username: true, + firstName: true, + lastName: true, + }, + }, + }, + }); + + // Check if both users have rated - if so, mark match as completed + const otherUserRating = await prisma.rating.findUnique({ + where: { + matchId_raterId_ratedId: { + matchId: match.id, + raterId: ratedUserId, + ratedId: userId, + }, + }, + }); + + if (otherUserRating) { + // Both users have rated - mark match as completed + await prisma.match.update({ + where: { id: match.id }, + data: { status: 'completed' }, + }); + } + + res.status(201).json({ + success: true, + data: rating, + }); + } catch (error) { + // Handle unique constraint violation + if (error.code === 'P2002') { + return res.status(400).json({ + success: false, + error: 'You have already rated this match', + }); + } + next(error); + } +}); + module.exports = router; diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js index ca36d70..6b62646 100644 --- a/backend/src/routes/users.js +++ b/backend/src/routes/users.js @@ -148,4 +148,92 @@ router.get('/:username', authenticate, async (req, res, next) => { } }); +// GET /api/users/:username/ratings - Get ratings for a user +router.get('/:username/ratings', authenticate, async (req, res, next) => { + try { + const { username } = req.params; + + // Find user + const user = await prisma.user.findUnique({ + where: { username }, + select: { id: true, username: true }, + }); + + if (!user) { + return res.status(404).json({ + success: false, + error: 'User not found', + }); + } + + // Get ratings received by this user + const ratings = await prisma.rating.findMany({ + where: { ratedId: user.id }, + include: { + rater: { + select: { + id: true, + username: true, + firstName: true, + lastName: true, + avatar: true, + }, + }, + match: { + select: { + id: true, + slug: true, + event: { + select: { + id: true, + slug: true, + name: true, + startDate: true, + }, + }, + }, + }, + }, + orderBy: { createdAt: 'desc' }, + take: 50, // Last 50 ratings + }); + + // Calculate average + const averageRating = ratings.length > 0 + ? ratings.reduce((sum, r) => sum + r.score, 0) / ratings.length + : 0; + + res.json({ + success: true, + data: { + username: user.username, + averageRating: averageRating.toFixed(1), + ratingsCount: ratings.length, + ratings: ratings.map(r => ({ + id: r.id, + score: r.score, + comment: r.comment, + wouldCollaborateAgain: r.wouldCollaborateAgain, + createdAt: r.createdAt, + rater: { + id: r.rater.id, + username: r.rater.username, + firstName: r.rater.firstName, + lastName: r.rater.lastName, + avatar: r.rater.avatar, + }, + event: { + id: r.match.event.id, + slug: r.match.event.slug, + name: r.match.event.name, + startDate: r.match.event.startDate, + }, + })), + }, + }); + } catch (error) { + next(error); + } +}); + module.exports = router; diff --git a/frontend/src/pages/MatchChatPage.jsx b/frontend/src/pages/MatchChatPage.jsx index 5622963..c888a51 100644 --- a/frontend/src/pages/MatchChatPage.jsx +++ b/frontend/src/pages/MatchChatPage.jsx @@ -266,12 +266,19 @@ const MatchChatPage = () => {
@{partner.username}
- + {!match.hasRated && ( + + )} + {match.hasRated && ( +⭐ {partner.rating} • {partner.matches_count} collaborations
+@{partner.username}