feat: add match slugs for security and fix message history loading

Security improvements:
- Add random CUID slugs to Match model to prevent ID enumeration attacks
- Update all match URLs from /matches/:id to /matches/:slug
- Keep numeric IDs for internal Socket.IO operations only

Backend changes:
- Add slug field to matches table with unique index
- Update all match endpoints to use slug-based lookups (GET, PUT, DELETE)
- Add GET /api/matches/:slug/messages endpoint to fetch message history
- Include matchSlug in all Socket.IO notifications

Frontend changes:
- Update all match routes to use slug parameter
- Update MatchesPage to use slug for accept/reject/navigate operations
- Update MatchChatPage to fetch match data by slug and load message history
- Update RatePartnerPage to use slug parameter
- Add matchesAPI.getMatchMessages() function

Bug fixes:
- Fix MatchChatPage not loading message history from database on mount
- Messages now persist and display correctly when users reconnect
This commit is contained in:
Radosław Gierwiało
2025-11-14 22:22:11 +01:00
parent 4a3e32f3b6
commit c2010246e3
8 changed files with 201 additions and 53 deletions

View File

@@ -66,13 +66,13 @@ const MatchesPage = () => {
const handleMatchCancelled = (data) => {
// Remove cancelled match from list
setMatches(prev => prev.filter(m => m.id !== data.matchId));
setMatches(prev => prev.filter(m => m.slug !== data.matchSlug));
};
const handleAccept = async (matchId) => {
const handleAccept = async (matchSlug) => {
try {
setProcessingMatchId(matchId);
await matchesAPI.acceptMatch(matchId);
setProcessingMatchId(matchSlug);
await matchesAPI.acceptMatch(matchSlug);
// Reload matches
await loadMatches();
@@ -87,17 +87,17 @@ const MatchesPage = () => {
}
};
const handleReject = async (matchId) => {
const handleReject = async (matchSlug) => {
if (!confirm('Are you sure you want to reject this match request?')) {
return;
}
try {
setProcessingMatchId(matchId);
await matchesAPI.deleteMatch(matchId);
setProcessingMatchId(matchSlug);
await matchesAPI.deleteMatch(matchSlug);
// Remove from list
setMatches(prev => prev.filter(m => m.id !== matchId));
setMatches(prev => prev.filter(m => m.slug !== matchSlug));
} catch (error) {
console.error('Failed to reject match:', error);
alert('Failed to reject match. Please try again.');
@@ -108,7 +108,7 @@ const MatchesPage = () => {
const handleOpenChat = (match) => {
if (match.status === 'accepted' && match.roomId) {
navigate(`/matches/${match.id}/chat`);
navigate(`/matches/${match.slug}/chat`);
}
};
@@ -187,7 +187,7 @@ const MatchesPage = () => {
onAccept={handleAccept}
onReject={handleReject}
onOpenChat={handleOpenChat}
processing={processingMatchId === match.id}
processing={processingMatchId === match.slug}
/>
))}
</div>
@@ -208,7 +208,7 @@ const MatchesPage = () => {
onAccept={handleAccept}
onReject={handleReject}
onOpenChat={handleOpenChat}
processing={processingMatchId === match.id}
processing={processingMatchId === match.slug}
/>
))}
</div>
@@ -295,7 +295,7 @@ const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
{isIncoming && (
<>
<button
onClick={() => onAccept(match.id)}
onClick={() => onAccept(match.slug)}
disabled={processing}
className="p-2 bg-green-600 text-white rounded-full hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
title="Accept"
@@ -307,7 +307,7 @@ const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
)}
</button>
<button
onClick={() => onReject(match.id)}
onClick={() => onReject(match.slug)}
disabled={processing}
className="p-2 bg-red-600 text-white rounded-full hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
title="Reject"
@@ -319,7 +319,7 @@ const MatchCard = ({ match, onAccept, onReject, onOpenChat, processing }) => {
{isOutgoing && (
<button
onClick={() => onReject(match.id)}
onClick={() => onReject(match.slug)}
disabled={processing}
className="px-3 py-1.5 text-sm border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>