117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
apperrors "veza-backend-api/internal/errors"
|
|
"veza-backend-api/internal/repositories"
|
|
chatws "veza-backend-api/internal/websocket/chat"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ChatSearchHandler handles REST API for chat message search (v0.9.6)
|
|
type ChatSearchHandler struct {
|
|
msgRepo *repositories.ChatMessageRepository
|
|
permissions *chatws.PermissionService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewChatSearchHandler creates a new ChatSearchHandler
|
|
func NewChatSearchHandler(
|
|
msgRepo *repositories.ChatMessageRepository,
|
|
permissions *chatws.PermissionService,
|
|
logger *zap.Logger,
|
|
) *ChatSearchHandler {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &ChatSearchHandler{
|
|
msgRepo: msgRepo,
|
|
permissions: permissions,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
const (
|
|
defaultSearchLimit = 20
|
|
maxSearchLimit = 100
|
|
)
|
|
|
|
// SearchMessages searches messages in a room
|
|
// GET /api/v1/chat/rooms/:roomId/messages/search?q=...&limit=20&offset=0
|
|
func (h *ChatSearchHandler) SearchMessages(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
roomID, err := uuid.Parse(c.Param("roomId"))
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewValidationError("Invalid room ID"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(c.Request.Context(), userID, roomID) {
|
|
RespondWithAppError(c, apperrors.NewForbiddenError("Not allowed to access this conversation"))
|
|
return
|
|
}
|
|
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
RespondWithAppError(c, apperrors.NewValidationError("query parameter 'q' is required"))
|
|
return
|
|
}
|
|
|
|
limit := defaultSearchLimit
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
limit = parsed
|
|
if limit > maxSearchLimit {
|
|
limit = maxSearchLimit
|
|
}
|
|
}
|
|
}
|
|
offset := 0
|
|
if o := c.Query("offset"); o != "" {
|
|
if parsed, err := strconv.Atoi(o); err == nil && parsed > 0 {
|
|
offset = parsed
|
|
}
|
|
}
|
|
|
|
messages, total, err := h.msgRepo.Search(c.Request.Context(), roomID, query, limit, offset)
|
|
if err != nil {
|
|
h.logger.Error("Failed to search messages", zap.Error(err))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to search messages", err))
|
|
return
|
|
}
|
|
|
|
// Convert to response format
|
|
out := make([]gin.H, 0, len(messages))
|
|
for _, m := range messages {
|
|
out = append(out, gin.H{
|
|
"id": m.ID,
|
|
"conversation_id": m.ConversationID,
|
|
"sender_id": m.SenderID,
|
|
"content": m.Content,
|
|
"message_type": m.MessageType,
|
|
"parent_message_id": m.ParentMessageID,
|
|
"reply_to_id": m.ReplyToID,
|
|
"is_pinned": m.IsPinned,
|
|
"is_edited": m.IsEdited,
|
|
"is_deleted": m.IsDeleted,
|
|
"edited_at": m.EditedAt,
|
|
"status": m.Status,
|
|
"created_at": m.CreatedAt,
|
|
"updated_at": m.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{
|
|
"messages": out,
|
|
"total": total,
|
|
})
|
|
}
|