This commit is contained in:
2025-10-21 10:02:48 +02:00
parent c15b054151
commit 98974dcc2a
12 changed files with 109 additions and 192 deletions

View File

@@ -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() ||
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<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() ||
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) {

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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(),

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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)));

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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<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);
}
/**