- Implement full MessageHandler dispatch with all 18 incoming message types - Add handler_messages.go: SendMessage, EditMessage, DeleteMessage with ownership checks - Add handler_rooms.go: JoinConversation, LeaveConversation - Add handler_history.go: FetchHistory (cursor-based), SearchMessages (ILIKE), SyncMessages - Add handler_realtime.go: Typing, MarkAsRead, Delivered, AddReaction, RemoveReaction - Add handler_calls.go: WebRTC signaling relay (CallOffer/Answer/ICE/Hangup/Reject) - Add PermissionService: CanRead/CanSend/CanJoin/CanModerate based on room_members - Add RateLimiter: per-user per-action sliding window (in-memory) - Wire all dependencies in router.go setupChatWebSocket
170 lines
4.3 KiB
Go
170 lines
4.3 KiB
Go
package chat
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (h *MessageHandler) HandleSendMessage(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("conversation_id is required"))
|
|
return
|
|
}
|
|
if msg.Content == "" {
|
|
client.SendJSON(NewErrorResponse("content is required"))
|
|
return
|
|
}
|
|
|
|
if !h.rateLimiter.Allow(client.UserID, "send_message") {
|
|
client.SendJSON(NewErrorResponse("rate limit exceeded"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanSend(ctx, client.UserID, *msg.ConversationID) {
|
|
client.SendJSON(NewErrorResponse("not allowed to send messages in this conversation"))
|
|
return
|
|
}
|
|
|
|
var metadata []byte
|
|
if len(msg.Attachments) > 0 {
|
|
metadata, _ = json.Marshal(map[string]interface{}{
|
|
"attachments": msg.Attachments,
|
|
})
|
|
}
|
|
|
|
chatMsg := &models.ChatMessage{
|
|
ID: uuid.New(),
|
|
ConversationID: *msg.ConversationID,
|
|
SenderID: client.UserID,
|
|
Content: msg.Content,
|
|
MessageType: "text",
|
|
ParentMessageID: msg.ParentMessageID,
|
|
Status: "sent",
|
|
Metadata: metadata,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
if err := h.msgRepo.Create(ctx, chatMsg); err != nil {
|
|
h.logger.Error("Failed to save message",
|
|
zap.Error(err),
|
|
zap.String("user_id", client.UserID.String()))
|
|
client.SendJSON(NewErrorResponse("failed to send message"))
|
|
return
|
|
}
|
|
|
|
client.SendJSON(NewActionConfirmedResponse("message_sent", true))
|
|
|
|
outgoing := NewNewMessageResponse(
|
|
chatMsg.ConversationID,
|
|
chatMsg.ID,
|
|
chatMsg.SenderID,
|
|
chatMsg.Content,
|
|
chatMsg.CreatedAt,
|
|
msg.Attachments,
|
|
)
|
|
|
|
data, _ := json.Marshal(outgoing)
|
|
h.hub.BroadcastToRoom(*msg.ConversationID, data, nil)
|
|
|
|
if h.pubsub != nil {
|
|
_ = h.pubsub.Publish(ctx, *msg.ConversationID, data)
|
|
}
|
|
}
|
|
|
|
func (h *MessageHandler) HandleEditMessage(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.MessageID == nil || msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("message_id and conversation_id are required"))
|
|
return
|
|
}
|
|
if msg.NewContent == "" {
|
|
client.SendJSON(NewErrorResponse("new_content is required"))
|
|
return
|
|
}
|
|
|
|
chatMsg, err := h.msgRepo.GetByID(ctx, *msg.MessageID)
|
|
if err != nil {
|
|
client.SendJSON(NewErrorResponse("message not found"))
|
|
return
|
|
}
|
|
|
|
if chatMsg.SenderID != client.UserID {
|
|
client.SendJSON(NewErrorResponse("can only edit your own messages"))
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
chatMsg.Content = msg.NewContent
|
|
chatMsg.IsEdited = true
|
|
chatMsg.EditedAt = &now
|
|
|
|
if err := h.msgRepo.Update(ctx, chatMsg); err != nil {
|
|
h.logger.Error("Failed to update message", zap.Error(err))
|
|
client.SendJSON(NewErrorResponse("failed to edit message"))
|
|
return
|
|
}
|
|
|
|
client.SendJSON(NewActionConfirmedResponse("message_edited", true))
|
|
|
|
outgoing := NewMessageEditedResponse(
|
|
chatMsg.ID,
|
|
chatMsg.ConversationID,
|
|
client.UserID,
|
|
now,
|
|
msg.NewContent,
|
|
)
|
|
|
|
data, _ := json.Marshal(outgoing)
|
|
h.hub.BroadcastToRoom(*msg.ConversationID, data, nil)
|
|
|
|
if h.pubsub != nil {
|
|
_ = h.pubsub.Publish(ctx, *msg.ConversationID, data)
|
|
}
|
|
}
|
|
|
|
func (h *MessageHandler) HandleDeleteMessage(ctx context.Context, client *Client, msg *IncomingMessage) {
|
|
if msg.MessageID == nil || msg.ConversationID == nil {
|
|
client.SendJSON(NewErrorResponse("message_id and conversation_id are required"))
|
|
return
|
|
}
|
|
|
|
chatMsg, err := h.msgRepo.GetByID(ctx, *msg.MessageID)
|
|
if err != nil {
|
|
client.SendJSON(NewErrorResponse("message not found"))
|
|
return
|
|
}
|
|
|
|
isModerator := h.permissions.CanModerate(ctx, client.UserID, *msg.ConversationID)
|
|
if chatMsg.SenderID != client.UserID && !isModerator {
|
|
client.SendJSON(NewErrorResponse("can only delete your own messages"))
|
|
return
|
|
}
|
|
|
|
if err := h.msgRepo.SoftDelete(ctx, *msg.MessageID); err != nil {
|
|
h.logger.Error("Failed to delete message", zap.Error(err))
|
|
client.SendJSON(NewErrorResponse("failed to delete message"))
|
|
return
|
|
}
|
|
|
|
client.SendJSON(NewActionConfirmedResponse("message_deleted", true))
|
|
|
|
outgoing := NewMessageDeletedResponse(
|
|
*msg.MessageID,
|
|
*msg.ConversationID,
|
|
client.UserID,
|
|
time.Now(),
|
|
)
|
|
|
|
data, _ := json.Marshal(outgoing)
|
|
h.hub.BroadcastToRoom(*msg.ConversationID, data, nil)
|
|
|
|
if h.pubsub != nil {
|
|
_ = h.pubsub.Publish(ctx, *msg.ConversationID, data)
|
|
}
|
|
}
|