added stickers and emojies list
This commit is contained in:
parent
24276d1379
commit
43894f2fc9
26 changed files with 1118 additions and 14 deletions
|
|
@ -1,11 +1,14 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
@ -34,6 +37,10 @@ const (
|
|||
ActionPublishGiftCollectibles = "gifts.collectibles.publish"
|
||||
ActionSetStarGiftEnabled = "gifts.set_enabled"
|
||||
ActionSetStarGiftSortOrder = "gifts.set_sort_order"
|
||||
ActionSetStickerSetArchived = "stickers.set_archived"
|
||||
ActionSetStickerSetSortOrder = "stickers.set_sort_order"
|
||||
ActionRenameStickerSet = "stickers.rename"
|
||||
ActionDeleteStickerSet = "stickers.delete"
|
||||
|
||||
maxCommandIDLength = 128
|
||||
maxActorLength = 128
|
||||
|
|
@ -124,6 +131,16 @@ type AvatarResolver interface {
|
|||
GetFile(ctx context.Context, req domain.FileDownloadRequest) (domain.FileChunk, bool, error)
|
||||
}
|
||||
|
||||
// StickerSetsService is the admin-console management surface over sticker/custom-emoji
|
||||
// sets: no ownership check (see internal/app/files.Service.AdminSetStickerSetArchived),
|
||||
// so it works for seed-imported system/regular packs as well as user-created ones.
|
||||
type StickerSetsService interface {
|
||||
AdminSetStickerSetArchived(ctx context.Context, setID int64, archived bool) (bool, error)
|
||||
AdminSetStickerSetSortOrder(ctx context.Context, setID int64, order int) (bool, error)
|
||||
AdminRenameStickerSet(ctx context.Context, setID int64, title string) (domain.StickerSet, error)
|
||||
AdminDeleteStickerSet(ctx context.Context, setID int64) (domain.StickerSetKind, error)
|
||||
}
|
||||
|
||||
type Dependencies struct {
|
||||
Commands CommandRepository
|
||||
Restrictions RestrictionStore
|
||||
|
|
@ -139,6 +156,7 @@ type Dependencies struct {
|
|||
Gifts GiftsService
|
||||
OfficialGifts OfficialGiftsSource
|
||||
Photos AvatarResolver
|
||||
StickerSets StickerSetsService
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +175,7 @@ type Service struct {
|
|||
gifts GiftsService
|
||||
officialGifts OfficialGiftsSource
|
||||
photos AvatarResolver
|
||||
stickerSets StickerSetsService
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +227,9 @@ func (s *Service) Configure(deps Dependencies) *Service {
|
|||
if deps.Photos != nil {
|
||||
s.photos = deps.Photos
|
||||
}
|
||||
if deps.StickerSets != nil {
|
||||
s.stickerSets = deps.StickerSets
|
||||
}
|
||||
if deps.Now != nil {
|
||||
s.now = deps.Now
|
||||
}
|
||||
|
|
@ -279,6 +301,29 @@ type SetStarGiftSortOrderRequest struct {
|
|||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
type SetStickerSetArchivedRequest struct {
|
||||
CommandMeta
|
||||
SetID int64 `json:"set_id"`
|
||||
Archived bool `json:"archived"`
|
||||
}
|
||||
|
||||
type SetStickerSetSortOrderRequest struct {
|
||||
CommandMeta
|
||||
SetID int64 `json:"set_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
type RenameStickerSetRequest struct {
|
||||
CommandMeta
|
||||
SetID int64 `json:"set_id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type DeleteStickerSetRequest struct {
|
||||
CommandMeta
|
||||
SetID int64 `json:"set_id"`
|
||||
}
|
||||
|
||||
type StarGiftCollectibleAnimationUpload struct {
|
||||
Name string `json:"name"`
|
||||
RarityPermille int `json:"rarity_permille"`
|
||||
|
|
@ -1394,6 +1439,123 @@ func (s *Service) SetStarGiftSortOrder(ctx context.Context, req SetStarGiftSortO
|
|||
})
|
||||
}
|
||||
|
||||
func (s *Service) SetStickerSetArchived(ctx context.Context, req SetStickerSetArchivedRequest) (CommandResult, error) {
|
||||
if s == nil || s.stickerSets == nil || req.SetID <= 0 {
|
||||
return CommandResult{}, fmt.Errorf("valid sticker set and service are required")
|
||||
}
|
||||
return s.runCommand(ctx, req.CommandMeta, ActionSetStickerSetArchived, 0, domain.Peer{}, req, func() (CommandResult, error) {
|
||||
details := map[string]any{"set_id": strconv.FormatInt(req.SetID, 10), "archived": req.Archived}
|
||||
if req.DryRun {
|
||||
return CommandResult{Message: "sticker set state change validated", Details: details}, nil
|
||||
}
|
||||
changed, err := s.stickerSets.AdminSetStickerSetArchived(ctx, req.SetID, req.Archived)
|
||||
details["changed"] = changed
|
||||
return CommandResult{Message: "sticker set state updated", Details: details}, err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) SetStickerSetSortOrder(ctx context.Context, req SetStickerSetSortOrderRequest) (CommandResult, error) {
|
||||
if s == nil || s.stickerSets == nil || req.SetID <= 0 || req.SortOrder < math.MinInt32 || req.SortOrder > math.MaxInt32 {
|
||||
return CommandResult{}, fmt.Errorf("valid sticker set and service are required")
|
||||
}
|
||||
return s.runCommand(ctx, req.CommandMeta, ActionSetStickerSetSortOrder, 0, domain.Peer{}, req, func() (CommandResult, error) {
|
||||
details := map[string]any{"set_id": strconv.FormatInt(req.SetID, 10), "sort_order": req.SortOrder}
|
||||
if req.DryRun {
|
||||
return CommandResult{Message: "sticker set order change validated", Details: details}, nil
|
||||
}
|
||||
changed, err := s.stickerSets.AdminSetStickerSetSortOrder(ctx, req.SetID, req.SortOrder)
|
||||
details["changed"] = changed
|
||||
return CommandResult{Message: "sticker set order updated", Details: details}, err
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) RenameStickerSet(ctx context.Context, req RenameStickerSetRequest) (CommandResult, error) {
|
||||
if s == nil || s.stickerSets == nil || req.SetID <= 0 || strings.TrimSpace(req.Title) == "" {
|
||||
return CommandResult{}, fmt.Errorf("valid sticker set, title and service are required")
|
||||
}
|
||||
return s.runCommand(ctx, req.CommandMeta, ActionRenameStickerSet, 0, domain.Peer{}, req, func() (CommandResult, error) {
|
||||
details := map[string]any{"set_id": strconv.FormatInt(req.SetID, 10), "title": req.Title}
|
||||
if req.DryRun {
|
||||
return CommandResult{Message: "sticker set rename validated", Details: details}, nil
|
||||
}
|
||||
set, err := s.stickerSets.AdminRenameStickerSet(ctx, req.SetID, req.Title)
|
||||
if err != nil {
|
||||
return CommandResult{Details: details}, err
|
||||
}
|
||||
details["title"] = set.Title
|
||||
return CommandResult{Message: "sticker set renamed", Details: details}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) DeleteStickerSet(ctx context.Context, req DeleteStickerSetRequest) (CommandResult, error) {
|
||||
if s == nil || s.stickerSets == nil || req.SetID <= 0 {
|
||||
return CommandResult{}, fmt.Errorf("valid sticker set and service are required")
|
||||
}
|
||||
return s.runCommand(ctx, req.CommandMeta, ActionDeleteStickerSet, 0, domain.Peer{}, req, func() (CommandResult, error) {
|
||||
details := map[string]any{"set_id": strconv.FormatInt(req.SetID, 10)}
|
||||
if req.DryRun {
|
||||
return CommandResult{Message: "sticker set deletion validated", Details: details}, nil
|
||||
}
|
||||
kind, err := s.stickerSets.AdminDeleteStickerSet(ctx, req.SetID)
|
||||
if err != nil {
|
||||
return CommandResult{Details: details}, err
|
||||
}
|
||||
details["kind"] = string(kind)
|
||||
return CommandResult{Message: "sticker set deleted", Details: details}, nil
|
||||
})
|
||||
}
|
||||
|
||||
const maxStickerDocumentBytes = 8 << 20
|
||||
|
||||
// StickerDocumentAnimation returns a sticker/custom-emoji document's preview
|
||||
// for the admin console's set-preview grid, plus the content type the caller
|
||||
// should serve it as. Most packs are gzip-compressed TGS (decompressed here to
|
||||
// Lottie JSON), but some (e.g. hand-uploaded packs) contain plain static
|
||||
// raster stickers instead — those are returned as-is with their real image
|
||||
// content type so the frontend can render an <img> instead of a Lottie player.
|
||||
func (s *Service) StickerDocumentAnimation(ctx context.Context, documentID int64) ([]byte, string, bool, error) {
|
||||
if s == nil || s.photos == nil || documentID <= 0 {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
chunk, found, err := s.photos.GetFile(ctx, domain.FileDownloadRequest{
|
||||
LocationKey: fmt.Sprintf("doc:%d", documentID),
|
||||
Limit: maxStickerDocumentBytes + 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", false, err
|
||||
}
|
||||
if !found || chunk.Total <= 0 || chunk.Total > maxStickerDocumentBytes || int64(len(chunk.Bytes)) != chunk.Total {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
data := chunk.Bytes
|
||||
if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
|
||||
reader, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
defer reader.Close()
|
||||
decompressed, err := io.ReadAll(io.LimitReader(reader, maxStickerDocumentBytes))
|
||||
if err != nil {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
return decompressed, "application/json; charset=utf-8", true, nil
|
||||
}
|
||||
detected := http.DetectContentType(data)
|
||||
if !isSafeStickerPreviewImageType(detected) {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
return data, detected, true, nil
|
||||
}
|
||||
|
||||
func isSafeStickerPreviewImageType(value string) bool {
|
||||
switch value {
|
||||
case "image/webp", "image/png", "image/jpeg", "image/gif":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) StarGiftAnimation(ctx context.Context, giftID int64) ([]byte, bool, error) {
|
||||
if s == nil || s.gifts == nil || giftID <= 0 {
|
||||
return nil, false, nil
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ type Service interface {
|
|||
PublishStarGiftCollectibles(ctx context.Context, req admin.PublishStarGiftCollectiblesRequest) (admin.CommandResult, error)
|
||||
SetStarGiftEnabled(ctx context.Context, req admin.SetStarGiftEnabledRequest) (admin.CommandResult, error)
|
||||
SetStarGiftSortOrder(ctx context.Context, req admin.SetStarGiftSortOrderRequest) (admin.CommandResult, error)
|
||||
SetStickerSetArchived(ctx context.Context, req admin.SetStickerSetArchivedRequest) (admin.CommandResult, error)
|
||||
SetStickerSetSortOrder(ctx context.Context, req admin.SetStickerSetSortOrderRequest) (admin.CommandResult, error)
|
||||
RenameStickerSet(ctx context.Context, req admin.RenameStickerSetRequest) (admin.CommandResult, error)
|
||||
DeleteStickerSet(ctx context.Context, req admin.DeleteStickerSetRequest) (admin.CommandResult, error)
|
||||
StickerDocumentAnimation(ctx context.Context, documentID int64) ([]byte, string, bool, error)
|
||||
StarGiftAnimation(ctx context.Context, giftID int64) ([]byte, bool, error)
|
||||
StarGiftCollectibles(ctx context.Context, giftID int64) (domain.StarGiftUpgradePreview, bool, error)
|
||||
StarGiftCollectibleAnimation(ctx context.Context, giftID int64, kind domain.StarGiftCollectibleAttributeKind, attributeID int64) ([]byte, bool, error)
|
||||
|
|
@ -110,6 +115,11 @@ func (s *Server) routes() http.Handler {
|
|||
mux.HandleFunc("POST /v1/gifts/{id}/collectibles/publish", s.authenticated(s.handlePublishStarGiftCollectibles))
|
||||
mux.HandleFunc("POST /v1/gifts/set-enabled", s.authenticated(s.handleSetStarGiftEnabled))
|
||||
mux.HandleFunc("POST /v1/gifts/set-sort-order", s.authenticated(s.handleSetStarGiftSortOrder))
|
||||
mux.HandleFunc("POST /v1/stickers/set-archived", s.authenticated(s.handleSetStickerSetArchived))
|
||||
mux.HandleFunc("POST /v1/stickers/set-sort-order", s.authenticated(s.handleSetStickerSetSortOrder))
|
||||
mux.HandleFunc("POST /v1/stickers/rename", s.authenticated(s.handleRenameStickerSet))
|
||||
mux.HandleFunc("POST /v1/stickers/delete", s.authenticated(s.handleDeleteStickerSet))
|
||||
mux.HandleFunc("GET /v1/stickers/documents/{id}/animation", s.authenticated(s.handleStickerDocumentAnimation))
|
||||
mux.HandleFunc("GET /v1/gifts/{id}/animation", s.authenticated(s.handleStarGiftAnimation))
|
||||
mux.HandleFunc("GET /v1/gifts/{id}/collectibles", s.authenticated(s.handleStarGiftCollectibles))
|
||||
mux.HandleFunc("GET /v1/gifts/{id}/collectibles/{kind}/{attribute_id}/animation", s.authenticated(s.handleStarGiftCollectibleAnimation))
|
||||
|
|
@ -402,6 +412,63 @@ func (s *Server) handleSetStarGiftSortOrder(w http.ResponseWriter, r *http.Reque
|
|||
writeCommandResult(w, result, err)
|
||||
}
|
||||
|
||||
func (s *Server) handleSetStickerSetArchived(w http.ResponseWriter, r *http.Request) {
|
||||
var req admin.SetStickerSetArchivedRequest
|
||||
if !decodeJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
result, err := s.svc.SetStickerSetArchived(r.Context(), req)
|
||||
writeCommandResult(w, result, err)
|
||||
}
|
||||
|
||||
func (s *Server) handleSetStickerSetSortOrder(w http.ResponseWriter, r *http.Request) {
|
||||
var req admin.SetStickerSetSortOrderRequest
|
||||
if !decodeJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
result, err := s.svc.SetStickerSetSortOrder(r.Context(), req)
|
||||
writeCommandResult(w, result, err)
|
||||
}
|
||||
|
||||
func (s *Server) handleRenameStickerSet(w http.ResponseWriter, r *http.Request) {
|
||||
var req admin.RenameStickerSetRequest
|
||||
if !decodeJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
result, err := s.svc.RenameStickerSet(r.Context(), req)
|
||||
writeCommandResult(w, result, err)
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteStickerSet(w http.ResponseWriter, r *http.Request) {
|
||||
var req admin.DeleteStickerSetRequest
|
||||
if !decodeJSON(w, r, &req) {
|
||||
return
|
||||
}
|
||||
result, err := s.svc.DeleteStickerSet(r.Context(), req)
|
||||
writeCommandResult(w, result, err)
|
||||
}
|
||||
|
||||
func (s *Server) handleStickerDocumentAnimation(w http.ResponseWriter, r *http.Request) {
|
||||
documentID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil || documentID <= 0 {
|
||||
writeError(w, http.StatusBadRequest, "invalid document id")
|
||||
return
|
||||
}
|
||||
raw, contentType, found, err := s.svc.StickerDocumentAnimation(r.Context(), documentID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
writeError(w, http.StatusNotFound, "document animation not found")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Cache-Control", "private, max-age=300")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(raw)
|
||||
}
|
||||
|
||||
func (s *Server) handleStarGiftAnimation(w http.ResponseWriter, r *http.Request) {
|
||||
giftID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil || giftID <= 0 {
|
||||
|
|
|
|||
|
|
@ -275,6 +275,26 @@ func (fakeService) AccountAvatar(context.Context, int64) ([]byte, string, bool,
|
|||
return nil, "", false, nil
|
||||
}
|
||||
|
||||
func (fakeService) SetStickerSetArchived(_ context.Context, req admin.SetStickerSetArchivedRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) SetStickerSetSortOrder(_ context.Context, req admin.SetStickerSetSortOrderRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) RenameStickerSet(_ context.Context, req admin.RenameStickerSetRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) DeleteStickerSet(_ context.Context, req admin.DeleteStickerSetRequest) (admin.CommandResult, error) {
|
||||
return admin.CommandResult{CommandID: req.CommandID, Status: "completed", DryRun: req.DryRun}, nil
|
||||
}
|
||||
|
||||
func (fakeService) StickerDocumentAnimation(context.Context, int64) ([]byte, string, bool, error) {
|
||||
return nil, "", false, nil
|
||||
}
|
||||
|
||||
func (fakeService) OfficialStarGifts(context.Context) ([]officialgifts.GiftSummary, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,6 +282,17 @@ func (f *fakeMediaStore) DeleteStickerSet(_ context.Context, setID int64, creato
|
|||
f.sets[setID] = set
|
||||
return nil
|
||||
}
|
||||
func (f *fakeMediaStore) AdminDeleteStickerSet(_ context.Context, setID int64) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
set, ok := f.sets[setID]
|
||||
if !ok || set.Deleted {
|
||||
return domain.ErrStickerSetInvalid
|
||||
}
|
||||
set.Deleted = true
|
||||
f.sets[setID] = set
|
||||
return nil
|
||||
}
|
||||
func (f *fakeMediaStore) GetStickerSetByID(_ context.Context, id int64) (domain.StickerSet, bool, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -134,6 +134,93 @@ func (s *Service) DeleteStickerSet(ctx context.Context, actorUserID int64, ref d
|
|||
return set.Kind, nil
|
||||
}
|
||||
|
||||
// AdminSetStickerSetArchived toggles a set's archived flag with no ownership
|
||||
// check, so admins can hide/show any pack — including seed-imported system
|
||||
// and regular packs, which have no creator_user_id to match against.
|
||||
func (s *Service) AdminSetStickerSetArchived(ctx context.Context, setID int64, archived bool) (bool, error) {
|
||||
set, found, err := s.media.GetStickerSetByID(ctx, setID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !found {
|
||||
return false, domain.ErrStickerSetInvalid
|
||||
}
|
||||
if set.Archived == archived {
|
||||
return false, nil
|
||||
}
|
||||
set.Archived = archived
|
||||
if err := s.media.UpdateStickerSet(ctx, set, nil); err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.deleteCachedStickerSet(set)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// AdminSetStickerSetSortOrder sets a set's display sort order with no
|
||||
// ownership check; see AdminSetStickerSetArchived for why that's needed here.
|
||||
func (s *Service) AdminSetStickerSetSortOrder(ctx context.Context, setID int64, order int) (bool, error) {
|
||||
set, found, err := s.media.GetStickerSetByID(ctx, setID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !found {
|
||||
return false, domain.ErrStickerSetInvalid
|
||||
}
|
||||
if set.SortOrder == order {
|
||||
return false, nil
|
||||
}
|
||||
set.SortOrder = order
|
||||
if err := s.media.UpdateStickerSet(ctx, set, nil); err != nil {
|
||||
return false, err
|
||||
}
|
||||
s.deleteCachedStickerSet(set)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// AdminRenameStickerSet renames a set with no ownership check; see
|
||||
// AdminSetStickerSetArchived for why that's needed here.
|
||||
func (s *Service) AdminRenameStickerSet(ctx context.Context, setID int64, title string) (domain.StickerSet, error) {
|
||||
title = strings.TrimSpace(title)
|
||||
if err := validateStickerSetTitle(title); err != nil {
|
||||
return domain.StickerSet{}, err
|
||||
}
|
||||
set, found, err := s.media.GetStickerSetByID(ctx, setID)
|
||||
if err != nil {
|
||||
return domain.StickerSet{}, err
|
||||
}
|
||||
if !found {
|
||||
return domain.StickerSet{}, domain.ErrStickerSetInvalid
|
||||
}
|
||||
set.Title = title
|
||||
set.Hash = stickerSetHash(set)
|
||||
if err := s.media.UpdateStickerSet(ctx, set, nil); err != nil {
|
||||
return domain.StickerSet{}, err
|
||||
}
|
||||
s.deleteCachedStickerSet(set)
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// AdminDeleteStickerSet deletes (soft-delete) a set with no ownership check;
|
||||
// see AdminSetStickerSetArchived for why that's needed here. Safe to bypass
|
||||
// ownership for: sticker_sets has no incoming foreign keys, so there's no
|
||||
// cascade to worry about (unlike star gifts, which have ~15 dependent
|
||||
// tables). Seed-imported sets will reappear on next restart if their source
|
||||
// files are still under data/sticker-seed — this only removes the DB row.
|
||||
func (s *Service) AdminDeleteStickerSet(ctx context.Context, setID int64) (domain.StickerSetKind, error) {
|
||||
set, found, err := s.media.GetStickerSetByID(ctx, setID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !found {
|
||||
return "", domain.ErrStickerSetInvalid
|
||||
}
|
||||
if err := s.media.AdminDeleteStickerSet(ctx, setID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.deleteCachedStickerSet(set)
|
||||
return set.Kind, nil
|
||||
}
|
||||
|
||||
func (s *Service) resolveOwnedStickerSet(ctx context.Context, actorUserID int64, ref domain.StickerSetRef) (domain.StickerSet, []domain.Document, error) {
|
||||
if actorUserID <= 0 {
|
||||
return domain.StickerSet{}, nil, domain.ErrStickerSetCreatorInvalid
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ type MediaStore interface {
|
|||
CreateStickerSet(ctx context.Context, set domain.StickerSet, docs []domain.Document) error
|
||||
UpdateStickerSet(ctx context.Context, set domain.StickerSet, docs []domain.Document) error
|
||||
DeleteStickerSet(ctx context.Context, setID int64, creatorUserID int64) error
|
||||
// AdminDeleteStickerSet is DeleteStickerSet without the creator_user_id match —
|
||||
// for admin-console management of any set, including seed-imported system/regular
|
||||
// packs that have no creator (creator_user_id=0).
|
||||
AdminDeleteStickerSet(ctx context.Context, setID int64) error
|
||||
GetStickerSetByID(ctx context.Context, id int64) (domain.StickerSet, bool, error)
|
||||
GetStickerSetByShortName(ctx context.Context, shortName string) (domain.StickerSet, bool, error)
|
||||
GetStickerSetBySystemKey(ctx context.Context, systemKey string) (domain.StickerSet, bool, error)
|
||||
|
|
|
|||
|
|
@ -756,6 +756,24 @@ WHERE id = $1
|
|||
})
|
||||
}
|
||||
|
||||
func (s *MediaStore) AdminDeleteStickerSet(ctx context.Context, setID int64) error {
|
||||
return withTx(ctx, s.db, "admin delete sticker set", func(tx pgx.Tx) error {
|
||||
tag, err := tx.Exec(ctx, `
|
||||
UPDATE sticker_sets
|
||||
SET deleted = true, updated_at = now()
|
||||
WHERE id = $1
|
||||
AND deleted = false`, setID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return domain.ErrStickerSetInvalid
|
||||
}
|
||||
_, err = tx.Exec(ctx, `DELETE FROM user_sticker_sets WHERE sticker_set_id = $1`, setID)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func insertStickerSet(ctx context.Context, db sqlcgen.DBTX, set domain.StickerSet) error {
|
||||
thumbs, err := jsonArrayOrEmpty(set.Thumbs)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue