feat: add stickers
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, repository.ErrStickerNotOwned):
|
||||
response.Error(w, http.StatusForbidden, "that sticker isn't in your collection")
|
||||
case errors.Is(err, service.ErrChatBadEmoji):
|
||||
response.Error(w, http.StatusBadRequest, err.Error())
|
||||
case errors.Is(err, service.ErrChatBadQuote):
|
||||
@@ -206,6 +208,8 @@ type sendReq struct {
|
||||
// quote is a passage shared from the library. `path` must be app-relative;
|
||||
// the service refuses anything that could navigate off-site.
|
||||
Quote *quoteReq `json:"quote"`
|
||||
// sticker_key sends reward artwork. Ownership is checked server-side.
|
||||
StickerKey *string `json:"sticker_key"`
|
||||
// entity shares something else in the app — a post — as a card. Only the
|
||||
// reference; the BFF resolves the contents and re-checks visibility.
|
||||
Entity *entityReq `json:"entity"`
|
||||
@@ -249,6 +253,7 @@ func (h *ChatHandler) Send(w http.ResponseWriter, r *http.Request) {
|
||||
ReplyToID: req.ReplyToID,
|
||||
PreviewURL: req.PreviewURL,
|
||||
Quote: quote,
|
||||
StickerKey: req.StickerKey,
|
||||
Entity: entity,
|
||||
Attachments: media,
|
||||
})
|
||||
@@ -675,6 +680,38 @@ func (h *ChatHandler) MessageReactions(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
// Stickers — GET /chat/stickers. What the caller may send.
|
||||
func (h *ChatHandler) Stickers(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.svc.Stickers(r.Context(), middleware.UserID(r.Context()))
|
||||
if err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
type saveStickerReq struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// SaveSticker — POST /chat/conversations/{id}/messages/{messageID}/sticker
|
||||
//
|
||||
// Keyed to the message it was sent on, not just the sticker key: that is what
|
||||
// proves the caller was actually sent it rather than guessing at the catalog.
|
||||
func (h *ChatHandler) SaveSticker(w http.ResponseWriter, r *http.Request) {
|
||||
var req saveStickerReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
if err := h.svc.SaveSticker(r.Context(), middleware.UserID(r.Context()),
|
||||
chi.URLParam(r, "messageID"), req.Key); err != nil {
|
||||
writeChatError(w, err)
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// Typing — POST /chat/conversations/{id}/typing
|
||||
//
|
||||
// Fire-and-forget: nothing is stored and the response carries nothing, because
|
||||
|
||||
@@ -2,11 +2,14 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/middleware"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/model"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/service"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
@@ -114,6 +117,55 @@ func (h *SadhnaHandler) Rewards(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// AckRewards — POST /sadhna/rewards/ack. Clears pending celebrations once the
|
||||
// client has shown the confetti, so a gift surfaces exactly once.
|
||||
// OpenGift — POST /sadhna/rewards/gifts/{key}/open
|
||||
//
|
||||
// Called after the celebration is dismissed. Idempotent: opening an already
|
||||
// opened gift changes nothing.
|
||||
func (h *SadhnaHandler) OpenGift(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h.svc.OpenGift(r.Context(),
|
||||
middleware.UserID(r.Context()), chi.URLParam(r, "key")); err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "could not open that gift")
|
||||
return
|
||||
}
|
||||
response.NoContent(w)
|
||||
}
|
||||
|
||||
// GiftableRewards — GET /admin/reward-gifts/catalog
|
||||
func (h *SadhnaHandler) GiftableRewards(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.svc.GiftableRewards(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "could not load the catalog")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, list)
|
||||
}
|
||||
|
||||
type giftReq struct {
|
||||
RewardKey string `json:"reward_key"`
|
||||
}
|
||||
|
||||
// GiftToEveryone — POST /admin/reward-gifts
|
||||
//
|
||||
// Hands one reward to every account, unopened. Idempotent, so a repeated
|
||||
// click tops up only the people who don't have it rather than duplicating.
|
||||
func (h *SadhnaHandler) GiftToEveryone(w http.ResponseWriter, r *http.Request) {
|
||||
var req giftReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.RewardKey == "" {
|
||||
response.Error(w, http.StatusBadRequest, "reward_key is required")
|
||||
return
|
||||
}
|
||||
n, err := h.svc.GiftToEveryone(r.Context(), req.RewardKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrRewardNotFound) {
|
||||
response.Error(w, http.StatusNotFound, "no such reward")
|
||||
return
|
||||
}
|
||||
response.Error(w, http.StatusInternalServerError, "could not send the gift")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]int64{"granted": n})
|
||||
}
|
||||
|
||||
func (h *SadhnaHandler) AckRewards(w http.ResponseWriter, r *http.Request) {
|
||||
uid := middleware.UserID(r.Context())
|
||||
if err := h.svc.AckRewards(r.Context(), uid); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user