diff --git a/internal/api/client/admin/emojicreate_test.go b/internal/api/client/admin/emojicreate_test.go index 290b478f..14b83b53 100644 --- a/internal/api/client/admin/emojicreate_test.go +++ b/internal/api/client/admin/emojicreate_test.go @@ -1,6 +1,8 @@ package admin_test import ( + "context" + "encoding/json" "io/ioutil" "net/http" "net/http/httptest" @@ -8,6 +10,9 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -43,6 +48,41 @@ func (suite *EmojiCreateTestSuite) TestEmojiCreate() { b, err := ioutil.ReadAll(result.Body) suite.NoError(err) suite.NotEmpty(b) + + // response should be an api model emoji + apiEmoji := &apimodel.Emoji{} + err = json.Unmarshal(b, apiEmoji) + suite.NoError(err) + + // appropriate fields should be set + suite.Equal("rainbow", apiEmoji.Shortcode) + suite.NotEmpty(apiEmoji.URL) + suite.NotEmpty(apiEmoji.StaticURL) + suite.True(apiEmoji.VisibleInPicker) + + // emoji should be in the db + dbEmoji := >smodel.Emoji{} + err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "shortcode", Value: "rainbow"}}, dbEmoji) + suite.NoError(err) + + // check fields on the emoji + suite.NotEmpty(dbEmoji.ID) + suite.Equal("rainbow", dbEmoji.Shortcode) + suite.Empty(dbEmoji.Domain) + suite.Empty(dbEmoji.ImageRemoteURL) + suite.Empty(dbEmoji.ImageStaticRemoteURL) + suite.Equal(apiEmoji.URL, dbEmoji.ImageURL) + suite.Equal(apiEmoji.StaticURL, dbEmoji.ImageURL) + suite.NotEmpty(dbEmoji.ImagePath) + suite.NotEmpty(dbEmoji.ImageStaticPath) + suite.Equal("image/png", dbEmoji.ImageContentType) + suite.Equal("image/png", dbEmoji.ImageStaticContentType) + suite.Equal(36702, dbEmoji.ImageFileSize) + suite.Equal(10413, dbEmoji.ImageStaticFileSize) + suite.False(dbEmoji.Disabled) + suite.NotEmpty(dbEmoji.URI) + suite.True(dbEmoji.VisibleInPicker) + suite.Empty(dbEmoji.CategoryID)aaaaaaaaa } func TestEmojiCreateTestSuite(t *testing.T) { diff --git a/internal/media/image.go b/internal/media/image.go index a5a81820..de4b7121 100644 --- a/internal/media/image.go +++ b/internal/media/image.go @@ -20,21 +20,16 @@ package media import ( "bytes" - "context" "errors" "fmt" "image" "image/gif" "image/jpeg" "image/png" - "time" "github.com/buckket/go-blurhash" "github.com/nfnt/resize" "github.com/superseriousbusiness/exifremove/pkg/exifremove" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/id" - "github.com/superseriousbusiness/gotosocial/internal/uris" ) const ( diff --git a/internal/media/manager.go b/internal/media/manager.go index e3447159..7f626271 100644 --- a/internal/media/manager.go +++ b/internal/media/manager.go @@ -41,7 +41,7 @@ type Manager interface { // // ai is optional and can be nil. Any additional information about the attachment provided will be put in the database. ProcessMedia(ctx context.Context, data DataFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) - ProcessEmoji(ctx context.Context, data DataFunc, shortcode string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) + ProcessEmoji(ctx context.Context, data DataFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) // NumWorkers returns the total number of workers available to this manager. NumWorkers() int // QueueSize returns the total capacity of the queue. @@ -125,8 +125,8 @@ func (m *manager) ProcessMedia(ctx context.Context, data DataFunc, accountID str return processingMedia, nil } -func (m *manager) ProcessEmoji(ctx context.Context, data DataFunc, shortcode string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) { - processingEmoji, err := m.preProcessEmoji(ctx, data, shortcode, ai) +func (m *manager) ProcessEmoji(ctx context.Context, data DataFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) { + processingEmoji, err := m.preProcessEmoji(ctx, data, shortcode, id, uri, ai) if err != nil { return nil, err } diff --git a/internal/media/processingemoji.go b/internal/media/processingemoji.go index 41754830..eeccdb28 100644 --- a/internal/media/processingemoji.go +++ b/internal/media/processingemoji.go @@ -28,7 +28,6 @@ import ( "codeberg.org/gruf/go-store/kv" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/uris" ) @@ -126,33 +125,28 @@ func (p *ProcessingEmoji) loadStatic(ctx context.Context) (*ImageMeta, error) { } // set appropriate fields on the emoji based on the static version we derived - p.attachment.FileMeta.Small = gtsmodel.Small{ - Width: static.width, - Height: static.height, - Size: static.size, - Aspect: static.aspect, - } - p.attachment.Thumbnail.FileSize = static.size + p.emoji.ImageStaticFileSize = len(static.image) - if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { + // update the emoji in the db + if err := putOrUpdate(ctx, p.database, p.emoji); err != nil { p.err = err - p.thumbstate = errored + p.staticState = errored return nil, err } - // set the thumbnail of this media - p.thumb = static + // set the static on the processing emoji + p.static = static - // we're done processing the thumbnail! - p.thumbstate = complete + // we're done processing the static version of the emoji! + p.staticState = complete fallthrough case complete: - return p.thumb, nil + return p.static, nil case errored: return nil, p.err } - return nil, fmt.Errorf("thumbnail processing status %d unknown", p.thumbstate) + return nil, fmt.Errorf("static processing status %d unknown", p.staticState) } func (p *ProcessingEmoji) loadFullSize(ctx context.Context) (*ImageMeta, error) { @@ -161,26 +155,17 @@ func (p *ProcessingEmoji) loadFullSize(ctx context.Context) (*ImageMeta, error) switch p.fullSizeState { case received: - var clean []byte var err error var decoded *ImageMeta - ct := p.attachment.File.ContentType + ct := p.emoji.ImageContentType switch ct { - case mimeImageJpeg, mimeImagePng: - // first 'clean' image by purging exif data from it - var exifErr error - if clean, exifErr = purgeExif(p.rawData); exifErr != nil { - err = exifErr - break - } - decoded, err = decodeImage(clean, ct) + case mimeImagePng: + decoded, err = decodeImage(p.rawData, ct) case mimeImageGif: - // gifs are already clean - no exif data to remove - clean = p.rawData - decoded, err = decodeGif(clean) + decoded, err = decodeGif(p.rawData) default: - err = fmt.Errorf("content type %s not a processible image type", ct) + err = fmt.Errorf("content type %s not a processible emoji type", ct) } if err != nil { @@ -189,34 +174,17 @@ func (p *ProcessingEmoji) loadFullSize(ctx context.Context) (*ImageMeta, error) return nil, err } - // put the full size in storage - if err := p.storage.Put(p.attachment.File.Path, decoded.image); err != nil { - p.err = fmt.Errorf("error storing full size image: %s", err) + // put the full size emoji in storage + if err := p.storage.Put(p.emoji.ImagePath, decoded.image); err != nil { + p.err = fmt.Errorf("error storing full size emoji: %s", err) p.fullSizeState = errored return nil, p.err } - // set appropriate fields on the attachment based on the image we derived - p.attachment.FileMeta.Original = gtsmodel.Original{ - Width: decoded.width, - Height: decoded.height, - Size: decoded.size, - Aspect: decoded.aspect, - } - p.attachment.File.FileSize = decoded.size - p.attachment.File.UpdatedAt = time.Now() - p.attachment.Processing = gtsmodel.ProcessingStatusProcessed - - if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { - p.err = err - p.fullSizeState = errored - return nil, err - } - // set the fullsize of this media p.fullSize = decoded - // we're done processing the full-size image + // we're done processing the full-size emoji p.fullSizeState = complete fallthrough case complete: @@ -255,55 +223,24 @@ func (p *ProcessingEmoji) fetchRawData(ctx context.Context) error { } split := strings.Split(contentType, "/") - mainType := split[0] // something like 'image' extension := split[1] // something like 'gif' // set some additional fields on the emoji now that // we know more about what the underlying image actually is - p.emoji.ImageURL = uris.GenerateURIForAttachment(p.attachment.AccountID, string(TypeAttachment), string(SizeOriginal), p.attachment.ID, extension) - p.attachment.File.Path = fmt.Sprintf("%s/%s/%s/%s.%s", p.attachment.AccountID, TypeAttachment, SizeOriginal, p.attachment.ID, extension) - p.attachment.File.ContentType = contentType - - switch mainType { - case mimeImage: - if extension == mimeGif { - p.attachment.Type = gtsmodel.FileTypeGif - } else { - p.attachment.Type = gtsmodel.FileTypeImage - } - default: - return fmt.Errorf("fetchRawData: cannot process mime type %s (yet)", mainType) - } + p.emoji.ImageURL = uris.GenerateURIForAttachment(p.instanceAccountID, string(TypeEmoji), string(SizeOriginal), p.emoji.ID, extension) + p.emoji.ImagePath = fmt.Sprintf("%s/%s/%s/%s.%s", p.instanceAccountID, TypeEmoji, SizeOriginal, p.emoji.ID, extension) + p.emoji.ImageContentType = contentType + p.emoji.ImageFileSize = len(p.rawData) return nil } -// putOrUpdateEmoji is just a convenience function for first trying to PUT the emoji in the database, -// and then if that doesn't work because the emoji already exists, updating it instead. -func putOrUpdateEmoji(ctx context.Context, database db.DB, emoji *gtsmodel.Emoji) error { - if err := database.Put(ctx, emoji); err != nil { - if err != db.ErrAlreadyExists { - return fmt.Errorf("putOrUpdateEmoji: proper error while putting emoji: %s", err) - } - if err := database.UpdateByPrimaryKey(ctx, emoji); err != nil { - return fmt.Errorf("putOrUpdateEmoji: error while updating emoji: %s", err) - } - } - - return nil -} - -func (m *manager) preProcessEmoji(ctx context.Context, data DataFunc, shortcode string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) { +func (m *manager) preProcessEmoji(ctx context.Context, data DataFunc, shortcode string, id string, uri string, ai *AdditionalEmojiInfo) (*ProcessingEmoji, error) { instanceAccount, err := m.db.GetInstanceAccount(ctx, "") if err != nil { return nil, fmt.Errorf("preProcessEmoji: error fetching this instance account from the db: %s", err) } - id, err := id.NewRandomULID() - if err != nil { - return nil, err - } - // populate initial fields on the emoji -- some of these will be overwritten as we proceed emoji := >smodel.Emoji{ ID: id, @@ -323,7 +260,7 @@ func (m *manager) preProcessEmoji(ctx context.Context, data DataFunc, shortcode ImageStaticFileSize: 0, ImageUpdatedAt: time.Now(), Disabled: false, - URI: "", // we don't know yet + URI: uri, VisibleInPicker: true, CategoryID: "", } @@ -332,43 +269,31 @@ func (m *manager) preProcessEmoji(ctx context.Context, data DataFunc, shortcode // and overwrite some of the emoji fields if so if ai != nil { if ai.CreatedAt != nil { - attachment.CreatedAt = *ai.CreatedAt + emoji.CreatedAt = *ai.CreatedAt } - if ai.StatusID != nil { - attachment.StatusID = *ai.StatusID + if ai.Domain != nil { + emoji.Domain = *ai.Domain } - if ai.RemoteURL != nil { - attachment.RemoteURL = *ai.RemoteURL + if ai.ImageRemoteURL != nil { + emoji.ImageRemoteURL = *ai.ImageRemoteURL } - if ai.Description != nil { - attachment.Description = *ai.Description + if ai.ImageStaticRemoteURL != nil { + emoji.ImageStaticRemoteURL = *ai.ImageStaticRemoteURL } - if ai.ScheduledStatusID != nil { - attachment.ScheduledStatusID = *ai.ScheduledStatusID + if ai.Disabled != nil { + emoji.Disabled = *ai.Disabled } - if ai.Blurhash != nil { - attachment.Blurhash = *ai.Blurhash + if ai.VisibleInPicker != nil { + emoji.VisibleInPicker = *ai.VisibleInPicker } - if ai.Avatar != nil { - attachment.Avatar = *ai.Avatar - } - - if ai.Header != nil { - attachment.Header = *ai.Header - } - - if ai.FocusX != nil { - attachment.FileMeta.Focus.X = *ai.FocusX - } - - if ai.FocusY != nil { - attachment.FileMeta.Focus.Y = *ai.FocusY + if ai.CategoryID != nil { + emoji.CategoryID = *ai.CategoryID } } diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go index a6e45034..1bfd7b62 100644 --- a/internal/media/processingmedia.go +++ b/internal/media/processingmedia.go @@ -32,14 +32,6 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/uris" ) -type processState int - -const ( - received processState = iota // processing order has been received but not done yet - complete // processing order has been completed successfully - errored // processing order has been completed with an error -) - // ProcessingMedia represents a piece of media that is currently being processed. It exposes // various functions for retrieving data from the process. type ProcessingMedia struct { @@ -103,10 +95,6 @@ func (p *ProcessingMedia) LoadAttachment(ctx context.Context) (*gtsmodel.MediaAt return p.attachment, nil } -func (p *ProcessingMedia) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error) { - return nil, nil -} - // Finished returns true if processing has finished for both the thumbnail // and full fized version of this piece of media. func (p *ProcessingMedia) Finished() bool { @@ -153,9 +141,9 @@ func (p *ProcessingMedia) loadThumb(ctx context.Context) (*ImageMeta, error) { Size: thumb.size, Aspect: thumb.aspect, } - p.attachment.Thumbnail.FileSize = thumb.size + p.attachment.Thumbnail.FileSize = len(thumb.image) - if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { + if err := putOrUpdate(ctx, p.database, p.attachment); err != nil { p.err = err p.thumbstate = errored return nil, err @@ -224,11 +212,11 @@ func (p *ProcessingMedia) loadFullSize(ctx context.Context) (*ImageMeta, error) Size: decoded.size, Aspect: decoded.aspect, } - p.attachment.File.FileSize = decoded.size + p.attachment.File.FileSize = len(decoded.image) p.attachment.File.UpdatedAt = time.Now() p.attachment.Processing = gtsmodel.ProcessingStatusProcessed - if err := putOrUpdateAttachment(ctx, p.database, p.attachment); err != nil { + if err := putOrUpdate(ctx, p.database, p.attachment); err != nil { p.err = err p.fullSizeState = errored return nil, err @@ -299,21 +287,6 @@ func (p *ProcessingMedia) fetchRawData(ctx context.Context) error { return nil } -// putOrUpdateAttachment is just a convenience function for first trying to PUT the attachment in the database, -// and then if that doesn't work because the attachment already exists, updating it instead. -func putOrUpdateAttachment(ctx context.Context, database db.DB, attachment *gtsmodel.MediaAttachment) error { - if err := database.Put(ctx, attachment); err != nil { - if err != db.ErrAlreadyExists { - return fmt.Errorf("putOrUpdateAttachment: proper error while putting attachment: %s", err) - } - if err := database.UpdateByPrimaryKey(ctx, attachment); err != nil { - return fmt.Errorf("putOrUpdateAttachment: error while updating attachment: %s", err) - } - } - - return nil -} - func (m *manager) preProcessMedia(ctx context.Context, data DataFunc, accountID string, ai *AdditionalMediaInfo) (*ProcessingMedia, error) { id, err := id.NewRandomULID() if err != nil { diff --git a/internal/media/types.go b/internal/media/types.go index 6426223d..5b3fe4a4 100644 --- a/internal/media/types.go +++ b/internal/media/types.go @@ -19,15 +19,17 @@ package media import ( - "bytes" "context" - "errors" - "fmt" "time" - - "github.com/h2non/filetype" ) +// maxFileHeaderBytes represents the maximum amount of bytes we want +// to examine from the beginning of a file to determine its type. +// +// See: https://en.wikipedia.org/wiki/File_format#File_header +// and https://github.com/h2non/filetype +const maxFileHeaderBytes = 262 + // mime consts const ( mimeImage = "image" @@ -42,16 +44,17 @@ const ( mimeImagePng = mimeImage + "/" + mimePng ) +type processState int + +const ( + received processState = iota // processing order has been received but not done yet + complete // processing order has been completed successfully + errored // processing order has been completed with an error +) + // EmojiMaxBytes is the maximum permitted bytes of an emoji upload (50kb) // const EmojiMaxBytes = 51200 -// maxFileHeaderBytes represents the maximum amount of bytes we want -// to examine from the beginning of a file to determine its type. -// -// See: https://en.wikipedia.org/wiki/File_format#File_header -// and https://github.com/h2non/filetype -const maxFileHeaderBytes = 262 - type Size string const ( @@ -94,89 +97,24 @@ type AdditionalMediaInfo struct { FocusY *float32 } +// AdditionalMediaInfo represents additional information +// that should be added to an emoji when processing it. type AdditionalEmojiInfo struct { - + // Time that this emoji was created; defaults to time.Now(). + CreatedAt *time.Time + // Domain the emoji originated from. Blank for this instance's domain. Defaults to "". + Domain *string + // URL of this emoji on a remote instance; defaults to "". + ImageRemoteURL *string + // URL of the static version of this emoji on a remote instance; defaults to "". + ImageStaticRemoteURL *string + // Whether this emoji should be disabled (not shown) on this instance; defaults to false. + Disabled *bool + // Whether this emoji should be visible in the instance's emoji picker; defaults to true. + VisibleInPicker *bool + // ID of the category this emoji should be placed in; defaults to "". + CategoryID *string } // DataFunc represents a function used to retrieve the raw bytes of a piece of media. type DataFunc func(ctx context.Context) ([]byte, error) - -// parseContentType parses the MIME content type from a file, returning it as a string in the form (eg., "image/jpeg"). -// Returns an error if the content type is not something we can process. -func parseContentType(content []byte) (string, error) { - - // read in the first bytes of the file - fileHeader := make([]byte, maxFileHeaderBytes) - if _, err := bytes.NewReader(content).Read(fileHeader); err != nil { - return "", fmt.Errorf("could not read first magic bytes of file: %s", err) - } - - kind, err := filetype.Match(fileHeader) - if err != nil { - return "", err - } - - if kind == filetype.Unknown { - return "", errors.New("filetype unknown") - } - - return kind.MIME.Value, nil -} - -// supportedImage checks mime type of an image against a slice of accepted types, -// and returns True if the mime type is accepted. -func supportedImage(mimeType string) bool { - acceptedImageTypes := []string{ - mimeImageJpeg, - mimeImageGif, - mimeImagePng, - } - for _, accepted := range acceptedImageTypes { - if mimeType == accepted { - return true - } - } - return false -} - -// supportedEmoji checks that the content type is image/png -- the only type supported for emoji. -func supportedEmoji(mimeType string) bool { - acceptedEmojiTypes := []string{ - mimeImageGif, - mimeImagePng, - } - for _, accepted := range acceptedEmojiTypes { - if mimeType == accepted { - return true - } - } - return false -} - -// ParseMediaType converts s to a recognized MediaType, or returns an error if unrecognized -func ParseMediaType(s string) (Type, error) { - switch s { - case string(TypeAttachment): - return TypeAttachment, nil - case string(TypeHeader): - return TypeHeader, nil - case string(TypeAvatar): - return TypeAvatar, nil - case string(TypeEmoji): - return TypeEmoji, nil - } - return "", fmt.Errorf("%s not a recognized MediaType", s) -} - -// ParseMediaSize converts s to a recognized MediaSize, or returns an error if unrecognized -func ParseMediaSize(s string) (Size, error) { - switch s { - case string(SizeSmall): - return SizeSmall, nil - case string(SizeOriginal): - return SizeOriginal, nil - case string(SizeStatic): - return SizeStatic, nil - } - return "", fmt.Errorf("%s not a recognized MediaSize", s) -} diff --git a/internal/media/util.go b/internal/media/util.go new file mode 100644 index 00000000..16e874a9 --- /dev/null +++ b/internal/media/util.go @@ -0,0 +1,123 @@ +/* + 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 . +*/ + +package media + +import ( + "bytes" + "context" + "errors" + "fmt" + + "github.com/h2non/filetype" + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +// parseContentType parses the MIME content type from a file, returning it as a string in the form (eg., "image/jpeg"). +// Returns an error if the content type is not something we can process. +func parseContentType(content []byte) (string, error) { + // read in the first bytes of the file + fileHeader := make([]byte, maxFileHeaderBytes) + if _, err := bytes.NewReader(content).Read(fileHeader); err != nil { + return "", fmt.Errorf("could not read first magic bytes of file: %s", err) + } + + kind, err := filetype.Match(fileHeader) + if err != nil { + return "", err + } + + if kind == filetype.Unknown { + return "", errors.New("filetype unknown") + } + + return kind.MIME.Value, nil +} + +// supportedImage checks mime type of an image against a slice of accepted types, +// and returns True if the mime type is accepted. +func supportedImage(mimeType string) bool { + acceptedImageTypes := []string{ + mimeImageJpeg, + mimeImageGif, + mimeImagePng, + } + for _, accepted := range acceptedImageTypes { + if mimeType == accepted { + return true + } + } + return false +} + +// supportedEmoji checks that the content type is image/png -- the only type supported for emoji. +func supportedEmoji(mimeType string) bool { + acceptedEmojiTypes := []string{ + mimeImageGif, + mimeImagePng, + } + for _, accepted := range acceptedEmojiTypes { + if mimeType == accepted { + return true + } + } + return false +} + +// ParseMediaType converts s to a recognized MediaType, or returns an error if unrecognized +func ParseMediaType(s string) (Type, error) { + switch s { + case string(TypeAttachment): + return TypeAttachment, nil + case string(TypeHeader): + return TypeHeader, nil + case string(TypeAvatar): + return TypeAvatar, nil + case string(TypeEmoji): + return TypeEmoji, nil + } + return "", fmt.Errorf("%s not a recognized MediaType", s) +} + +// ParseMediaSize converts s to a recognized MediaSize, or returns an error if unrecognized +func ParseMediaSize(s string) (Size, error) { + switch s { + case string(SizeSmall): + return SizeSmall, nil + case string(SizeOriginal): + return SizeOriginal, nil + case string(SizeStatic): + return SizeStatic, nil + } + return "", fmt.Errorf("%s not a recognized MediaSize", s) +} + +// putOrUpdate is just a convenience function for first trying to PUT the attachment or emoji in the database, +// and then if that doesn't work because the attachment/emoji already exists, updating it instead. +func putOrUpdate(ctx context.Context, database db.DB, i interface{}) error { + if err := database.Put(ctx, i); err != nil { + if err != db.ErrAlreadyExists { + return fmt.Errorf("putOrUpdate: proper error while putting: %s", err) + } + if err := database.UpdateByPrimaryKey(ctx, i); err != nil { + return fmt.Errorf("putOrUpdate: error while updating: %s", err) + } + } + + return nil +} diff --git a/internal/processing/admin/emoji.go b/internal/processing/admin/emoji.go index 8858dbd0..77fa5102 100644 --- a/internal/processing/admin/emoji.go +++ b/internal/processing/admin/emoji.go @@ -27,6 +27,8 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/uris" ) func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, user *gtsmodel.User, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error) { @@ -52,7 +54,14 @@ func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account, return buf.Bytes(), f.Close() } - processingEmoji, err := p.mediaManager.ProcessEmoji(ctx, data, form.Shortcode, nil) + emojiID, err := id.NewRandomULID() + if err != nil { + return nil, fmt.Errorf("error creating id for new emoji: %s", err) + } + + emojiURI := uris.GenerateURIForEmoji(emojiID) + + processingEmoji, err := p.mediaManager.ProcessEmoji(ctx, data, form.Shortcode, emojiID, emojiURI, nil) if err != nil { return nil, err }