2025-11-14 19:22:23 +01:00
|
|
|
const express = require('express');
|
|
|
|
|
const { prisma } = require('../utils/db');
|
|
|
|
|
const { authenticate } = require('../middleware/auth');
|
|
|
|
|
const { getIO } = require('../socket');
|
|
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
// POST /api/matches - Create a match request
|
|
|
|
|
router.post('/', authenticate, async (req, res, next) => {
|
|
|
|
|
try {
|
|
|
|
|
const { targetUserId, eventSlug } = req.body;
|
|
|
|
|
const requesterId = req.user.id;
|
|
|
|
|
|
|
|
|
|
// Validation
|
|
|
|
|
if (!targetUserId || !eventSlug) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'targetUserId and eventSlug are required',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requesterId === targetUserId) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Cannot create match with yourself',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find event by slug
|
|
|
|
|
const event = await prisma.event.findUnique({
|
|
|
|
|
where: { slug: eventSlug },
|
|
|
|
|
select: { id: true, name: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!event) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Event not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if both users are participants
|
|
|
|
|
const [requesterParticipant, targetParticipant] = await Promise.all([
|
|
|
|
|
prisma.eventParticipant.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
userId_eventId: {
|
|
|
|
|
userId: requesterId,
|
|
|
|
|
eventId: event.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
prisma.eventParticipant.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
userId_eventId: {
|
|
|
|
|
userId: targetUserId,
|
|
|
|
|
eventId: event.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!requesterParticipant) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'You must be a participant of this event',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!targetParticipant) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Target user is not a participant of this event',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if match already exists (in either direction)
|
|
|
|
|
const existingMatch = await prisma.match.findFirst({
|
|
|
|
|
where: {
|
|
|
|
|
eventId: event.id,
|
|
|
|
|
OR: [
|
|
|
|
|
{ user1Id: requesterId, user2Id: targetUserId },
|
|
|
|
|
{ user1Id: targetUserId, user2Id: requesterId },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingMatch) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match already exists with this user',
|
|
|
|
|
match: existingMatch,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create match
|
|
|
|
|
const match = await prisma.match.create({
|
|
|
|
|
data: {
|
|
|
|
|
user1Id: requesterId,
|
|
|
|
|
user2Id: targetUserId,
|
|
|
|
|
eventId: event.id,
|
|
|
|
|
status: 'pending',
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
user1: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user2: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Emit socket event to target user
|
|
|
|
|
try {
|
|
|
|
|
const io = getIO();
|
|
|
|
|
const targetSocketRoom = `user_${targetUserId}`;
|
|
|
|
|
io.to(targetSocketRoom).emit('match_request_received', {
|
|
|
|
|
matchId: match.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
matchSlug: match.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
from: {
|
|
|
|
|
id: match.user1.id,
|
|
|
|
|
username: match.user1.username,
|
|
|
|
|
avatar: match.user1.avatar,
|
|
|
|
|
firstName: match.user1.firstName,
|
|
|
|
|
lastName: match.user1.lastName,
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
slug: match.event.slug,
|
|
|
|
|
name: match.event.name,
|
|
|
|
|
},
|
|
|
|
|
createdAt: match.createdAt,
|
|
|
|
|
});
|
|
|
|
|
} catch (socketError) {
|
|
|
|
|
console.error('Failed to emit match request notification:', socketError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.status(201).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: match,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Handle unique constraint violation
|
|
|
|
|
if (error.code === 'P2002') {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match already exists with this user for this event',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// GET /api/matches - List matches for current user (optionally filtered by event)
|
|
|
|
|
router.get('/', authenticate, async (req, res, next) => {
|
|
|
|
|
try {
|
|
|
|
|
const userId = req.user.id;
|
|
|
|
|
const { eventSlug, status } = req.query;
|
|
|
|
|
|
|
|
|
|
// Build where clause
|
|
|
|
|
const where = {
|
|
|
|
|
OR: [
|
|
|
|
|
{ user1Id: userId },
|
|
|
|
|
{ user2Id: userId },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Filter by event if provided
|
|
|
|
|
if (eventSlug) {
|
|
|
|
|
const event = await prisma.event.findUnique({
|
|
|
|
|
where: { slug: eventSlug },
|
|
|
|
|
select: { id: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!event) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Event not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
where.eventId = event.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Filter by status if provided
|
|
|
|
|
if (status) {
|
|
|
|
|
where.status = status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch matches
|
|
|
|
|
const matches = await prisma.match.findMany({
|
|
|
|
|
where,
|
|
|
|
|
include: {
|
|
|
|
|
user1: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user2: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
location: true,
|
|
|
|
|
startDate: true,
|
|
|
|
|
endDate: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
room: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
|
|
|
|
createdAt: 'desc',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Transform matches to include partner info
|
|
|
|
|
const transformedMatches = matches.map(match => {
|
|
|
|
|
const isUser1 = match.user1Id === userId;
|
|
|
|
|
const partner = isUser1 ? match.user2 : match.user1;
|
|
|
|
|
const isInitiator = match.user1Id === userId;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: match.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
slug: match.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
partner: {
|
|
|
|
|
id: partner.id,
|
|
|
|
|
username: partner.username,
|
|
|
|
|
avatar: partner.avatar,
|
|
|
|
|
firstName: partner.firstName,
|
|
|
|
|
lastName: partner.lastName,
|
|
|
|
|
},
|
|
|
|
|
event: match.event,
|
|
|
|
|
status: match.status,
|
|
|
|
|
roomId: match.roomId,
|
|
|
|
|
isInitiator,
|
|
|
|
|
createdAt: match.createdAt,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
count: transformedMatches.length,
|
|
|
|
|
data: transformedMatches,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-14 22:22:11 +01:00
|
|
|
// GET /api/matches/:slug/messages - Get messages for a match
|
|
|
|
|
router.get('/:slug/messages', authenticate, async (req, res, next) => {
|
2025-11-14 19:22:23 +01:00
|
|
|
try {
|
2025-11-14 22:22:11 +01:00
|
|
|
const { slug } = req.params;
|
|
|
|
|
const userId = req.user.id;
|
|
|
|
|
|
|
|
|
|
// Find match
|
|
|
|
|
const match = await prisma.match.findUnique({
|
|
|
|
|
where: { slug },
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
user1Id: true,
|
|
|
|
|
user2Id: true,
|
|
|
|
|
roomId: true,
|
|
|
|
|
status: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check authorization
|
|
|
|
|
if (match.user1Id !== userId && match.user2Id !== userId) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'You are not authorized to view messages for this match',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if match is accepted
|
|
|
|
|
if (match.status !== 'accepted' || !match.roomId) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match must be accepted before viewing messages',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get messages
|
|
|
|
|
const messages = await prisma.message.findMany({
|
|
|
|
|
where: { roomId: match.roomId },
|
|
|
|
|
include: {
|
|
|
|
|
user: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { createdAt: 'asc' },
|
|
|
|
|
take: 100, // Last 100 messages
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
count: messages.length,
|
|
|
|
|
data: messages.map(msg => ({
|
|
|
|
|
id: msg.id,
|
|
|
|
|
roomId: msg.roomId,
|
|
|
|
|
userId: msg.user.id,
|
|
|
|
|
username: msg.user.username,
|
|
|
|
|
avatar: msg.user.avatar,
|
|
|
|
|
firstName: msg.user.firstName,
|
|
|
|
|
lastName: msg.user.lastName,
|
|
|
|
|
content: msg.content,
|
|
|
|
|
type: msg.type,
|
|
|
|
|
createdAt: msg.createdAt,
|
|
|
|
|
})),
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// GET /api/matches/:slug - Get specific match
|
|
|
|
|
router.get('/:slug', authenticate, async (req, res, next) => {
|
|
|
|
|
try {
|
|
|
|
|
const { slug } = req.params;
|
2025-11-14 19:22:23 +01:00
|
|
|
const userId = req.user.id;
|
|
|
|
|
|
|
|
|
|
const match = await prisma.match.findUnique({
|
2025-11-14 22:22:11 +01:00
|
|
|
where: { slug },
|
2025-11-14 19:22:23 +01:00
|
|
|
include: {
|
|
|
|
|
user1: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user2: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
location: true,
|
|
|
|
|
startDate: true,
|
|
|
|
|
endDate: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
room: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check authorization
|
|
|
|
|
if (match.user1Id !== userId && match.user2Id !== userId) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'You are not authorized to view this match',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform match data
|
|
|
|
|
const isUser1 = match.user1Id === userId;
|
|
|
|
|
const partner = isUser1 ? match.user2 : match.user1;
|
|
|
|
|
const isInitiator = match.user1Id === userId;
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
id: match.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
slug: match.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
partner: {
|
|
|
|
|
id: partner.id,
|
|
|
|
|
username: partner.username,
|
|
|
|
|
avatar: partner.avatar,
|
|
|
|
|
firstName: partner.firstName,
|
|
|
|
|
lastName: partner.lastName,
|
|
|
|
|
},
|
|
|
|
|
event: match.event,
|
|
|
|
|
status: match.status,
|
|
|
|
|
roomId: match.roomId,
|
|
|
|
|
isInitiator,
|
|
|
|
|
createdAt: match.createdAt,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-14 22:22:11 +01:00
|
|
|
// PUT /api/matches/:slug/accept - Accept a pending match
|
|
|
|
|
router.put('/:slug/accept', authenticate, async (req, res, next) => {
|
2025-11-14 19:22:23 +01:00
|
|
|
try {
|
2025-11-14 22:22:11 +01:00
|
|
|
const { slug } = req.params;
|
2025-11-14 19:22:23 +01:00
|
|
|
const userId = req.user.id;
|
|
|
|
|
|
|
|
|
|
// Find match
|
|
|
|
|
const match = await prisma.match.findUnique({
|
2025-11-14 22:22:11 +01:00
|
|
|
where: { slug },
|
2025-11-14 19:22:23 +01:00
|
|
|
include: {
|
|
|
|
|
user1: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user2: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check authorization - only user2 can accept
|
|
|
|
|
if (match.user2Id !== userId) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Only the match recipient can accept',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check status
|
|
|
|
|
if (match.status !== 'pending') {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: `Match is already ${match.status}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create private chat room and update match in transaction
|
|
|
|
|
const updatedMatch = await prisma.$transaction(async (tx) => {
|
|
|
|
|
// Create private chat room
|
|
|
|
|
const chatRoom = await tx.chatRoom.create({
|
|
|
|
|
data: {
|
|
|
|
|
type: 'private',
|
|
|
|
|
eventId: match.eventId,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update match status and link to chat room
|
|
|
|
|
const updated = await tx.match.update({
|
2025-11-14 22:22:11 +01:00
|
|
|
where: { slug },
|
2025-11-14 19:22:23 +01:00
|
|
|
data: {
|
|
|
|
|
status: 'accepted',
|
|
|
|
|
roomId: chatRoom.id,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
user1: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user2: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
username: true,
|
|
|
|
|
avatar: true,
|
|
|
|
|
firstName: true,
|
|
|
|
|
lastName: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
room: {
|
|
|
|
|
select: {
|
|
|
|
|
id: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Emit socket event to both users
|
|
|
|
|
try {
|
|
|
|
|
const io = getIO();
|
|
|
|
|
const user1SocketRoom = `user_${match.user1Id}`;
|
|
|
|
|
const user2SocketRoom = `user_${match.user2Id}`;
|
|
|
|
|
|
|
|
|
|
const notification = {
|
|
|
|
|
matchId: updatedMatch.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
matchSlug: updatedMatch.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
roomId: updatedMatch.roomId,
|
|
|
|
|
event: {
|
|
|
|
|
slug: updatedMatch.event.slug,
|
|
|
|
|
name: updatedMatch.event.name,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
io.to(user1SocketRoom).emit('match_accepted', {
|
|
|
|
|
...notification,
|
|
|
|
|
partner: {
|
|
|
|
|
id: updatedMatch.user2.id,
|
|
|
|
|
username: updatedMatch.user2.username,
|
|
|
|
|
avatar: updatedMatch.user2.avatar,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
io.to(user2SocketRoom).emit('match_accepted', {
|
|
|
|
|
...notification,
|
|
|
|
|
partner: {
|
|
|
|
|
id: updatedMatch.user1.id,
|
|
|
|
|
username: updatedMatch.user1.username,
|
|
|
|
|
avatar: updatedMatch.user1.avatar,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (socketError) {
|
|
|
|
|
console.error('Failed to emit match accepted notification:', socketError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform response
|
|
|
|
|
const isUser1 = updatedMatch.user1Id === userId;
|
|
|
|
|
const partner = isUser1 ? updatedMatch.user2 : updatedMatch.user1;
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: {
|
|
|
|
|
id: updatedMatch.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
slug: updatedMatch.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
partner: {
|
|
|
|
|
id: partner.id,
|
|
|
|
|
username: partner.username,
|
|
|
|
|
avatar: partner.avatar,
|
|
|
|
|
firstName: partner.firstName,
|
|
|
|
|
lastName: partner.lastName,
|
|
|
|
|
},
|
|
|
|
|
event: updatedMatch.event,
|
|
|
|
|
status: updatedMatch.status,
|
|
|
|
|
roomId: updatedMatch.roomId,
|
|
|
|
|
isInitiator: isUser1,
|
|
|
|
|
createdAt: updatedMatch.createdAt,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-14 22:22:11 +01:00
|
|
|
// DELETE /api/matches/:slug - Reject or cancel a match
|
|
|
|
|
router.delete('/:slug', authenticate, async (req, res, next) => {
|
2025-11-14 19:22:23 +01:00
|
|
|
try {
|
2025-11-14 22:22:11 +01:00
|
|
|
const { slug } = req.params;
|
2025-11-14 19:22:23 +01:00
|
|
|
const userId = req.user.id;
|
|
|
|
|
|
|
|
|
|
// Find match
|
|
|
|
|
const match = await prisma.match.findUnique({
|
2025-11-14 22:22:11 +01:00
|
|
|
where: { slug },
|
2025-11-14 19:22:23 +01:00
|
|
|
include: {
|
|
|
|
|
event: {
|
|
|
|
|
select: {
|
|
|
|
|
slug: true,
|
|
|
|
|
name: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Match not found',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check authorization - both users can delete
|
|
|
|
|
if (match.user1Id !== userId && match.user2Id !== userId) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'You are not authorized to delete this match',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cannot delete completed matches
|
|
|
|
|
if (match.status === 'completed') {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
error: 'Cannot delete completed matches',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete match (will cascade delete chat room if exists)
|
|
|
|
|
await prisma.match.delete({
|
2025-11-14 22:22:11 +01:00
|
|
|
where: { slug },
|
2025-11-14 19:22:23 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Emit socket event to the other user
|
|
|
|
|
try {
|
|
|
|
|
const io = getIO();
|
|
|
|
|
const otherUserId = match.user1Id === userId ? match.user2Id : match.user1Id;
|
|
|
|
|
const otherUserSocketRoom = `user_${otherUserId}`;
|
|
|
|
|
|
|
|
|
|
io.to(otherUserSocketRoom).emit('match_cancelled', {
|
|
|
|
|
matchId: match.id,
|
2025-11-14 22:22:11 +01:00
|
|
|
matchSlug: match.slug,
|
2025-11-14 19:22:23 +01:00
|
|
|
event: {
|
|
|
|
|
slug: match.event.slug,
|
|
|
|
|
name: match.event.name,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (socketError) {
|
|
|
|
|
console.error('Failed to emit match cancelled notification:', socketError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: 'Match deleted successfully',
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
next(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|