213 lines
6 KiB
Go
213 lines
6 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
defaultHistoryLimit = 50
|
|
maxHistoryLimit = 100
|
|
)
|
|
|
|
// enrichMessageDTOsWithReactions fetches reactions for all messages and attaches them to DTOs.
|
|
func (h *MessageHandler) enrichMessageDTOsWithReactions(ctx context.Context, dtos []MessageDTO, messages []models.ChatMessage) {
|
|
ids := make([]uuid.UUID, len(messages))
|
|
for i, m := range messages {
|
|
ids[i] = m.ID
|
|
}
|
|
reactionsByMsg, err := h.reactionRepo.GetReactionsForMessageIDs(ctx, ids)
|
|
if err != nil {
|
|
h.logger.Warn("Failed to fetch reactions for messages", zap.Error(err))
|
|
return
|
|
}
|
|
for i := range dtos {
|
|
if r, ok := reactionsByMsg[dtos[i].ID]; ok {
|
|
dtos[i].Reactions = r
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *MessageHandler) HandleFetchHistory(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("conversation_id is required"))
|
|
return
|
|
}
|
|
|
|
if !h.rateLimiter.Allow(client.UserID, "fetch_history") {
|
|
client.SendJSON(NewErrorResponse("rate limit exceeded"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(ctx, client.UserID, *msg.ConversationID) {
|
|
client.SendJSON(NewErrorResponse("not allowed to read this conversation"))
|
|
return
|
|
}
|
|
|
|
limit := defaultHistoryLimit
|
|
if msg.Limit != nil && *msg.Limit > 0 {
|
|
limit = *msg.Limit
|
|
if limit > maxHistoryLimit {
|
|
limit = maxHistoryLimit
|
|
}
|
|
}
|
|
|
|
messages, err := h.msgRepo.GetConversationMessages(ctx, *msg.ConversationID, limit+1, 0)
|
|
if err != nil {
|
|
h.logger.Error("Failed to fetch history", zap.Error(err))
|
|
client.SendJSON(NewErrorResponse("failed to fetch history"))
|
|
return
|
|
}
|
|
|
|
hasMoreBefore := len(messages) > limit
|
|
if hasMoreBefore {
|
|
messages = messages[:limit]
|
|
}
|
|
|
|
dtos := make([]MessageDTO, 0, len(messages))
|
|
for _, m := range messages {
|
|
dtos = append(dtos, MessageDTO{
|
|
ID: m.ID,
|
|
ConversationID: m.ConversationID,
|
|
SenderID: m.SenderID,
|
|
Content: m.Content,
|
|
MessageType: m.MessageType,
|
|
ParentMessageID: m.ReplyToID, // ReplyToID is the DB column for threading
|
|
ReplyToID: m.ReplyToID,
|
|
IsPinned: m.IsPinned,
|
|
IsEdited: m.IsEdited,
|
|
IsDeleted: m.IsDeleted,
|
|
EditedAt: m.EditedAt,
|
|
Status: m.Status,
|
|
Metadata: m.Metadata,
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
})
|
|
}
|
|
h.enrichMessageDTOsWithReactions(ctx, dtos, messages)
|
|
|
|
client.SendJSON(NewHistoryChunkResponse(*msg.ConversationID, dtos, hasMoreBefore, false))
|
|
}
|
|
|
|
func (h *MessageHandler) HandleSearchMessages(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("conversation_id is required"))
|
|
return
|
|
}
|
|
if msg.Query == "" {
|
|
client.SendJSON(NewErrorResponse("query is required"))
|
|
return
|
|
}
|
|
|
|
if !h.rateLimiter.Allow(client.UserID, "search") {
|
|
client.SendJSON(NewErrorResponse("rate limit exceeded"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(ctx, client.UserID, *msg.ConversationID) {
|
|
client.SendJSON(NewErrorResponse("not allowed to read this conversation"))
|
|
return
|
|
}
|
|
|
|
limit := defaultHistoryLimit
|
|
if msg.Limit != nil && *msg.Limit > 0 {
|
|
limit = *msg.Limit
|
|
if limit > maxHistoryLimit {
|
|
limit = maxHistoryLimit
|
|
}
|
|
}
|
|
|
|
offset := 0
|
|
if msg.Offset != nil && *msg.Offset > 0 {
|
|
offset = *msg.Offset
|
|
}
|
|
|
|
messages, total, err := h.msgRepo.Search(ctx, *msg.ConversationID, msg.Query, limit, offset)
|
|
if err != nil {
|
|
h.logger.Error("Failed to search messages", zap.Error(err))
|
|
client.SendJSON(NewErrorResponse("failed to search messages"))
|
|
return
|
|
}
|
|
|
|
dtos := make([]MessageDTO, 0, len(messages))
|
|
for _, m := range messages {
|
|
dtos = append(dtos, MessageDTO{
|
|
ID: m.ID,
|
|
ConversationID: m.ConversationID,
|
|
SenderID: m.SenderID,
|
|
Content: m.Content,
|
|
MessageType: m.MessageType,
|
|
ParentMessageID: m.ReplyToID, // ReplyToID is the DB column for threading
|
|
ReplyToID: m.ReplyToID,
|
|
IsPinned: m.IsPinned,
|
|
IsEdited: m.IsEdited,
|
|
IsDeleted: m.IsDeleted,
|
|
EditedAt: m.EditedAt,
|
|
Status: m.Status,
|
|
Metadata: m.Metadata,
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
})
|
|
}
|
|
h.enrichMessageDTOsWithReactions(ctx, dtos, messages)
|
|
|
|
client.SendJSON(NewSearchResultsResponse(*msg.ConversationID, dtos, msg.Query, total))
|
|
}
|
|
|
|
func (h *MessageHandler) HandleSyncMessages(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("conversation_id is required"))
|
|
return
|
|
}
|
|
if msg.Since == nil {
|
|
client.SendJSON(NewErrorResponse("since is required"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(ctx, client.UserID, *msg.ConversationID) {
|
|
client.SendJSON(NewErrorResponse("not allowed to read this conversation"))
|
|
return
|
|
}
|
|
|
|
since, err := time.Parse(time.RFC3339, *msg.Since)
|
|
if err != nil {
|
|
client.SendJSON(NewErrorResponse("invalid since format, expected RFC3339"))
|
|
return
|
|
}
|
|
|
|
messages, err := h.msgRepo.GetMessagesSince(ctx, *msg.ConversationID, since, maxHistoryLimit)
|
|
if err != nil {
|
|
h.logger.Error("Failed to sync messages", zap.Error(err))
|
|
client.SendJSON(NewErrorResponse("failed to sync messages"))
|
|
return
|
|
}
|
|
|
|
dtos := make([]MessageDTO, 0, len(messages))
|
|
for _, m := range messages {
|
|
dtos = append(dtos, MessageDTO{
|
|
ID: m.ID,
|
|
ConversationID: m.ConversationID,
|
|
SenderID: m.SenderID,
|
|
Content: m.Content,
|
|
MessageType: m.MessageType,
|
|
ParentMessageID: m.ReplyToID, // ReplyToID is the DB column for threading
|
|
ReplyToID: m.ReplyToID,
|
|
IsPinned: m.IsPinned,
|
|
IsEdited: m.IsEdited,
|
|
IsDeleted: m.IsDeleted,
|
|
EditedAt: m.EditedAt,
|
|
Status: m.Status,
|
|
Metadata: m.Metadata,
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
})
|
|
}
|
|
h.enrichMessageDTOsWithReactions(ctx, dtos, messages)
|
|
|
|
client.SendJSON(NewSyncChunkResponse(*msg.ConversationID, dtos, time.Now()))
|
|
}
|