diff --git a/Dockerfile.chat-service b/Dockerfile.chat-service index 262e190..5f687e2 100644 --- a/Dockerfile.chat-service +++ b/Dockerfile.chat-service @@ -37,4 +37,4 @@ EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 -ENTRYPOINT ["java", "-jar", "app.jar"] +ENTRYPOINT ["java", "-XX:+UseG1GC", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"] diff --git a/chat-service/mvnw b/chat-service/mvnw old mode 100644 new mode 100755 diff --git a/chat-service/src/main/java/com/blink/chatservice/chat/service/ChatServiceImpl.java b/chat-service/src/main/java/com/blink/chatservice/chat/service/ChatServiceImpl.java index 2b63e81..3ed6e54 100644 --- a/chat-service/src/main/java/com/blink/chatservice/chat/service/ChatServiceImpl.java +++ b/chat-service/src/main/java/com/blink/chatservice/chat/service/ChatServiceImpl.java @@ -20,6 +20,7 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; +import java.util.concurrent.CompletableFuture; @Service @RequiredArgsConstructor @@ -165,10 +166,12 @@ public Message sendMessage(String conversationId, String senderId, String body) } private void broadcast(Message msg) { - var resp = new com.blink.chatservice.websocket.dto.RealtimeMessageResponse(msg.getId(), msg.getConversationId(), msg.getSenderId(), msg.getRecipientId(), msg.getBody(), msg.getCreatedAt()); - messagingTemplate.convertAndSend("/topic/conversations/" + msg.getConversationId(), resp); - if (msg.getRecipientId() != null) messagingTemplate.convertAndSendToUser(msg.getRecipientId(), "/queue/messages", resp); - messagingTemplate.convertAndSendToUser(msg.getSenderId(), "/queue/messages", resp); + CompletableFuture.runAsync(() -> { + var resp = new com.blink.chatservice.websocket.dto.RealtimeMessageResponse(msg.getId(), msg.getConversationId(), msg.getSenderId(), msg.getRecipientId(), msg.getBody(), msg.getCreatedAt()); + messagingTemplate.convertAndSend("/topic/conversations/" + msg.getConversationId(), resp); + if (msg.getRecipientId() != null) messagingTemplate.convertAndSendToUser(msg.getRecipientId(), "/queue/messages", resp); + messagingTemplate.convertAndSendToUser(msg.getSenderId(), "/queue/messages", resp); + }); } @Override diff --git a/chat-service/src/main/java/com/blink/chatservice/security/SecurityConfig.java b/chat-service/src/main/java/com/blink/chatservice/security/SecurityConfig.java index fe40878..e53eec5 100644 --- a/chat-service/src/main/java/com/blink/chatservice/security/SecurityConfig.java +++ b/chat-service/src/main/java/com/blink/chatservice/security/SecurityConfig.java @@ -46,6 +46,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/swagger-ui/**", "/swagger-ui.html", "/swagger-ui/index.html", + "/actuator/health", + "/actuator/info", // Allowing public access to ws handshake, socket security is handled separately. "/ws/**" ).permitAll() diff --git a/chat-service/src/main/resources/application-prod.yaml b/chat-service/src/main/resources/application-prod.yaml index 8228752..77487c1 100644 --- a/chat-service/src/main/resources/application-prod.yaml +++ b/chat-service/src/main/resources/application-prod.yaml @@ -5,6 +5,7 @@ spring: data: mongodb: uri: ${MONGODB_URI} + auto-index-creation: false redis: host: ${SPRING_DATA_REDIS_HOST} port: ${SPRING_DATA_REDIS_PORT:6379} diff --git a/chat-service/src/test/java/com/blink/chatservice/chat/service/ChatServiceImplTest.java b/chat-service/src/test/java/com/blink/chatservice/chat/service/ChatServiceImplTest.java index 7f24a63..438013a 100644 --- a/chat-service/src/test/java/com/blink/chatservice/chat/service/ChatServiceImplTest.java +++ b/chat-service/src/test/java/com/blink/chatservice/chat/service/ChatServiceImplTest.java @@ -82,7 +82,7 @@ void sendMessage_shouldSaveMessageAndBroadcast() { assertNotNull(result); assertEquals("Hello", result.getBody()); verify(messageRepository).save(any(Message.class)); - verify(messagingTemplate, atLeastOnce()).convertAndSend(anyString(), any(Object.class)); + verify(messagingTemplate, timeout(1000).atLeastOnce()).convertAndSend(anyString(), any(Object.class)); } @Test diff --git a/frontend-v2/src/components/chat/MessageList.jsx b/frontend-v2/src/components/chat/MessageList.jsx index 844b611..ab4f538 100644 --- a/frontend-v2/src/components/chat/MessageList.jsx +++ b/frontend-v2/src/components/chat/MessageList.jsx @@ -1,5 +1,5 @@ import { useInfiniteQuery, useQueryClient, useMutation, useQuery } from '@tanstack/react-query'; -import { useEffect, useRef, useState, useLayoutEffect, useCallback } from 'react'; +import { useEffect, useRef, useState, useLayoutEffect, useCallback, memo } from 'react'; import { chatService, socketService, userService } from '../../services'; import { queryKeys } from '../../lib/queryClient'; import { useAuthStore, useChatStore } from '../../stores'; @@ -588,7 +588,7 @@ function renderMessageWithLinks(text, isOwn) { } // ─── Message bubble ────────────────────────────────────────────────── -function MessageBubble({ +const MessageBubble = memo(({ message, isOwn, showAvatar, @@ -598,7 +598,7 @@ function MessageBubble({ fallbackName, isGroup, isAI -}) { +}) => { const [isHovered, setIsHovered] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const queryClient = useQueryClient(); @@ -766,4 +766,4 @@ function MessageBubble({ ); -} +}); diff --git a/nginxv2.conf b/nginxv2.conf index 7943d23..56375a1 100644 --- a/nginxv2.conf +++ b/nginxv2.conf @@ -48,7 +48,8 @@ http { add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss: https:;" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' ws: wss: https:; object-src 'none'; frame-ancestors 'self'; base-uri 'self';" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Health check endpoint