feat: chat reactions
This commit is contained in:
@@ -38,6 +38,8 @@ func writeChatError(w http.ResponseWriter, err error) {
|
||||
response.Error(w, http.StatusNotFound, err.Error())
|
||||
case errors.Is(err, service.ErrChatBadEntity):
|
||||
response.Error(w, http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, service.ErrChatBadEmoji):
|
||||
response.Error(w, http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, service.ErrChatBadQuote):
|
||||
response.Error(w, http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, service.ErrChatBadAvatarKey):
|
||||
@@ -648,6 +650,78 @@ func (h *ChatHandler) SetMute(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(w, http.StatusOK, map[string]any{"muted_until": until})
|
||||
}
|
||||
|
||||
// Message — GET /chat/conversations/{id}/messages/{messageID}
|
||||
func (h *ChatHandler) Message(w http.ResponseWriter, r *http.Request) {
|
||||
msg, err := h.svc.Message(r.Context(), middleware.UserID(r.Context()),
|
||||
chi.URLParam(r, "id"), chi.URLParam(r, "messageID"))
|
||||
if err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, msg)
|
||||
}
|
||||
|
||||
// MessageReactions — GET /chat/conversations/{id}/messages/{messageID}/reactions
|
||||
//
|
||||
// Only read when someone opens the list; the thread itself renders from the
|
||||
// grouped counts that ride along with each message.
|
||||
func (h *ChatHandler) MessageReactions(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.svc.MessageReactions(r.Context(),
|
||||
middleware.UserID(r.Context()), chi.URLParam(r, "messageID"))
|
||||
if err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
// Typing — POST /chat/conversations/{id}/typing
|
||||
//
|
||||
// Fire-and-forget: nothing is stored and the response carries nothing, because
|
||||
// a typing indicator is stale within seconds of being sent.
|
||||
func (h *ChatHandler) Typing(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.svc.Typing(r.Context(),
|
||||
middleware.UserID(r.Context()), chi.URLParam(r, "id")); err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
type reactReq struct {
|
||||
Emoji string `json:"emoji"`
|
||||
}
|
||||
|
||||
// React — PUT /chat/conversations/{id}/messages/{messageID}/reaction.
|
||||
// Replaces any previous choice.
|
||||
func (h *ChatHandler) React(w http.ResponseWriter, r *http.Request) {
|
||||
var req reactReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
// The message id is authoritative: React re-derives the conversation from
|
||||
// it and authorizes there, so the id in the path can't be used to react
|
||||
// into a thread the caller isn't in.
|
||||
if err := h.svc.React(r.Context(), middleware.UserID(r.Context()),
|
||||
chi.URLParam(r, "messageID"), req.Emoji); err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// Unreact — DELETE /chat/conversations/{id}/messages/{messageID}/reaction.
|
||||
// Idempotent.
|
||||
func (h *ChatHandler) Unreact(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.svc.Unreact(r.Context(), middleware.UserID(r.Context()),
|
||||
chi.URLParam(r, "messageID")); err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// Receipts — GET /chat/conversations/{id}/receipts
|
||||
func (h *ChatHandler) Receipts(w http.ResponseWriter, r *http.Request) {
|
||||
rc, err := h.svc.Receipts(r.Context(), middleware.UserID(r.Context()), chi.URLParam(r, "id"))
|
||||
|
||||
Reference in New Issue
Block a user