diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageApiController.java b/src/main/java/de/assecutor/votianlt/controller/MessageApiController.java index 260a651..1719bf8 100644 --- a/src/main/java/de/assecutor/votianlt/controller/MessageApiController.java +++ b/src/main/java/de/assecutor/votianlt/controller/MessageApiController.java @@ -31,25 +31,23 @@ public class MessageApiController { /** * Send a general message to a client * POST /api/messages/send - * Body: { "content": "message text", "sender": "username", "receiver": "username", "contentType": "TEXT|IMAGE" } + * Body: { "content": "message text", "receiver": "appUserId", "contentType": "TEXT|IMAGE" } */ @PostMapping("/send") public ResponseEntity sendGeneralMessage(@RequestBody Map request) { try { String content = request.get("content"); - String sender = request.get("sender"); String receiver = request.get("receiver"); MessageContentType contentType = resolveContentType(request.get("contentType")); - if (content == null || content.isBlank() || - sender == null || sender.isBlank() || + if (content == null || content.isBlank() || receiver == null || receiver.isBlank()) { log.warn("Invalid message request: missing required fields"); return ResponseEntity.badRequest().build(); } - Message message = messageService.sendGeneralMessageToClient(content, sender, receiver, contentType); - log.info("General message sent from {} to {}", sender, receiver); + Message message = messageService.sendGeneralMessageToClient(content, receiver, contentType); + log.info("General message sent to AppUser '{}'", receiver); return ResponseEntity.ok(message); } catch (IllegalArgumentException e) { @@ -64,21 +62,19 @@ public class MessageApiController { /** * Send a job-related message to a client * POST /api/messages/send-job-message - * Body: { "content": "message text", "sender": "username", "receiver": "username", + * Body: { "content": "message text", "receiver": "appUserId", * "jobId": "job id", "jobNumber": "job number", "contentType": "TEXT|IMAGE" } */ @PostMapping("/send-job-message") public ResponseEntity sendJobMessage(@RequestBody Map request) { try { String content = request.get("content"); - String sender = request.get("sender"); String receiver = request.get("receiver"); String jobIdStr = request.get("jobId"); String jobNumber = request.get("jobNumber"); MessageContentType contentType = resolveContentType(request.get("contentType")); - if (content == null || content.isBlank() || - sender == null || sender.isBlank() || + if (content == null || content.isBlank() || receiver == null || receiver.isBlank() || jobIdStr == null || jobIdStr.isBlank()) { log.warn("Invalid job message request: missing required fields"); @@ -86,8 +82,8 @@ public class MessageApiController { } ObjectId jobId = new ObjectId(jobIdStr); - Message message = messageService.sendJobMessageToClient(content, sender, receiver, contentType, jobId, jobNumber); - log.info("Job-related message sent from {} to {} for job {}", sender, receiver, jobNumber); + Message message = messageService.sendJobMessageToClient(content, receiver, contentType, jobId, jobNumber); + log.info("Job-related message sent to AppUser '{}' for job {}", receiver, jobNumber); return ResponseEntity.ok(message); } catch (IllegalArgumentException e) { diff --git a/src/main/java/de/assecutor/votianlt/controller/MessageController.java b/src/main/java/de/assecutor/votianlt/controller/MessageController.java index e402510..ded5f69 100644 --- a/src/main/java/de/assecutor/votianlt/controller/MessageController.java +++ b/src/main/java/de/assecutor/votianlt/controller/MessageController.java @@ -613,27 +613,19 @@ public class MessageController { * Handle incoming message from a client via MQTT. * Client sends to /server/{clientId}/message with payload: * { - * "sender": "appUserUsername", - * "receiver": "systemUserUsername", * "content": "message payload", * "contentType": "TEXT|IMAGE", * "jobId": "optional job id", - * "jobNumber": "optional job number", - * "clientId": "extracted from topic" + * "jobNumber": "optional job number" * } * - * Logic: - * 1. Extract clientId from topic (this is the AppUser ID) - * 2. Find AppUser by ID in database - * 3. Get owner (User) from AppUser.owner field - * 4. Set receiver = User ID, sender = AppUser ID + * The clientId is extracted from the MQTT topic and represents the AppUser ID. + * This clientId is stored as the receiver field in the message. */ public void handleIncomingMessage(Map payload) { log.info("MQTT Endpoint '/server/{clientId}/message' called with data: {}", payload); try { - ChatMessageInboundPayload inboundPayload = ChatMessageInboundPayload.fromPayload(payload); - // Extract clientId from payload (added by MqttV5ClientManager from topic) // The clientId IS the AppUser ID String clientId = payload.get("clientId") != null ? payload.get("clientId").toString() : null; @@ -643,53 +635,15 @@ public class MessageController { return; } - // Convert clientId (AppUser ID) to ObjectId - ObjectId appUserObjectId; - try { - appUserObjectId = new ObjectId(clientId); - } catch (IllegalArgumentException e) { - log.warn("Invalid clientId/AppUser ID '{}': {}", clientId, e.getMessage()); - return; - } + // Add clientId as receiver to the payload + payload.put("receiver", clientId); - // Find AppUser by ID - AppUser appUser = appUserService.findById(appUserObjectId); - if (appUser == null) { - log.warn("AppUser not found for clientId '{}'", clientId); - return; - } + // Parse the payload + ChatMessageInboundPayload inboundPayload = ChatMessageInboundPayload.fromPayload(payload); - // Get owner (User) of AppUser from the owner field - ObjectId ownerId = appUser.getOwner(); - if (ownerId == null) { - log.warn("AppUser '{}' has no owner, cannot determine receiver", clientId); - return; - } - - // Verify that owner exists - de.assecutor.votianlt.model.User owner = userService.findById(ownerId); - if (owner == null) { - log.warn("Owner User not found for AppUser '{}'", clientId); - return; - } - - // Convert owner ID to string for receiver field - String ownerIdString = ownerId.toHexString(); - - // Create payload with: - // - sender = AppUser ID (clientId) - // - receiver = User ID (owner's ID as string) - ChatMessageInboundPayload resolvedPayload = new ChatMessageInboundPayload( - clientId, // sender = AppUser ID - ownerIdString, // receiver = User ID - inboundPayload.content(), - inboundPayload.contentType(), - inboundPayload.jobId(), - inboundPayload.jobNumber() - ); - - messageService.receiveMessageFromClient(resolvedPayload); - log.info("Successfully saved incoming message from AppUser '{}' to User '{}'", clientId, ownerIdString); + // Save the message with receiver = AppUser ID (clientId) + messageService.receiveMessageFromClient(inboundPayload); + log.info("Successfully saved incoming message for AppUser '{}'", clientId); } catch (IllegalArgumentException validationError) { log.warn("Incoming chat message rejected: {}", validationError.getMessage()); } catch (Exception e) { diff --git a/src/main/java/de/assecutor/votianlt/dto/ChatMessageInboundPayload.java b/src/main/java/de/assecutor/votianlt/dto/ChatMessageInboundPayload.java index 6225396..36dee77 100644 --- a/src/main/java/de/assecutor/votianlt/dto/ChatMessageInboundPayload.java +++ b/src/main/java/de/assecutor/votianlt/dto/ChatMessageInboundPayload.java @@ -6,8 +6,9 @@ import org.bson.types.ObjectId; /** * Normalized payload for chat messages sent by mobile clients via MQTT. + * receiver = AppUser ID (clientId) extracted from MQTT topic */ -public record ChatMessageInboundPayload(String sender, String receiver, String content, +public record ChatMessageInboundPayload(String receiver, String content, MessageContentType contentType, ObjectId jobId, String jobNumber) { public ChatMessageInboundPayload { @@ -19,14 +20,13 @@ public record ChatMessageInboundPayload(String sender, String receiver, String c throw new IllegalArgumentException("payload must not be null"); } - String sender = extractRequiredString(payload, "sender"); String receiver = extractRequiredString(payload, "receiver"); String content = extractRequiredString(payload, "content"); MessageContentType contentType = extractContentType(payload.get("contentType")); ObjectId jobId = extractObjectId(payload.get("jobId"), "jobId"); String jobNumber = extractOptionalString(payload.get("jobNumber")); - return new ChatMessageInboundPayload(sender, receiver, content, contentType, jobId, jobNumber); + return new ChatMessageInboundPayload(receiver, content, contentType, jobId, jobNumber); } public boolean hasJobContext() { diff --git a/src/main/java/de/assecutor/votianlt/dto/ChatMessageOutboundPayload.java b/src/main/java/de/assecutor/votianlt/dto/ChatMessageOutboundPayload.java index f8cf67e..e23b85f 100644 --- a/src/main/java/de/assecutor/votianlt/dto/ChatMessageOutboundPayload.java +++ b/src/main/java/de/assecutor/votianlt/dto/ChatMessageOutboundPayload.java @@ -8,11 +8,10 @@ import java.time.LocalDateTime; /** * Outbound chat message payload published to MQTT subscribers. + * The receiver is implicit from the MQTT topic (/client/{appUserId}/message) */ public record ChatMessageOutboundPayload( String messageId, - String sender, - String receiver, String content, MessageContentType contentType, MessageOrigin origin, @@ -26,8 +25,6 @@ public record ChatMessageOutboundPayload( public static ChatMessageOutboundPayload fromMessage(Message message) { return new ChatMessageOutboundPayload( message.getIdAsString(), - message.getSender(), - message.getReceiver(), message.getContent(), message.getContentType(), message.getOrigin(), diff --git a/src/main/java/de/assecutor/votianlt/model/Message.java b/src/main/java/de/assecutor/votianlt/model/Message.java index e4aaede..6b5b5b4 100644 --- a/src/main/java/de/assecutor/votianlt/model/Message.java +++ b/src/main/java/de/assecutor/votianlt/model/Message.java @@ -37,13 +37,7 @@ public class Message { private MessageContentType contentType = MessageContentType.TEXT; /** - * Username of the sender (app user or system user) - */ - @Field("sender") - private String sender; - - /** - * Username of the receiver (app user or system user) + * AppUser ID (clientId) - the AppUser to whom this message belongs */ @Field("receiver") private String receiver; @@ -93,33 +87,33 @@ public class Message { /** * Constructor for general messages */ - public Message(String content, String sender, String receiver, MessageOrigin origin) { - this(content, sender, receiver, origin, MessageContentType.TEXT); + public Message(String content, String receiver, MessageOrigin origin) { + this(content, receiver, origin, MessageContentType.TEXT); } /** * Constructor for general messages with explicit content type */ - public Message(String content, String sender, String receiver, MessageOrigin origin, + public Message(String content, String receiver, MessageOrigin origin, MessageContentType contentType) { - initializeBaseFields(content, sender, receiver, origin, contentType); + initializeBaseFields(content, receiver, origin, contentType); this.messageType = MessageType.GENERAL; } /** * Constructor for job-related messages */ - public Message(String content, String sender, String receiver, MessageOrigin origin, + public Message(String content, String receiver, MessageOrigin origin, ObjectId jobId, String jobNumber) { - this(content, sender, receiver, origin, MessageContentType.TEXT, jobId, jobNumber); + this(content, receiver, origin, MessageContentType.TEXT, jobId, jobNumber); } /** * Constructor for job-related messages with explicit content type */ - public Message(String content, String sender, String receiver, MessageOrigin origin, + public Message(String content, String receiver, MessageOrigin origin, MessageContentType contentType, ObjectId jobId, String jobNumber) { - initializeBaseFields(content, sender, receiver, origin, contentType); + initializeBaseFields(content, receiver, origin, contentType); this.messageType = MessageType.JOB_RELATED; this.jobId = jobId; this.jobNumber = jobNumber; @@ -156,10 +150,9 @@ public class Message { return contentType != null ? contentType : MessageContentType.TEXT; } - private void initializeBaseFields(String content, String sender, String receiver, MessageOrigin origin, + private void initializeBaseFields(String content, String receiver, MessageOrigin origin, MessageContentType contentType) { this.content = content; - this.sender = sender; this.receiver = receiver; this.origin = origin; this.createdAt = LocalDateTime.now(); diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java index 1a721e5..c338d69 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java @@ -25,6 +25,7 @@ import com.vaadin.flow.server.menu.MenuEntry; import com.vaadin.flow.shared.Registration; import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.model.UserInvoiceData; +import de.assecutor.votianlt.pages.service.AppUserService; import de.assecutor.votianlt.pages.service.UserInvoiceDataService; import de.assecutor.votianlt.pages.view.EditProfileView; import de.assecutor.votianlt.security.SecurityService; @@ -34,9 +35,8 @@ import lombok.extern.slf4j.Slf4j; import static com.vaadin.flow.theme.lumo.LumoUtility.*; -import java.util.LinkedHashSet; +import java.util.List; import java.util.Optional; -import java.util.Set; @AnonymousAllowed @Slf4j @@ -47,6 +47,7 @@ public final class MainLayout extends AppLayout { private final UserInvoiceDataService userInvoiceDataService; private final MessageService messageService; private final MessageBadgeUpdateService messageBadgeUpdateService; + private final AppUserService appUserService; private Div headerRef; private Scroller navRef; private Component userMenuRef; @@ -55,11 +56,13 @@ public final class MainLayout extends AppLayout { private Registration badgeUpdateRegistration; // Track badge update listener registration public MainLayout(SecurityService securityService, UserInvoiceDataService userInvoiceDataService, - MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService) { + MessageService messageService, MessageBadgeUpdateService messageBadgeUpdateService, + AppUserService appUserService) { this.securityService = securityService; this.userInvoiceDataService = userInvoiceDataService; this.messageService = messageService; this.messageBadgeUpdateService = messageBadgeUpdateService; + this.appUserService = appUserService; setPrimarySection(Section.DRAWER); // Always build the drawer; keep references and toggle visibility on attach and @@ -218,47 +221,28 @@ public final class MainLayout extends AppLayout { return 0; } - Set candidateReceivers = new LinkedHashSet<>(); - try { - User currentUser = securityService.getCurrentDatabaseUser(); + // Get all AppUsers for the current user + List appUsers = appUserService.findByCurrentUser(); - if (currentUser != null) { - // Add User ID (ObjectId as string) - this is now the primary receiver identifier - if (currentUser.getId() != null) { - candidateReceivers.add(currentUser.getId().toHexString()); - } + if (appUsers == null || appUsers.isEmpty()) { + return 0; + } - // Also add email for backward compatibility with old messages - String email = Optional.ofNullable(currentUser.getEmail()).map(String::trim).orElse(""); - if (!email.isBlank()) { - candidateReceivers.add(email); - } - - // Also add full name for backward compatibility - String fullName = ((Optional.ofNullable(currentUser.getFirstname()).orElse("") + " " - + Optional.ofNullable(currentUser.getName()).orElse(""))).trim(); - if (!fullName.isBlank()) { - candidateReceivers.add(fullName); + // Count unread messages for all AppUsers (receiver = AppUser ID) + long unread = 0; + for (de.assecutor.votianlt.model.AppUser appUser : appUsers) { + if (appUser != null && appUser.getId() != null) { + String appUserId = appUser.getId().toHexString(); + unread += messageService.getUnreadMessageCount(appUserId); } } - } catch (RuntimeException ignored) { - // Fallback to username only - } - Optional.ofNullable(securityService.getCurrentUsername()).map(String::trim).filter(name -> !name.isBlank()) - .ifPresent(candidateReceivers::add); - - if (candidateReceivers.isEmpty()) { + return unread; + } catch (RuntimeException e) { + log.error("Error resolving unread message count: {}", e.getMessage(), e); return 0; } - - long unread = 0; - for (String receiver : candidateReceivers) { - unread += messageService.getUnreadMessageCount(receiver); - } - - return unread; } private Component createUserMenu() { diff --git a/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java b/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java index 923d751..168be7d 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/MessageDetailsView.java @@ -151,7 +151,7 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { clientName = Optional.ofNullable(client).map(AppUser::getBezeichnung).orElse("Unbekannter Teilnehmer"); } - List allMessages = messageService.getMessagesForParticipantAscending(participantKey); + List allMessages = messageService.getMessagesForAppUserAscending(participantKey); List filteredMessages = filterMessagesForConversation(allMessages, conversationId); this.jobConversation = conversationId != null && conversationId.startsWith("job-"); @@ -808,10 +808,12 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { try { Message saved; if (jobConversation) { - saved = messageService.sendJobMessageToClient(payload, sender, participantKey, + // participantKey = AppUser ID (receiver) + saved = messageService.sendJobMessageToClient(payload, participantKey, contentType, jobIdContext, jobNumberContext); } else { - saved = messageService.sendGeneralMessageToClient(payload, sender, participantKey, + // participantKey = AppUser ID (receiver) + saved = messageService.sendGeneralMessageToClient(payload, participantKey, contentType); } @@ -1018,12 +1020,11 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver { return; } - // Check if message involves the current participant - boolean involvesParticipant = participantKey.equals(message.getSender()) - || participantKey.equals(message.getReceiver()); + // Check if message belongs to the current participant (receiver = AppUser ID) + boolean involvesParticipant = participantKey.equals(message.getReceiver()); if (!involvesParticipant) { - log.debug("Message does not involve current participant, ignoring"); + log.debug("Message does not belong to current participant, ignoring"); return; } diff --git a/src/main/java/de/assecutor/votianlt/pages/view/MessagesView.java b/src/main/java/de/assecutor/votianlt/pages/view/MessagesView.java index 0b94a1d..b095119 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/MessagesView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/MessagesView.java @@ -258,9 +258,7 @@ public class MessagesView extends Main { if (message == null) { return null; } - if (message.getOrigin() == MessageOrigin.CLIENT) { - return message.getSender(); - } + // All messages now have receiver = AppUser ID (clientId) return message.getReceiver(); } diff --git a/src/main/java/de/assecutor/votianlt/pages/view/UserMessagesView.java b/src/main/java/de/assecutor/votianlt/pages/view/UserMessagesView.java index 69b0dd9..b89a124 100644 --- a/src/main/java/de/assecutor/votianlt/pages/view/UserMessagesView.java +++ b/src/main/java/de/assecutor/votianlt/pages/view/UserMessagesView.java @@ -85,7 +85,7 @@ public class UserMessagesView extends Main implements HasUrlParameter { HorizontalLayout headerLayout = createHeaderLayout(clientName); contentLayout.add(headerLayout); - List conversation = messageService.getMessagesForParticipantAscending(participantKey); + List conversation = messageService.getMessagesForAppUserAscending(participantKey); Map> messagesByType = conversation.stream() .collect(Collectors.groupingBy(message -> Optional.ofNullable(message.getMessageType()).orElse(MessageType.GENERAL))); diff --git a/src/main/java/de/assecutor/votianlt/repository/MessageRepository.java b/src/main/java/de/assecutor/votianlt/repository/MessageRepository.java index f7f173b..cd3d5d0 100644 --- a/src/main/java/de/assecutor/votianlt/repository/MessageRepository.java +++ b/src/main/java/de/assecutor/votianlt/repository/MessageRepository.java @@ -13,17 +13,17 @@ import java.util.List; public interface MessageRepository extends MongoRepository { /** - * Find all messages for a specific receiver, ordered by creation time descending (newest first) + * Find all messages for a specific receiver (AppUser ID), ordered by creation time ascending (oldest first) + */ + List findByReceiverOrderByCreatedAtAsc(String receiver); + + /** + * Find all messages for a specific receiver (AppUser ID), ordered by creation time descending (newest first) */ List findByReceiverOrderByCreatedAtDesc(String receiver); /** - * Find all messages sent by a specific sender, ordered by creation time descending - */ - List findBySenderOrderByCreatedAtDesc(String sender); - - /** - * Find all unread messages for a specific receiver + * Find all unread messages for a specific receiver (AppUser ID) */ List findByReceiverAndIsReadFalseOrderByCreatedAtDesc(String receiver); @@ -33,27 +33,22 @@ public interface MessageRepository extends MongoRepository { List findByJobIdOrderByCreatedAtDesc(ObjectId jobId); /** - * Find all messages of a specific type for a receiver + * Find all messages of a specific type for a receiver (AppUser ID) */ List findByReceiverAndMessageTypeOrderByCreatedAtDesc(String receiver, MessageType messageType); /** - * Find all messages by origin (incoming/outgoing/server) + * Find all messages by origin (CLIENT/SERVER) */ List findByOriginOrderByCreatedAtDesc(MessageOrigin origin); /** - * Find all messages between two users (in both directions) - */ - List findBySenderAndReceiverOrderByCreatedAtAsc(String sender, String receiver); - - /** - * Find all unread messages count for a specific receiver + * Find all unread messages count for a specific receiver (AppUser ID) */ long countByReceiverAndIsReadFalse(String receiver); /** - * Find all messages for a specific receiver and job + * Find all messages for a specific receiver (AppUser ID) and job */ List findByReceiverAndJobIdOrderByCreatedAtDesc(String receiver, ObjectId jobId); @@ -61,16 +56,4 @@ public interface MessageRepository extends MongoRepository { * Find all messages (for admin/overview), ordered by creation time descending */ List findAllByOrderByCreatedAtDesc(); - - /** - * Find all messages where the sender or receiver matches the provided value, - * ordered by creation time ascending. - */ - List findBySenderOrReceiverOrderByCreatedAtAsc(String sender, String receiver); - - /** - * Find all messages where the sender or receiver matches the provided value, - * ordered by creation time descending. - */ - List findBySenderOrReceiverOrderByCreatedAtDesc(String sender, String receiver); } diff --git a/src/main/java/de/assecutor/votianlt/service/MessageBroadcaster.java b/src/main/java/de/assecutor/votianlt/service/MessageBroadcaster.java index c0641e2..0d4680d 100644 --- a/src/main/java/de/assecutor/votianlt/service/MessageBroadcaster.java +++ b/src/main/java/de/assecutor/votianlt/service/MessageBroadcaster.java @@ -64,7 +64,8 @@ public class MessageBroadcaster { @EventListener public void onMessageReceived(MessageReceivedEvent event) { Message message = event.getMessage(); - log.info("MessageBroadcaster received event for message from: {}", message.getSender()); + log.info("MessageBroadcaster received event for message with origin {} for receiver {}", + message.getOrigin(), message.getReceiver()); broadcast(message); } } diff --git a/src/main/java/de/assecutor/votianlt/service/MessageService.java b/src/main/java/de/assecutor/votianlt/service/MessageService.java index 7847db8..d649719 100644 --- a/src/main/java/de/assecutor/votianlt/service/MessageService.java +++ b/src/main/java/de/assecutor/votianlt/service/MessageService.java @@ -41,20 +41,22 @@ public class MessageService { * Save a message to the database */ public Message saveMessage(Message message) { - log.info("Saving message from {} to {}", message.getSender(), message.getReceiver()); + log.info("Saving message with origin {} for receiver {}", message.getOrigin(), message.getReceiver()); return messageRepository.save(message); } /** * Send a general message to a client via MQTT + * @param content Message content + * @param receiver AppUser ID (clientId) */ - public Message sendGeneralMessageToClient(String content, String sender, String receiver) { - return sendGeneralMessageToClient(content, sender, receiver, MessageContentType.TEXT); + public Message sendGeneralMessageToClient(String content, String receiver) { + return sendGeneralMessageToClient(content, receiver, MessageContentType.TEXT); } - public Message sendGeneralMessageToClient(String content, String sender, String receiver, + public Message sendGeneralMessageToClient(String content, String receiver, MessageContentType contentType) { - Message message = new Message(content, sender, receiver, MessageOrigin.SERVER, contentType); + Message message = new Message(content, receiver, MessageOrigin.SERVER, contentType); message = saveMessage(message); publishMessageToMqtt(message, receiver); return message; @@ -62,16 +64,20 @@ public class MessageService { /** * Send a job-related message to a client via MQTT + * @param content Message content + * @param receiver AppUser ID (clientId) + * @param jobId Job ObjectId + * @param jobNumber Job number */ - public Message sendJobMessageToClient(String content, String sender, String receiver, + public Message sendJobMessageToClient(String content, String receiver, ObjectId jobId, String jobNumber) { - return sendJobMessageToClient(content, sender, receiver, MessageContentType.TEXT, jobId, jobNumber); + return sendJobMessageToClient(content, receiver, MessageContentType.TEXT, jobId, jobNumber); } - public Message sendJobMessageToClient(String content, String sender, String receiver, + public Message sendJobMessageToClient(String content, String receiver, MessageContentType contentType, ObjectId jobId, String jobNumber) { JobContext context = resolveJobContext(jobId, jobNumber); - Message message = new Message(content, sender, receiver, MessageOrigin.SERVER, contentType, + Message message = new Message(content, receiver, MessageOrigin.SERVER, contentType, context.jobId(), context.jobNumber()); message = saveMessage(message); publishMessageToMqtt(message, receiver); @@ -80,24 +86,28 @@ public class MessageService { /** * Handle incoming message from a client + * @param payload Inbound message payload where receiver = AppUser ID (clientId) */ public Message receiveMessageFromClient(ChatMessageInboundPayload payload) { Message message; MessageContentType contentType = payload.contentType(); if (payload.hasJobContext()) { JobContext context = resolveJobContext(payload.jobId(), payload.jobNumber()); - message = new Message(payload.content(), payload.sender(), payload.receiver(), + // receiver = AppUser ID (clientId) + message = new Message(payload.content(), payload.receiver(), MessageOrigin.CLIENT, contentType, context.jobId(), context.jobNumber()); } else { - message = new Message(payload.content(), payload.sender(), payload.receiver(), + // receiver = AppUser ID (clientId) + message = new Message(payload.content(), payload.receiver(), MessageOrigin.CLIENT, contentType); } message = saveMessage(message); - + // Publish event to notify UI components about the new message - log.info("Publishing MessageReceivedEvent for message from {}", message.getSender()); + log.info("Publishing MessageReceivedEvent for message with origin {} for receiver {}", + message.getOrigin(), message.getReceiver()); eventPublisher.publishEvent(new MessageReceivedEvent(this, message)); - + return message; } @@ -144,25 +154,25 @@ public class MessageService { } /** - * Get all messages a participant sent or received (oldest first) to reconstruct - * the conversation timeline. + * Get all messages for a specific AppUser (by receiver field), ordered by creation time ascending (oldest first) + * @param appUserId AppUser ID (clientId) */ - public List getMessagesForParticipantAscending(String participant) { - if (participant == null || participant.isBlank()) { + public List getMessagesForAppUserAscending(String appUserId) { + if (appUserId == null || appUserId.isBlank()) { return Collections.emptyList(); } - return messageRepository.findBySenderOrReceiverOrderByCreatedAtAsc(participant, participant); + return messageRepository.findByReceiverOrderByCreatedAtAsc(appUserId); } /** - * Get all messages a participant sent or received (newest first) for - * quick summaries. + * Get all messages for a specific AppUser (by receiver field), ordered by creation time descending + * @param appUserId AppUser ID (clientId) */ - public List getMessagesForParticipantDescending(String participant) { - if (participant == null || participant.isBlank()) { + public List getMessagesForAppUserDescending(String appUserId) { + if (appUserId == null || appUserId.isBlank()) { return Collections.emptyList(); } - return messageRepository.findBySenderOrReceiverOrderByCreatedAtDesc(participant, participant); + return messageRepository.findByReceiverOrderByCreatedAtDesc(appUserId); } /**