package api import ( "encoding/json" "net/http" "strconv" ) func (h *apiHandler) addReaction(w http.ResponseWriter, r *http.Request) { userID, _ := userIDFrom(r.Context()) convID, msgID, ok := h.parseMessagePath(w, r) if !ok { return } var req struct { Emoji string `json:"emoji"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid json") return } if len(req.Emoji) == 0 || len(req.Emoji) > 32 { writeError(w, http.StatusBadRequest, "emoji must be 1-32 chars") return } if err := h.store.AddReaction(r.Context(), msgID, userID, req.Emoji); err != nil { writeError(w, http.StatusInternalServerError, "internal server error") return } h.hub.PublishReactionChanged(convID, msgID, userID, req.Emoji, true) h.writeMessageWithReactions(w, r, convID, msgID) } func (h *apiHandler) removeReaction(w http.ResponseWriter, r *http.Request) { userID, _ := userIDFrom(r.Context()) convID, msgID, ok := h.parseMessagePath(w, r) if !ok { return } emoji := r.PathValue("emoji") if emoji == "" { writeError(w, http.StatusBadRequest, "emoji is required") return } if err := h.store.RemoveReaction(r.Context(), msgID, userID, emoji); err != nil { writeError(w, http.StatusInternalServerError, "internal server error") return } h.hub.PublishReactionChanged(convID, msgID, userID, emoji, false) h.writeMessageWithReactions(w, r, convID, msgID) } func (h *apiHandler) parseMessagePath(w http.ResponseWriter, r *http.Request) (convID, msgID int64, ok bool) { userID, _ := userIDFrom(r.Context()) convID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil || convID <= 0 { writeError(w, http.StatusBadRequest, "invalid conversation id") return 0, 0, false } msgID, err = strconv.ParseInt(r.PathValue("msg_id"), 10, 64) if err != nil || msgID <= 0 { writeError(w, http.StatusBadRequest, "invalid message id") return 0, 0, false } if member, err := h.store.IsMember(r.Context(), convID, userID); err != nil || !member { writeError(w, http.StatusForbidden, "not a member") return 0, 0, false } belongs, err := h.store.MessageBelongsToConversation(r.Context(), convID, msgID) if err != nil || !belongs { writeError(w, http.StatusNotFound, "message not found") return 0, 0, false } return convID, msgID, true } func (h *apiHandler) writeMessageWithReactions(w http.ResponseWriter, r *http.Request, convID, msgID int64) { msg, err := h.store.GetMessageByID(r.Context(), msgID) if err != nil { writeError(w, http.StatusInternalServerError, "internal server error") return } msgs := []messageJSON{toMessageJSON(msg)} reactions, err := h.store.ReactionsForMessages(r.Context(), []int64{msgID}) if err != nil { writeError(w, http.StatusInternalServerError, "internal server error") return } attachReactions(msgs, reactions) writeJSON(w, http.StatusOK, msgs[0]) }