From 4a91a10aff37e6e2fc6df6c7ebe7a2c097f17e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Tue, 2 Dec 2025 23:46:54 +0100 Subject: [PATCH] feat(chat): add 2000 character limit for messages Added message length validation to prevent spam and improve UX with character counter feedback. Backend: - Added MESSAGE_MAX_LENGTH constant (2000 characters) - Validation in send_event_message handler: - Check if content is string - Check if not empty after trim - Check if not exceeding max length - Validation in send_match_message handler (same checks) - Returns error message if validation fails Frontend: - Added MESSAGE_MAX_LENGTH constant (2000 characters) - ChatInput component enhancements: - maxLength attribute on input (hard limit) - Character counter shows when >80% of limit - Counter turns red when at limit - Submit button disabled when at limit - Counter format: "X/2000" UX: - User sees counter at 1600+ characters (80% of limit) - Hard limit prevents typing beyond 2000 chars - Clear visual feedback (red text) when at limit - Consistent validation on both event and match chats Security: - Prevents spam with extremely long messages - Protects against potential DoS via message size - Database already uses TEXT type (supports limit) --- backend/src/constants/index.js | 4 ++ backend/src/socket/index.js | 27 ++++++++++ frontend/src/components/chat/ChatInput.jsx | 57 ++++++++++++++-------- frontend/src/constants/index.js | 3 ++ 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/backend/src/constants/index.js b/backend/src/constants/index.js index 2614455..e9e9098 100644 --- a/backend/src/constants/index.js +++ b/backend/src/constants/index.js @@ -1,10 +1,14 @@ const { MATCH_STATUS, SUGGESTION_STATUS } = require('./statuses'); const { ACCOUNT_TIER, FAIRNESS_SUPPORTER_PENALTY, FAIRNESS_COMFORT_PENALTY } = require('./tiers'); +// Message validation +const MESSAGE_MAX_LENGTH = 2000; + module.exports = { MATCH_STATUS, SUGGESTION_STATUS, ACCOUNT_TIER, FAIRNESS_SUPPORTER_PENALTY, FAIRNESS_COMFORT_PENALTY, + MESSAGE_MAX_LENGTH, }; diff --git a/backend/src/socket/index.js b/backend/src/socket/index.js index ee7018d..c8d9e3b 100644 --- a/backend/src/socket/index.js +++ b/backend/src/socket/index.js @@ -2,6 +2,7 @@ const { Server } = require('socket.io'); const { verifyToken } = require('../utils/auth'); const { prisma } = require('../utils/db'); const { ACTIONS, log: activityLog } = require('../services/activityLog'); +const { MESSAGE_MAX_LENGTH } = require('../constants'); // Track active users in each event room const activeUsers = new Map(); // eventId -> Set of { socketId, userId, username, avatar } @@ -310,6 +311,19 @@ function initializeSocket(httpServer) { return socket.emit('error', { message: 'Not in an event room' }); } + // Validate message content + if (!content || typeof content !== 'string') { + return socket.emit('error', { message: 'Invalid message content' }); + } + + if (content.trim().length === 0) { + return socket.emit('error', { message: 'Message cannot be empty' }); + } + + if (content.length > MESSAGE_MAX_LENGTH) { + return socket.emit('error', { message: `Message too long. Maximum ${MESSAGE_MAX_LENGTH} characters allowed.` }); + } + const eventId = socket.currentEventId; const roomName = socket.currentEventRoom; @@ -434,6 +448,19 @@ function initializeSocket(httpServer) { // Send message to match room socket.on('send_match_message', async ({ matchId, content }) => { try { + // Validate message content + if (!content || typeof content !== 'string') { + return socket.emit('error', { message: 'Invalid message content' }); + } + + if (content.trim().length === 0) { + return socket.emit('error', { message: 'Message cannot be empty' }); + } + + if (content.length > MESSAGE_MAX_LENGTH) { + return socket.emit('error', { message: `Message too long. Maximum ${MESSAGE_MAX_LENGTH} characters allowed.` }); + } + const roomName = `match_${matchId}`; // Get match and its room diff --git a/frontend/src/components/chat/ChatInput.jsx b/frontend/src/components/chat/ChatInput.jsx index ce4a8e3..af48af9 100644 --- a/frontend/src/components/chat/ChatInput.jsx +++ b/frontend/src/components/chat/ChatInput.jsx @@ -1,7 +1,8 @@ import { Send } from 'lucide-react'; +import { MESSAGE_MAX_LENGTH } from '../../constants'; /** - * Chat Input component with send button + * Chat Input component with send button and character counter * * @param {string} value - Input value * @param {function} onChange - Change handler for input @@ -26,26 +27,42 @@ const ChatInput = ({ onSubmit(e); }; + const charCount = value.length; + const isNearLimit = charCount > MESSAGE_MAX_LENGTH * 0.8; // 80% of limit + const isOverLimit = charCount > MESSAGE_MAX_LENGTH; + return ( -
- - -
+
+
+ + +
+ + {/* Character counter - show when approaching limit */} + {isNearLimit && ( +
+ + {charCount}/{MESSAGE_MAX_LENGTH} + +
+ )} +
); }; diff --git a/frontend/src/constants/index.js b/frontend/src/constants/index.js index ffcf2c7..e2ff519 100644 --- a/frontend/src/constants/index.js +++ b/frontend/src/constants/index.js @@ -5,3 +5,6 @@ export { CONNECTION_STATE, SUGGESTION_TYPE, } from './statuses'; + +// Message validation +export const MESSAGE_MAX_LENGTH = 2000;