routes_users.go (already on main) calls settingsHandler.GetPreferences / UpdatePreferences and gdprExportHandler.ExportJSON, but the methods only existed in the working tree — main wouldn't compile, so deploy.yml's build-backend job was stuck on the same compile error every run. Bundles the WIP swagger annotation sweep across chat / marketplace / role / settings / gdpr / etc. handlers with the regenerated swagger.json, swagger.yaml, docs.go and openapi.yaml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
apperrors "veza-backend-api/internal/errors"
|
|
"veza-backend-api/internal/models"
|
|
"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"
|
|
)
|
|
|
|
// ChatReactionHandler handles REST API for chat message reactions (v0.9.6)
|
|
type ChatReactionHandler struct {
|
|
reactionRepo *repositories.ReactionRepository
|
|
msgRepo *repositories.ChatMessageRepository
|
|
permissions *chatws.PermissionService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewChatReactionHandler creates a new ChatReactionHandler
|
|
func NewChatReactionHandler(
|
|
reactionRepo *repositories.ReactionRepository,
|
|
msgRepo *repositories.ChatMessageRepository,
|
|
permissions *chatws.PermissionService,
|
|
logger *zap.Logger,
|
|
) *ChatReactionHandler {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &ChatReactionHandler{
|
|
reactionRepo: reactionRepo,
|
|
msgRepo: msgRepo,
|
|
permissions: permissions,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// AddReactionRequest body for POST
|
|
type AddReactionRequest struct {
|
|
Emoji string `json:"emoji" binding:"required" validate:"required,max=50"`
|
|
}
|
|
|
|
// AddReaction adds a reaction to a message
|
|
// POST /api/v1/chat/rooms/:roomId/messages/:messageId/reactions
|
|
// @Summary Add reaction
|
|
// @Description Add an emoji reaction to a specific chat message.
|
|
// @Tags Chat
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param roomId path string true "Room ID"
|
|
// @Param messageId path string true "Message ID"
|
|
// @Param reaction body AddReactionRequest true "Reaction emoji"
|
|
// @Success 201 {object} object{reaction=object}
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Failure 403 {object} handlers.APIResponse "Forbidden"
|
|
// @Router /api/v1/chat/rooms/{roomId}/messages/{messageId}/reactions [post]
|
|
func (h *ChatReactionHandler) AddReaction(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
|
|
}
|
|
messageID, err := uuid.Parse(c.Param("messageId"))
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewValidationError("Invalid message ID"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(c.Request.Context(), userID, roomID) {
|
|
RespondWithAppError(c, apperrors.NewForbiddenError("Not allowed to access this conversation"))
|
|
return
|
|
}
|
|
|
|
var req AddReactionRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Emoji == "" {
|
|
RespondWithAppError(c, apperrors.NewValidationError("emoji is required"))
|
|
return
|
|
}
|
|
|
|
msg, err := h.msgRepo.GetByID(c.Request.Context(), messageID)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewNotFoundError("Message"))
|
|
return
|
|
}
|
|
if msg.ConversationID != roomID {
|
|
RespondWithAppError(c, apperrors.NewValidationError("Message does not belong to this room"))
|
|
return
|
|
}
|
|
|
|
reaction := &models.MessageReaction{
|
|
ID: uuid.New(),
|
|
UserID: userID,
|
|
MessageID: messageID,
|
|
Emoji: req.Emoji,
|
|
}
|
|
if err := h.reactionRepo.Add(c.Request.Context(), reaction); err != nil {
|
|
h.logger.Error("Failed to add reaction", zap.Error(err))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to add reaction", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusCreated, gin.H{
|
|
"reaction": gin.H{
|
|
"id": reaction.ID,
|
|
"user_id": reaction.UserID,
|
|
"message_id": reaction.MessageID,
|
|
"emoji": reaction.Emoji,
|
|
},
|
|
})
|
|
}
|
|
|
|
// RemoveReaction removes a reaction from a message
|
|
// DELETE /api/v1/chat/rooms/:roomId/messages/:messageId/reactions?emoji=👍
|
|
// @Summary Remove reaction
|
|
// @Description Remove an emoji reaction from a specific chat message.
|
|
// @Tags Chat
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param roomId path string true "Room ID"
|
|
// @Param messageId path string true "Message ID"
|
|
// @Param emoji query string false "Specific emoji to remove"
|
|
// @Success 200 {object} object{success=boolean}
|
|
// @Failure 401 {object} handlers.APIResponse "Unauthorized"
|
|
// @Failure 403 {object} handlers.APIResponse "Forbidden"
|
|
// @Router /api/v1/chat/rooms/{roomId}/messages/{messageId}/reactions [delete]
|
|
func (h *ChatReactionHandler) RemoveReaction(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
|
|
}
|
|
messageID, err := uuid.Parse(c.Param("messageId"))
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewValidationError("Invalid message ID"))
|
|
return
|
|
}
|
|
|
|
if !h.permissions.CanRead(c.Request.Context(), userID, roomID) {
|
|
RespondWithAppError(c, apperrors.NewForbiddenError("Not allowed to access this conversation"))
|
|
return
|
|
}
|
|
|
|
msg, err := h.msgRepo.GetByID(c.Request.Context(), messageID)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewNotFoundError("Message"))
|
|
return
|
|
}
|
|
if msg.ConversationID != roomID {
|
|
RespondWithAppError(c, apperrors.NewValidationError("Message does not belong to this room"))
|
|
return
|
|
}
|
|
|
|
emoji := c.Query("emoji")
|
|
if emoji != "" {
|
|
err = h.reactionRepo.RemoveByEmoji(c.Request.Context(), userID, messageID, emoji)
|
|
} else {
|
|
err = h.reactionRepo.Remove(c.Request.Context(), userID, messageID)
|
|
}
|
|
if err != nil {
|
|
h.logger.Error("Failed to remove reaction", zap.Error(err))
|
|
RespondWithAppError(c, apperrors.NewInternalErrorWrap("Failed to remove reaction", err))
|
|
return
|
|
}
|
|
|
|
RespondSuccess(c, http.StatusOK, gin.H{"success": true})
|
|
}
|