This commit is contained in:
@@ -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<Message> sendGeneralMessage(@RequestBody Map<String, String> 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() ||
|
||||
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<Message> sendJobMessage(@RequestBody Map<String, String> 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() ||
|
||||
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) {
|
||||
|
||||
@@ -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<String, Object> 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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<String> candidateReceivers = new LinkedHashSet<>();
|
||||
|
||||
try {
|
||||
User currentUser = securityService.getCurrentDatabaseUser();
|
||||
// Get all AppUsers for the current user
|
||||
List<de.assecutor.votianlt.model.AppUser> 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() {
|
||||
|
||||
@@ -151,7 +151,7 @@ public class MessageDetailsView extends Main implements BeforeEnterObserver {
|
||||
clientName = Optional.ofNullable(client).map(AppUser::getBezeichnung).orElse("Unbekannter Teilnehmer");
|
||||
}
|
||||
|
||||
List<Message> allMessages = messageService.getMessagesForParticipantAscending(participantKey);
|
||||
List<Message> allMessages = messageService.getMessagesForAppUserAscending(participantKey);
|
||||
List<Message> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String> {
|
||||
HorizontalLayout headerLayout = createHeaderLayout(clientName);
|
||||
contentLayout.add(headerLayout);
|
||||
|
||||
List<Message> conversation = messageService.getMessagesForParticipantAscending(participantKey);
|
||||
List<Message> conversation = messageService.getMessagesForAppUserAscending(participantKey);
|
||||
Map<MessageType, List<Message>> messagesByType = conversation.stream()
|
||||
.collect(Collectors.groupingBy(message -> Optional.ofNullable(message.getMessageType()).orElse(MessageType.GENERAL)));
|
||||
|
||||
|
||||
@@ -13,17 +13,17 @@ import java.util.List;
|
||||
public interface MessageRepository extends MongoRepository<Message, ObjectId> {
|
||||
|
||||
/**
|
||||
* 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<Message> findByReceiverOrderByCreatedAtAsc(String receiver);
|
||||
|
||||
/**
|
||||
* Find all messages for a specific receiver (AppUser ID), ordered by creation time descending (newest first)
|
||||
*/
|
||||
List<Message> findByReceiverOrderByCreatedAtDesc(String receiver);
|
||||
|
||||
/**
|
||||
* Find all messages sent by a specific sender, ordered by creation time descending
|
||||
*/
|
||||
List<Message> findBySenderOrderByCreatedAtDesc(String sender);
|
||||
|
||||
/**
|
||||
* Find all unread messages for a specific receiver
|
||||
* Find all unread messages for a specific receiver (AppUser ID)
|
||||
*/
|
||||
List<Message> findByReceiverAndIsReadFalseOrderByCreatedAtDesc(String receiver);
|
||||
|
||||
@@ -33,27 +33,22 @@ public interface MessageRepository extends MongoRepository<Message, ObjectId> {
|
||||
List<Message> 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<Message> findByReceiverAndMessageTypeOrderByCreatedAtDesc(String receiver, MessageType messageType);
|
||||
|
||||
/**
|
||||
* Find all messages by origin (incoming/outgoing/server)
|
||||
* Find all messages by origin (CLIENT/SERVER)
|
||||
*/
|
||||
List<Message> findByOriginOrderByCreatedAtDesc(MessageOrigin origin);
|
||||
|
||||
/**
|
||||
* Find all messages between two users (in both directions)
|
||||
*/
|
||||
List<Message> 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<Message> findByReceiverAndJobIdOrderByCreatedAtDesc(String receiver, ObjectId jobId);
|
||||
|
||||
@@ -61,16 +56,4 @@ public interface MessageRepository extends MongoRepository<Message, ObjectId> {
|
||||
* Find all messages (for admin/overview), ordered by creation time descending
|
||||
*/
|
||||
List<Message> findAllByOrderByCreatedAtDesc();
|
||||
|
||||
/**
|
||||
* Find all messages where the sender or receiver matches the provided value,
|
||||
* ordered by creation time ascending.
|
||||
*/
|
||||
List<Message> findBySenderOrReceiverOrderByCreatedAtAsc(String sender, String receiver);
|
||||
|
||||
/**
|
||||
* Find all messages where the sender or receiver matches the provided value,
|
||||
* ordered by creation time descending.
|
||||
*/
|
||||
List<Message> findBySenderOrReceiverOrderByCreatedAtDesc(String sender, String receiver);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,22 +86,26 @@ 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<Message> getMessagesForParticipantAscending(String participant) {
|
||||
if (participant == null || participant.isBlank()) {
|
||||
public List<Message> 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<Message> getMessagesForParticipantDescending(String participant) {
|
||||
if (participant == null || participant.isBlank()) {
|
||||
public List<Message> getMessagesForAppUserDescending(String appUserId) {
|
||||
if (appUserId == null || appUserId.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return messageRepository.findBySenderOrReceiverOrderByCreatedAtDesc(participant, participant);
|
||||
return messageRepository.findByReceiverOrderByCreatedAtDesc(appUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user