diff options
author | 2022-11-24 19:12:07 +0100 | |
---|---|---|
committer | 2022-11-24 18:12:07 +0000 | |
commit | b6dbe21026615ef3fbaacff98c7cc860cef39d16 (patch) | |
tree | cef207c759ba936c9432c456e751c862e2c0830c /internal/processing/admin/updateemoji.go | |
parent | [bugfix] Fix status boosts giving 404 (#1137) (diff) | |
download | gotosocial-b6dbe21026615ef3fbaacff98c7cc860cef39d16.tar.xz |
[feature] `PATCH /api/v1/admin/custom_emojis/{id}` endpoint (#1061)
* start adding admin emoji PATCH stuff
* updating works OK, now how about copying
* allow emojis to be copied
* update swagger docs
* update admin processer to use non-interface storage driver
* remove shortcode updating for local emojis
* go fmt
Co-authored-by: f0x52 <f0x@cthu.lu>
Diffstat (limited to 'internal/processing/admin/updateemoji.go')
-rw-r--r-- | internal/processing/admin/updateemoji.go | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/internal/processing/admin/updateemoji.go b/internal/processing/admin/updateemoji.go new file mode 100644 index 000000000..1a86d5080 --- /dev/null +++ b/internal/processing/admin/updateemoji.go @@ -0,0 +1,237 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package admin + +import ( + "context" + "errors" + "fmt" + "io" + "mime/multipart" + + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/uris" +) + +func (p *processor) EmojiUpdate(ctx context.Context, id string, form *apimodel.EmojiUpdateRequest) (*apimodel.AdminEmoji, gtserror.WithCode) { + emoji, err := p.db.GetEmojiByID(ctx, id) + if err != nil { + if errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("EmojiUpdate: no emoji with id %s found in the db", id) + return nil, gtserror.NewErrorNotFound(err) + } + err := fmt.Errorf("EmojiUpdate: db error: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + switch form.Type { + case apimodel.EmojiUpdateCopy: + return p.emojiUpdateCopy(ctx, emoji, form.Shortcode, form.CategoryName) + case apimodel.EmojiUpdateDisable: + return p.emojiUpdateDisable(ctx, emoji) + case apimodel.EmojiUpdateModify: + return p.emojiUpdateModify(ctx, emoji, form.Image, form.CategoryName) + default: + err := errors.New("unrecognized emoji action type") + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } +} + +// copy an emoji from remote to local +func (p *processor) emojiUpdateCopy(ctx context.Context, emoji *gtsmodel.Emoji, shortcode *string, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain == "" { + err := fmt.Errorf("emojiUpdateCopy: emoji %s is not a remote emoji, cannot copy it to local", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + if shortcode == nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, no shortcode provided", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + maybeExisting, err := p.db.GetEmojiByShortcodeDomain(ctx, *shortcode, "") + if maybeExisting != nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, emoji with shortcode %s already exists on this instance", emoji.ID, *shortcode) + return nil, gtserror.NewErrorConflict(err, err.Error()) + } + + if err != nil && err != db.ErrNoEntries { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error checking existence of emoji with shortcode %s: %s", emoji.ID, *shortcode, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmojiID, err := id.NewRandomULID() + if err != nil { + err := fmt.Errorf("emojiUpdateCopy: emoji %s could not be copied, error creating id for new emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmojiURI := uris.GenerateURIForEmoji(newEmojiID) + + data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { + // 'copy' the emoji by pulling the existing one out of storage + i, err := p.storage.GetStream(ctx, emoji.ImagePath) + return i, int64(emoji.ImageFileSize), err + } + + var ai *media.AdditionalEmojiInfo + if categoryName != nil { + category, err := p.GetOrCreateEmojiCategory(ctx, *categoryName) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error getting or creating category: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + ai = &media.AdditionalEmojiInfo{ + CategoryID: &category.ID, + } + } + + processingEmoji, err := p.mediaManager.ProcessEmoji(ctx, data, nil, *shortcode, newEmojiID, newEmojiURI, ai, false) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error processing emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + newEmoji, err := processingEmoji.LoadEmoji(ctx) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error loading processed emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, newEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateCopy: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// disable a remote emoji +func (p *processor) emojiUpdateDisable(ctx context.Context, emoji *gtsmodel.Emoji) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain == "" { + err := fmt.Errorf("emojiUpdateDisable: emoji %s is not a remote emoji, cannot disable it via this endpoint", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + emojiDisabled := true + emoji.Disabled = &emojiDisabled + updatedEmoji, err := p.db.UpdateEmoji(ctx, emoji, "updated_at", "disabled") + if err != nil { + err = fmt.Errorf("emojiUpdateDisable: error updating emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateDisable: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} + +// modify a local emoji +func (p *processor) emojiUpdateModify(ctx context.Context, emoji *gtsmodel.Emoji, image *multipart.FileHeader, categoryName *string) (*apimodel.AdminEmoji, gtserror.WithCode) { + if emoji.Domain != "" { + err := fmt.Errorf("emojiUpdateModify: emoji %s is not a local emoji, cannot do a modify action on it", emoji.ID) + return nil, gtserror.NewErrorBadRequest(err, err.Error()) + } + + var updatedEmoji *gtsmodel.Emoji + + // keep existing categoryID unless a new one is defined + var ( + updatedCategoryID = emoji.CategoryID + updateCategoryID bool + ) + if categoryName != nil { + category, err := p.GetOrCreateEmojiCategory(ctx, *categoryName) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error getting or creating category: %s", err) + return nil, gtserror.NewErrorInternalError(err) + } + + updatedCategoryID = category.ID + updateCategoryID = true + } + + // only update image if provided with one + var updateImage bool + if image != nil && image.Size != 0 { + updateImage = true + } + + if !updateImage { + // only updating fields, we only need + // to do a database update for this + columns := []string{"updated_at"} + + if updateCategoryID { + emoji.CategoryID = updatedCategoryID + columns = append(columns, "category_id") + } + + var err error + updatedEmoji, err = p.db.UpdateEmoji(ctx, emoji, columns...) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error updating emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + } else { + // new image, so we need to reprocess the emoji + data := func(ctx context.Context) (reader io.ReadCloser, fileSize int64, err error) { + i, err := image.Open() + return i, image.Size, err + } + + var ai *media.AdditionalEmojiInfo + if updateCategoryID { + ai = &media.AdditionalEmojiInfo{ + CategoryID: &updatedCategoryID, + } + } + + processingEmoji, err := p.mediaManager.ProcessEmoji(ctx, data, nil, emoji.Shortcode, emoji.ID, emoji.URI, ai, true) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error processing emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + updatedEmoji, err = processingEmoji.LoadEmoji(ctx) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error loading processed emoji %s: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + } + + adminEmoji, err := p.tc.EmojiToAdminAPIEmoji(ctx, updatedEmoji) + if err != nil { + err = fmt.Errorf("emojiUpdateModify: error converting updated emoji %s to admin emoji: %s", emoji.ID, err) + return nil, gtserror.NewErrorInternalError(err) + } + + return adminEmoji, nil +} |