Add chat websocket flow
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import com.petshop.backend.security.JwtUtil;
|
||||||
|
import com.petshop.backend.service.ChatService;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.messaging.MessageChannel;
|
||||||
|
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||||
|
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||||
|
import org.springframework.messaging.support.ChannelInterceptor;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
||||||
|
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final ChatService chatService;
|
||||||
|
|
||||||
|
public WebSocketAuthChannelInterceptor(JwtUtil jwtUtil, UserRepository userRepository, ChatService chatService) {
|
||||||
|
this.jwtUtil = jwtUtil;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.chatService = chatService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||||
|
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
|
||||||
|
StompCommand command = accessor.getCommand();
|
||||||
|
|
||||||
|
if (command == null) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StompCommand.CONNECT.equals(command)) {
|
||||||
|
String tokenHeader = firstHeader(accessor, "Authorization");
|
||||||
|
String token = extractToken(tokenHeader != null ? tokenHeader : firstHeader(accessor, "token"));
|
||||||
|
if (token == null || token.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Missing websocket token");
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = jwtUtil.extractUsername(token);
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
if (user.getActive() == null || !user.getActive()) {
|
||||||
|
throw new IllegalArgumentException("User account is inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||||
|
user.getUsername(),
|
||||||
|
null,
|
||||||
|
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))
|
||||||
|
);
|
||||||
|
accessor.setUser(authentication);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
Principal principal = accessor.getUser();
|
||||||
|
if (principal == null) {
|
||||||
|
throw new IllegalArgumentException("Unauthenticated websocket session");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userRepository.findByUsername(principal.getName())
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
|
||||||
|
if (StompCommand.SUBSCRIBE.equals(command)) {
|
||||||
|
authorizeSubscription(accessor.getDestination(), user);
|
||||||
|
} else if (StompCommand.SEND.equals(command)) {
|
||||||
|
authorizeSend(accessor.getDestination(), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authorizeSubscription(String destination, User user) {
|
||||||
|
if (destination == null || destination.startsWith("/user/queue/")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("/topic/chat/conversations".equals(destination)) {
|
||||||
|
if (user.getRole() == User.Role.CUSTOMER) {
|
||||||
|
throw new IllegalArgumentException("Customers cannot subscribe to staff conversation feed");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long conversationId = extractConversationId(destination, "/topic/chat/conversations/");
|
||||||
|
if (conversationId != null && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Not authorized to subscribe to destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authorizeSend(String destination, User user) {
|
||||||
|
Long conversationId = extractConversationId(destination, "/app/chat/conversations/");
|
||||||
|
if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Not authorized to send to destination");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long extractConversationId(String destination, String prefix) {
|
||||||
|
if (destination == null || !destination.startsWith(prefix)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String suffix = destination.substring(prefix.length());
|
||||||
|
String[] parts = suffix.split("/");
|
||||||
|
if (parts.length == 0 || parts[0].isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Long.parseLong(parts[0]);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstHeader(StompHeaderAccessor accessor, String name) {
|
||||||
|
List<String> values = accessor.getNativeHeader(name);
|
||||||
|
return values == null || values.isEmpty() ? null : values.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractToken(String rawValue) {
|
||||||
|
if (rawValue == null || rawValue.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return rawValue.startsWith("Bearer ") ? rawValue.substring(7) : rawValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.petshop.backend.config;
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.messaging.simp.config.ChannelRegistration;
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||||
@@ -10,15 +11,29 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
|
|||||||
@EnableWebSocketMessageBroker
|
@EnableWebSocketMessageBroker
|
||||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||||
|
|
||||||
|
private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor;
|
||||||
|
|
||||||
|
public WebSocketConfig(WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor) {
|
||||||
|
this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||||
config.enableSimpleBroker("/topic", "/queue");
|
config.enableSimpleBroker("/topic", "/queue");
|
||||||
config.setApplicationDestinationPrefixes("/app");
|
config.setApplicationDestinationPrefixes("/app");
|
||||||
|
config.setUserDestinationPrefix("/user");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||||
|
registration.interceptors(webSocketAuthChannelInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
registry.addEndpoint("/ws/chat")
|
registry.addEndpoint("/ws/chat")
|
||||||
|
.setAllowedOriginPatterns("*");
|
||||||
|
registry.addEndpoint("/ws/chat-sockjs")
|
||||||
.setAllowedOriginPatterns("*")
|
.setAllowedOriginPatterns("*")
|
||||||
.withSockJS();
|
.withSockJS();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.petshop.backend.dto.chat.MessageResponse;
|
|||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.CustomerRepository;
|
import com.petshop.backend.repository.CustomerRepository;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import com.petshop.backend.service.ChatRealtimeService;
|
||||||
import com.petshop.backend.service.ChatService;
|
import com.petshop.backend.service.ChatService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -24,11 +25,13 @@ import java.util.List;
|
|||||||
public class ChatController {
|
public class ChatController {
|
||||||
|
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
|
private final ChatRealtimeService chatRealtimeService;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
|
|
||||||
public ChatController(ChatService chatService, UserRepository userRepository, CustomerRepository customerRepository) {
|
public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, CustomerRepository customerRepository) {
|
||||||
this.chatService = chatService;
|
this.chatService = chatService;
|
||||||
|
this.chatRealtimeService = chatRealtimeService;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.customerRepository = customerRepository;
|
this.customerRepository = customerRepository;
|
||||||
}
|
}
|
||||||
@@ -44,6 +47,7 @@ public class ChatController {
|
|||||||
public ResponseEntity<ConversationResponse> createConversation(@Valid @RequestBody ConversationRequest request) {
|
public ResponseEntity<ConversationResponse> createConversation(@Valid @RequestBody ConversationRequest request) {
|
||||||
User user = getCurrentUser();
|
User user = getCurrentUser();
|
||||||
ConversationResponse response = chatService.createConversation(user.getId(), request);
|
ConversationResponse response = chatService.createConversation(user.getId(), request);
|
||||||
|
chatRealtimeService.publishNewConversation(response);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,8 @@ public class ChatController {
|
|||||||
@Valid @RequestBody MessageRequest request) {
|
@Valid @RequestBody MessageRequest request) {
|
||||||
User user = getCurrentUser();
|
User user = getCurrentUser();
|
||||||
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
||||||
|
chatRealtimeService.publishMessage(id, message);
|
||||||
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.chat.MessageRequest;
|
||||||
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import com.petshop.backend.service.ChatRealtimeService;
|
||||||
|
import com.petshop.backend.service.ChatService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||||
|
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||||
|
import org.springframework.messaging.handler.annotation.Payload;
|
||||||
|
import org.springframework.messaging.simp.annotation.SendToUser;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class ChatWebSocketController {
|
||||||
|
|
||||||
|
private final ChatService chatService;
|
||||||
|
private final ChatRealtimeService chatRealtimeService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository) {
|
||||||
|
this.chatService = chatService;
|
||||||
|
this.chatRealtimeService = chatRealtimeService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MessageMapping("/chat/conversations/{id}/messages")
|
||||||
|
@SendToUser("/queue/chat/errors")
|
||||||
|
public void sendMessage(@DestinationVariable Long id, @Valid @Payload MessageRequest request, Authentication authentication) {
|
||||||
|
User user = userRepository.findByUsername(authentication.getName())
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
|
||||||
|
chatRealtimeService.publishMessage(id, message);
|
||||||
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ public class SecurityConfig {
|
|||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll()
|
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll()
|
||||||
.requestMatchers("/api/v1/health").permitAll()
|
.requestMatchers("/api/v1/health").permitAll()
|
||||||
|
.requestMatchers("/ws/chat/**", "/ws/chat-sockjs/**").permitAll()
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll()
|
.requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
|
.requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||||
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
|
import com.petshop.backend.entity.Conversation;
|
||||||
|
import com.petshop.backend.entity.Customer;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
|
import com.petshop.backend.repository.ConversationRepository;
|
||||||
|
import com.petshop.backend.repository.CustomerRepository;
|
||||||
|
import com.petshop.backend.repository.MessageRepository;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ChatRealtimeService {
|
||||||
|
|
||||||
|
private final SimpMessagingTemplate messagingTemplate;
|
||||||
|
private final ConversationRepository conversationRepository;
|
||||||
|
private final MessageRepository messageRepository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public ChatRealtimeService(SimpMessagingTemplate messagingTemplate, ConversationRepository conversationRepository, MessageRepository messageRepository, CustomerRepository customerRepository, UserRepository userRepository) {
|
||||||
|
this.messagingTemplate = messagingTemplate;
|
||||||
|
this.conversationRepository = conversationRepository;
|
||||||
|
this.messageRepository = messageRepository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishNewConversation(ConversationResponse conversation) {
|
||||||
|
messagingTemplate.convertAndSend("/topic/chat/conversations", conversation);
|
||||||
|
sendConversationToCustomerQueue(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishMessage(Long conversationId, MessageResponse message) {
|
||||||
|
messagingTemplate.convertAndSend("/topic/chat/conversations/" + conversationId, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void publishConversationUpdate(Long conversationId) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
List<com.petshop.backend.entity.Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||||
|
String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent();
|
||||||
|
ConversationResponse response = ConversationResponse.fromEntity(conversation, lastMessage);
|
||||||
|
|
||||||
|
messagingTemplate.convertAndSend("/topic/chat/conversations", response);
|
||||||
|
sendConversationToCustomerQueue(response);
|
||||||
|
sendConversationToStaffQueue(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendConversationToCustomerQueue(ConversationResponse conversation) {
|
||||||
|
Customer customer = customerRepository.findById(conversation.getCustomerId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Customer not found"));
|
||||||
|
if (customer.getUserId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
User customerUser = userRepository.findById(customer.getUserId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||||
|
messagingTemplate.convertAndSendToUser(customerUser.getUsername(), "/queue/chat/conversations", conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendConversationToStaffQueue(ConversationResponse conversation) {
|
||||||
|
if (conversation.getStaffId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
User staffUser = userRepository.findById(conversation.getStaffId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||||
|
messagingTemplate.convertAndSendToUser(staffUser.getUsername(), "/queue/chat/conversations", conversation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,14 +94,11 @@ public class ChatService {
|
|||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (!hasConversationAccess(conversation, userId, role)) {
|
||||||
if (role == User.Role.CUSTOMER) {
|
if (role == User.Role.CUSTOMER) {
|
||||||
Customer customer = customerRepository.findByUserId(userId)
|
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user"));
|
|
||||||
if (!conversation.getCustomerId().equals(customer.getCustomerId())) {
|
|
||||||
throw new AccessDeniedException("You can only view your own conversations");
|
throw new AccessDeniedException("You can only view your own conversations");
|
||||||
}
|
}
|
||||||
} else if (role == User.Role.STAFF) {
|
if (role == User.Role.STAFF) {
|
||||||
if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) {
|
|
||||||
throw new AccessDeniedException("You can only view conversations assigned to you or unassigned conversations");
|
throw new AccessDeniedException("You can only view conversations assigned to you or unassigned conversations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,14 +114,11 @@ public class ChatService {
|
|||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (!hasConversationAccess(conversation, userId, role)) {
|
||||||
if (role == User.Role.CUSTOMER) {
|
if (role == User.Role.CUSTOMER) {
|
||||||
Customer customer = customerRepository.findByUserId(userId)
|
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user"));
|
|
||||||
if (!conversation.getCustomerId().equals(customer.getCustomerId())) {
|
|
||||||
throw new AccessDeniedException("You can only send messages to your own conversations");
|
throw new AccessDeniedException("You can only send messages to your own conversations");
|
||||||
}
|
}
|
||||||
} else if (role == User.Role.STAFF) {
|
if (role == User.Role.STAFF) {
|
||||||
if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) {
|
|
||||||
throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations");
|
throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,14 +142,11 @@ public class ChatService {
|
|||||||
Conversation conversation = conversationRepository.findById(conversationId)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (!hasConversationAccess(conversation, userId, role)) {
|
||||||
if (role == User.Role.CUSTOMER) {
|
if (role == User.Role.CUSTOMER) {
|
||||||
Customer customer = customerRepository.findByUserId(userId)
|
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user"));
|
|
||||||
if (!conversation.getCustomerId().equals(customer.getCustomerId())) {
|
|
||||||
throw new AccessDeniedException("You can only view messages from your own conversations");
|
throw new AccessDeniedException("You can only view messages from your own conversations");
|
||||||
}
|
}
|
||||||
} else if (role == User.Role.STAFF) {
|
if (role == User.Role.STAFF) {
|
||||||
if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) {
|
|
||||||
throw new AccessDeniedException("You can only view messages from conversations assigned to you or unassigned conversations");
|
throw new AccessDeniedException("You can only view messages from conversations assigned to you or unassigned conversations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,4 +156,28 @@ public class ChatService {
|
|||||||
.map(MessageResponse::fromEntity)
|
.map(MessageResponse::fromEntity)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasConversationAccess(Long conversationId, Long userId, User.Role role) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
return hasConversationAccess(conversation, userId, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasConversationAccess(Conversation conversation, Long userId, User.Role role) {
|
||||||
|
if (role == User.Role.ADMIN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == User.Role.CUSTOMER) {
|
||||||
|
Customer customer = customerRepository.findByUserId(userId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user"));
|
||||||
|
return conversation.getCustomerId().equals(customer.getCustomerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == User.Role.STAFF) {
|
||||||
|
return conversation.getStaffId() == null || conversation.getStaffId().equals(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user