mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-30 08:12:27 -05:00
rewrite file serving system
This commit is contained in:
parent
e47ee2b883
commit
cc424df169
12 changed files with 355 additions and 226 deletions
|
|
@ -21,12 +21,11 @@ package fileserver
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServeFile is for serving attachments, headers, and avatars to the requester from instance storage.
|
// ServeFile is for serving attachments, headers, and avatars to the requester from instance storage.
|
||||||
|
|
@ -42,6 +41,12 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
})
|
})
|
||||||
l.Trace("received request")
|
l.Trace("received request")
|
||||||
|
|
||||||
|
authed, err := oauth.Authed(c, false, false, false, false)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusNotFound, "404 page not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We use request params to check what to pull out of the database/storage so check everything. A request URL should be formatted as follows:
|
// We use request params to check what to pull out of the database/storage so check everything. A request URL should be formatted as follows:
|
||||||
// "https://example.org/fileserver/[ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]"
|
// "https://example.org/fileserver/[ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]"
|
||||||
// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension.
|
// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension.
|
||||||
|
|
@ -73,171 +78,16 @@ func (m *FileServer) ServeFile(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only serve media types that are defined in our internal media module
|
content, err := m.processor.MediaGet(authed, &model.GetContentRequestForm{
|
||||||
switch mediaType {
|
AccountID: accountID,
|
||||||
case media.MediaHeader, media.MediaAvatar, media.MediaAttachment:
|
MediaType: mediaType,
|
||||||
m.serveAttachment(c, accountID, mediaType, mediaSize, fileName)
|
MediaSize: mediaSize,
|
||||||
return
|
FileName: fileName,
|
||||||
case media.MediaEmoji:
|
|
||||||
m.serveEmoji(c, accountID, mediaType, mediaSize, fileName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
l.Debugf("mediatype %s not recognized", mediaType)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FileServer) serveAttachment(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
|
|
||||||
l := m.log.WithFields(logrus.Fields{
|
|
||||||
"func": "serveAttachment",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// This corresponds to original-sized image as it was uploaded, small (which is the thumbnail), or static
|
|
||||||
switch mediaSize {
|
|
||||||
case media.MediaOriginal, media.MediaSmall, media.MediaStatic:
|
|
||||||
default:
|
|
||||||
l.Debugf("mediasize %s not recognized", mediaSize)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// derive the media id and the file extension from the last part of the request
|
|
||||||
spl := strings.Split(fileName, ".")
|
|
||||||
if len(spl) != 2 {
|
|
||||||
l.Debugf("filename %s not parseable", fileName)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wantedMediaID := spl[0]
|
|
||||||
fileExtension := spl[1]
|
|
||||||
if wantedMediaID == "" || fileExtension == "" {
|
|
||||||
l.Debugf("filename %s not parseable", fileName)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we know the attachment ID that the caller is asking for we can use it to pull the attachment out of the db
|
|
||||||
attachment := >smodel.MediaAttachment{}
|
|
||||||
if err := m.db.GetByID(wantedMediaID, attachment); err != nil {
|
|
||||||
l.Debugf("attachment with id %s not retrievable: %s", wantedMediaID, err)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the given account id owns the requested attachment
|
|
||||||
if accountID != attachment.AccountID {
|
|
||||||
l.Debugf("account %s does not own attachment with id %s", accountID, wantedMediaID)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we can start preparing the response depending on whether we're serving a thumbnail or a larger attachment
|
|
||||||
var storagePath string
|
|
||||||
var contentType string
|
|
||||||
var contentLength int
|
|
||||||
switch mediaSize {
|
|
||||||
case media.MediaOriginal:
|
|
||||||
storagePath = attachment.File.Path
|
|
||||||
contentType = attachment.File.ContentType
|
|
||||||
contentLength = attachment.File.FileSize
|
|
||||||
case media.MediaSmall:
|
|
||||||
storagePath = attachment.Thumbnail.Path
|
|
||||||
contentType = attachment.Thumbnail.ContentType
|
|
||||||
contentLength = attachment.Thumbnail.FileSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the path listed on the attachment we pulled out of the database to retrieve the object from storage
|
|
||||||
attachmentBytes, err := m.storage.RetrieveFileFrom(storagePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Debugf("error retrieving from storage: %s", err)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
c.String(http.StatusNotFound, "404 page not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Errorf("about to serve content length: %d attachment bytes is: %d", int64(contentLength), int64(len(attachmentBytes)))
|
c.DataFromReader(http.StatusOK, content.ContentLength, content.ContentType, bytes.NewReader(content.Content), nil)
|
||||||
|
|
||||||
// finally we can return with all the information we derived above
|
|
||||||
c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(attachmentBytes), map[string]string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FileServer) serveEmoji(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
|
|
||||||
l := m.log.WithFields(logrus.Fields{
|
|
||||||
"func": "serveEmoji",
|
|
||||||
"request_uri": c.Request.RequestURI,
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
"origin_ip": c.ClientIP(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// This corresponds to original-sized emoji as it was uploaded, or static
|
|
||||||
switch mediaSize {
|
|
||||||
case media.MediaOriginal, media.MediaStatic:
|
|
||||||
default:
|
|
||||||
l.Debugf("mediasize %s not recognized", mediaSize)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// derive the media id and the file extension from the last part of the request
|
|
||||||
spl := strings.Split(fileName, ".")
|
|
||||||
if len(spl) != 2 {
|
|
||||||
l.Debugf("filename %s not parseable", fileName)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wantedEmojiID := spl[0]
|
|
||||||
fileExtension := spl[1]
|
|
||||||
if wantedEmojiID == "" || fileExtension == "" {
|
|
||||||
l.Debugf("filename %s not parseable", fileName)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we know the attachment ID that the caller is asking for we can use it to pull the attachment out of the db
|
|
||||||
emoji := >smodel.Emoji{}
|
|
||||||
if err := m.db.GetByID(wantedEmojiID, emoji); err != nil {
|
|
||||||
l.Debugf("emoji with id %s not retrievable: %s", wantedEmojiID, err)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the instance account id owns the requested emoji
|
|
||||||
instanceAccount := >smodel.Account{}
|
|
||||||
if err := m.db.GetLocalAccountByUsername(m.config.Host, instanceAccount); err != nil {
|
|
||||||
l.Debugf("error fetching instance account: %s", err)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if accountID != instanceAccount.ID {
|
|
||||||
l.Debugf("account %s does not own emoji with id %s", accountID, wantedEmojiID)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we can start preparing the response depending on whether we're serving a thumbnail or a larger attachment
|
|
||||||
var storagePath string
|
|
||||||
var contentType string
|
|
||||||
var contentLength int
|
|
||||||
switch mediaSize {
|
|
||||||
case media.MediaOriginal:
|
|
||||||
storagePath = emoji.ImagePath
|
|
||||||
contentType = emoji.ImageContentType
|
|
||||||
contentLength = emoji.ImageFileSize
|
|
||||||
case media.MediaStatic:
|
|
||||||
storagePath = emoji.ImageStaticPath
|
|
||||||
contentType = "image/png"
|
|
||||||
contentLength = emoji.ImageStaticFileSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the path listed on the emoji we pulled out of the database to retrieve the object from storage
|
|
||||||
emojiBytes, err := m.storage.RetrieveFileFrom(storagePath)
|
|
||||||
if err != nil {
|
|
||||||
l.Debugf("error retrieving emoji from storage: %s", err)
|
|
||||||
c.String(http.StatusNotFound, "404 page not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally we can return with all the information we derived above
|
|
||||||
c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(emojiBytes), map[string]string{})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,11 +129,11 @@ func (suite *ServeFileTestSuite) TestServeOriginalFileSuccessful() {
|
||||||
},
|
},
|
||||||
gin.Param{
|
gin.Param{
|
||||||
Key: fileserver.MediaTypeKey,
|
Key: fileserver.MediaTypeKey,
|
||||||
Value: media.MediaAttachment,
|
Value: string(media.Attachment),
|
||||||
},
|
},
|
||||||
gin.Param{
|
gin.Param{
|
||||||
Key: fileserver.MediaSizeKey,
|
Key: fileserver.MediaSizeKey,
|
||||||
Value: media.MediaOriginal,
|
Value: string(media.Original),
|
||||||
},
|
},
|
||||||
gin.Param{
|
gin.Param{
|
||||||
Key: fileserver.FileNameKey,
|
Key: fileserver.FileNameKey,
|
||||||
|
|
|
||||||
41
internal/api/model/content.go
Normal file
41
internal/api/model/content.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) 2021 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 model
|
||||||
|
|
||||||
|
// Content wraps everything needed to serve a blob of content (some kind of media) through the API.
|
||||||
|
type Content struct {
|
||||||
|
// MIME content type
|
||||||
|
ContentType string
|
||||||
|
// ContentLength in bytes
|
||||||
|
ContentLength int64
|
||||||
|
// Actual content blob
|
||||||
|
Content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContentRequestForm describes a piece of content desired by the caller of the fileserver API.
|
||||||
|
type GetContentRequestForm struct {
|
||||||
|
// AccountID of the content owner
|
||||||
|
AccountID string
|
||||||
|
// MediaType of the content (should be convertible to a media.MediaType)
|
||||||
|
MediaType string
|
||||||
|
// MediaSize of the content (should be convertible to a media.MediaSize)
|
||||||
|
MediaSize string
|
||||||
|
// Filename of the content
|
||||||
|
FileName string
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
|
||||||
// build backend handlers
|
// build backend handlers
|
||||||
mediaHandler := media.New(c, dbService, storageBackend, log)
|
mediaHandler := media.New(c, dbService, storageBackend, log)
|
||||||
oauthServer := oauth.New(dbService, log)
|
oauthServer := oauth.New(dbService, log)
|
||||||
processor := message.NewProcessor(c, typeConverter, oauthServer, mediaHandler, dbService, log)
|
processor := message.NewProcessor(c, typeConverter, oauthServer, mediaHandler, storageBackend, dbService, log)
|
||||||
if err := processor.Start(); err != nil {
|
if err := processor.Start(); err != nil {
|
||||||
return fmt.Errorf("error starting processor: %s", err)
|
return fmt.Errorf("error starting processor: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ type Emoji struct {
|
||||||
// MIME content type of the emoji image
|
// MIME content type of the emoji image
|
||||||
// Probably "image/png"
|
// Probably "image/png"
|
||||||
ImageContentType string `pg:",notnull"`
|
ImageContentType string `pg:",notnull"`
|
||||||
|
// MIME content type of the static version of the emoji image.
|
||||||
|
ImageStaticContentType string `pg:",notnull"`
|
||||||
// Size of the emoji image file in bytes, for serving purposes.
|
// Size of the emoji image file in bytes, for serving purposes.
|
||||||
ImageFileSize int `pg:",notnull"`
|
ImageFileSize int `pg:",notnull"`
|
||||||
// Size of the static version of the emoji image file in bytes, for serving purposes.
|
// Size of the static version of the emoji image file in bytes, for serving purposes.
|
||||||
|
|
|
||||||
|
|
@ -32,21 +32,28 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MediaSize describes the *size* of a piece of media
|
||||||
|
type MediaSize string
|
||||||
|
|
||||||
|
// MediaType describes the *type* of a piece of media
|
||||||
|
type MediaType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MediaSmall is the key for small/thumbnail versions of media
|
// Small is the key for small/thumbnail versions of media
|
||||||
MediaSmall = "small"
|
Small MediaSize = "small"
|
||||||
// MediaOriginal is the key for original/fullsize versions of media and emoji
|
// Original is the key for original/fullsize versions of media and emoji
|
||||||
MediaOriginal = "original"
|
Original MediaSize = "original"
|
||||||
// MediaStatic is the key for static (non-animated) versions of emoji
|
// Static is the key for static (non-animated) versions of emoji
|
||||||
MediaStatic = "static"
|
Static MediaSize = "static"
|
||||||
// MediaAttachment is the key for media attachments
|
|
||||||
MediaAttachment = "attachment"
|
// Attachment is the key for media attachments
|
||||||
// MediaHeader is the key for profile header requests
|
Attachment MediaType = "attachment"
|
||||||
MediaHeader = "header"
|
// Header is the key for profile header requests
|
||||||
// MediaAvatar is the key for profile avatar requests
|
Header MediaType = "header"
|
||||||
MediaAvatar = "avatar"
|
// Avatar is the key for profile avatar requests
|
||||||
// MediaEmoji is the key for emoji type requests
|
Avatar MediaType = "avatar"
|
||||||
MediaEmoji = "emoji"
|
// Emoji is the key for emoji type requests
|
||||||
|
Emoji MediaType = "emoji"
|
||||||
|
|
||||||
// EmojiMaxBytes is the maximum permitted bytes of an emoji upload (50kb)
|
// EmojiMaxBytes is the maximum permitted bytes of an emoji upload (50kb)
|
||||||
EmojiMaxBytes = 51200
|
EmojiMaxBytes = 51200
|
||||||
|
|
@ -57,7 +64,7 @@ type Handler interface {
|
||||||
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
||||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
||||||
// and then returns information to the caller about the new header.
|
// and then returns information to the caller about the new header.
|
||||||
ProcessHeaderOrAvatar(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error)
|
ProcessHeaderOrAvatar(img []byte, accountID string, mediaType MediaType) (*gtsmodel.MediaAttachment, error)
|
||||||
|
|
||||||
// ProcessLocalAttachment takes a new attachment and the requesting account, checks it out, removes exif data from it,
|
// ProcessLocalAttachment takes a new attachment and the requesting account, checks it out, removes exif data from it,
|
||||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media,
|
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media,
|
||||||
|
|
@ -94,10 +101,10 @@ func New(config *config.Config, database db.DB, storage storage.Storage, log *lo
|
||||||
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
||||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
||||||
// and then returns information to the caller about the new header.
|
// and then returns information to the caller about the new header.
|
||||||
func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
|
func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID string, mediaType MediaType) (*gtsmodel.MediaAttachment, error) {
|
||||||
l := mh.log.WithField("func", "SetHeaderForAccountID")
|
l := mh.log.WithField("func", "SetHeaderForAccountID")
|
||||||
|
|
||||||
if headerOrAvi != MediaHeader && headerOrAvi != MediaAvatar {
|
if mediaType != Header && mediaType != Avatar {
|
||||||
return nil, errors.New("header or avatar not selected")
|
return nil, errors.New("header or avatar not selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,14 +123,14 @@ func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID strin
|
||||||
l.Tracef("read %d bytes of file", len(attachment))
|
l.Tracef("read %d bytes of file", len(attachment))
|
||||||
|
|
||||||
// process it
|
// process it
|
||||||
ma, err := mh.processHeaderOrAvi(attachment, contentType, headerOrAvi, accountID)
|
ma, err := mh.processHeaderOrAvi(attachment, contentType, mediaType, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error processing %s: %s", headerOrAvi, err)
|
return nil, fmt.Errorf("error processing %s: %s", mediaType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set it in the database
|
// set it in the database
|
||||||
if err := mh.db.SetHeaderOrAvatarForAccountID(ma, accountID); err != nil {
|
if err := mh.db.SetHeaderOrAvatarForAccountID(ma, accountID); err != nil {
|
||||||
return nil, fmt.Errorf("error putting %s in database: %s", headerOrAvi, err)
|
return nil, fmt.Errorf("error putting %s in database: %s", mediaType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ma, nil
|
return ma, nil
|
||||||
|
|
@ -234,15 +241,15 @@ func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) (
|
||||||
|
|
||||||
// webfinger uri for the emoji -- unrelated to actually serving the image
|
// webfinger uri for the emoji -- unrelated to actually serving the image
|
||||||
// will be something like https://example.org/emoji/70a7f3d7-7e35-4098-8ce3-9b5e8203bb9c
|
// will be something like https://example.org/emoji/70a7f3d7-7e35-4098-8ce3-9b5e8203bb9c
|
||||||
emojiURI := fmt.Sprintf("%s://%s/%s/%s", mh.config.Protocol, mh.config.Host, MediaEmoji, newEmojiID)
|
emojiURI := fmt.Sprintf("%s://%s/%s/%s", mh.config.Protocol, mh.config.Host, Emoji, newEmojiID)
|
||||||
|
|
||||||
// serve url and storage path for the original emoji -- can be png or gif
|
// serve url and storage path for the original emoji -- can be png or gif
|
||||||
emojiURL := fmt.Sprintf("%s/%s/%s/%s/%s.%s", URLbase, instanceAccount.ID, MediaEmoji, MediaOriginal, newEmojiID, extension)
|
emojiURL := fmt.Sprintf("%s/%s/%s/%s/%s.%s", URLbase, instanceAccount.ID, Emoji, Original, newEmojiID, extension)
|
||||||
emojiPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, instanceAccount.ID, MediaEmoji, MediaOriginal, newEmojiID, extension)
|
emojiPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, instanceAccount.ID, Emoji, Original, newEmojiID, extension)
|
||||||
|
|
||||||
// serve url and storage path for the static version -- will always be png
|
// serve url and storage path for the static version -- will always be png
|
||||||
emojiStaticURL := fmt.Sprintf("%s/%s/%s/%s/%s.png", URLbase, instanceAccount.ID, MediaEmoji, MediaStatic, newEmojiID)
|
emojiStaticURL := fmt.Sprintf("%s/%s/%s/%s/%s.png", URLbase, instanceAccount.ID, Emoji, Static, newEmojiID)
|
||||||
emojiStaticPath := fmt.Sprintf("%s/%s/%s/%s/%s.png", mh.config.StorageConfig.BasePath, instanceAccount.ID, MediaEmoji, MediaStatic, newEmojiID)
|
emojiStaticPath := fmt.Sprintf("%s/%s/%s/%s/%s.png", mh.config.StorageConfig.BasePath, instanceAccount.ID, Emoji, Static, newEmojiID)
|
||||||
|
|
||||||
// store the original
|
// store the original
|
||||||
if err := mh.storage.StoreFileAt(emojiPath, original.image); err != nil {
|
if err := mh.storage.StoreFileAt(emojiPath, original.image); err != nil {
|
||||||
|
|
@ -256,25 +263,26 @@ func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) (
|
||||||
|
|
||||||
// and finally return the new emoji data to the caller -- it's up to them what to do with it
|
// and finally return the new emoji data to the caller -- it's up to them what to do with it
|
||||||
e := >smodel.Emoji{
|
e := >smodel.Emoji{
|
||||||
ID: newEmojiID,
|
ID: newEmojiID,
|
||||||
Shortcode: shortcode,
|
Shortcode: shortcode,
|
||||||
Domain: "", // empty because this is a local emoji
|
Domain: "", // empty because this is a local emoji
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
ImageRemoteURL: "", // empty because this is a local emoji
|
ImageRemoteURL: "", // empty because this is a local emoji
|
||||||
ImageStaticRemoteURL: "", // empty because this is a local emoji
|
ImageStaticRemoteURL: "", // empty because this is a local emoji
|
||||||
ImageURL: emojiURL,
|
ImageURL: emojiURL,
|
||||||
ImageStaticURL: emojiStaticURL,
|
ImageStaticURL: emojiStaticURL,
|
||||||
ImagePath: emojiPath,
|
ImagePath: emojiPath,
|
||||||
ImageStaticPath: emojiStaticPath,
|
ImageStaticPath: emojiStaticPath,
|
||||||
ImageContentType: contentType,
|
ImageContentType: contentType,
|
||||||
ImageFileSize: len(original.image),
|
ImageStaticContentType: "image/png", // static version will always be a png
|
||||||
ImageStaticFileSize: len(static.image),
|
ImageFileSize: len(original.image),
|
||||||
ImageUpdatedAt: time.Now(),
|
ImageStaticFileSize: len(static.image),
|
||||||
Disabled: false,
|
ImageUpdatedAt: time.Now(),
|
||||||
URI: emojiURI,
|
Disabled: false,
|
||||||
VisibleInPicker: true,
|
URI: emojiURI,
|
||||||
CategoryID: "", // empty because this is a new emoji -- no category yet
|
VisibleInPicker: true,
|
||||||
|
CategoryID: "", // empty because this is a new emoji -- no category yet
|
||||||
}
|
}
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
@ -326,13 +334,13 @@ func (mh *mediaHandler) processImageAttachment(data []byte, accountID string, co
|
||||||
smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.jpeg", URLbase, accountID, newMediaID) // all thumbnails/smalls are encoded as jpeg
|
smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.jpeg", URLbase, accountID, newMediaID) // all thumbnails/smalls are encoded as jpeg
|
||||||
|
|
||||||
// we store the original...
|
// we store the original...
|
||||||
originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, MediaAttachment, MediaOriginal, newMediaID, extension)
|
originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, Attachment, Original, newMediaID, extension)
|
||||||
if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
|
if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
|
||||||
return nil, fmt.Errorf("storage error: %s", err)
|
return nil, fmt.Errorf("storage error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// and a thumbnail...
|
// and a thumbnail...
|
||||||
smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.jpeg", mh.config.StorageConfig.BasePath, accountID, MediaAttachment, MediaSmall, newMediaID) // all thumbnails/smalls are encoded as jpeg
|
smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.jpeg", mh.config.StorageConfig.BasePath, accountID, Attachment, Small, newMediaID) // all thumbnails/smalls are encoded as jpeg
|
||||||
if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
|
if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
|
||||||
return nil, fmt.Errorf("storage error: %s", err)
|
return nil, fmt.Errorf("storage error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
@ -386,14 +394,14 @@ func (mh *mediaHandler) processImageAttachment(data []byte, accountID string, co
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, headerOrAvi string, accountID string) (*gtsmodel.MediaAttachment, error) {
|
func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType MediaType, accountID string) (*gtsmodel.MediaAttachment, error) {
|
||||||
var isHeader bool
|
var isHeader bool
|
||||||
var isAvatar bool
|
var isAvatar bool
|
||||||
|
|
||||||
switch headerOrAvi {
|
switch mediaType {
|
||||||
case MediaHeader:
|
case Header:
|
||||||
isHeader = true
|
isHeader = true
|
||||||
case MediaAvatar:
|
case Avatar:
|
||||||
isAvatar = true
|
isAvatar = true
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("header or avatar not selected")
|
return nil, errors.New("header or avatar not selected")
|
||||||
|
|
@ -432,17 +440,17 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
|
||||||
newMediaID := uuid.NewString()
|
newMediaID := uuid.NewString()
|
||||||
|
|
||||||
URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
|
URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
|
||||||
originalURL := fmt.Sprintf("%s/%s/%s/original/%s.%s", URLbase, accountID, headerOrAvi, newMediaID, extension)
|
originalURL := fmt.Sprintf("%s/%s/%s/original/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
|
||||||
smallURL := fmt.Sprintf("%s/%s/%s/small/%s.%s", URLbase, accountID, headerOrAvi, newMediaID, extension)
|
smallURL := fmt.Sprintf("%s/%s/%s/small/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
|
||||||
|
|
||||||
// we store the original...
|
// we store the original...
|
||||||
originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, headerOrAvi, MediaOriginal, newMediaID, extension)
|
originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Original, newMediaID, extension)
|
||||||
if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
|
if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
|
||||||
return nil, fmt.Errorf("storage error: %s", err)
|
return nil, fmt.Errorf("storage error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// and a thumbnail...
|
// and a thumbnail...
|
||||||
smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, headerOrAvi, MediaSmall, newMediaID, extension)
|
smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Small, newMediaID, extension)
|
||||||
if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
|
if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
|
||||||
return nil, fmt.Errorf("storage error: %s", err)
|
return nil, fmt.Errorf("storage error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,31 @@ type imageAndMeta struct {
|
||||||
aspect float64
|
aspect float64
|
||||||
blurhash string
|
blurhash string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseMediaType converts s to a recognized MediaType, or returns an error if unrecognized
|
||||||
|
func ParseMediaType(s string) (MediaType, error) {
|
||||||
|
switch MediaType(s) {
|
||||||
|
case Attachment:
|
||||||
|
return Attachment, nil
|
||||||
|
case Header:
|
||||||
|
return Header, nil
|
||||||
|
case Avatar:
|
||||||
|
return Avatar, nil
|
||||||
|
case Emoji:
|
||||||
|
return Emoji, 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) (MediaSize, error) {
|
||||||
|
switch MediaSize(s) {
|
||||||
|
case Small:
|
||||||
|
return Small, nil
|
||||||
|
case Original:
|
||||||
|
return Original, nil
|
||||||
|
case Static:
|
||||||
|
return Static, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("%s not a recognized MediaSize", s)
|
||||||
|
}
|
||||||
|
|
|
||||||
106
internal/message/error.go
Normal file
106
internal/message/error.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorWithCode wraps an internal error with an http code, and a 'safe' version of
|
||||||
|
// the error that can be served to clients without revealing internal business logic.
|
||||||
|
//
|
||||||
|
// A typical use of this error would be to first log the Original error, then return
|
||||||
|
// the Safe error and the StatusCode to an API caller.
|
||||||
|
type ErrorWithCode interface {
|
||||||
|
// Error returns the original internal error for debugging within the GoToSocial logs.
|
||||||
|
// This should *NEVER* be returned to a client as it may contain sensitive information.
|
||||||
|
Error() string
|
||||||
|
// Safe returns the API-safe version of the error for serialization towards a client.
|
||||||
|
// There's not much point logging this internally because it won't contain much helpful information.
|
||||||
|
Safe() string
|
||||||
|
// Code returns the status code for serving to a client.
|
||||||
|
Code() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorWithCode struct {
|
||||||
|
original error
|
||||||
|
safe error
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorWithCode) Error() string {
|
||||||
|
return e.original.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorWithCode) Safe() string {
|
||||||
|
return e.safe.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errorWithCode) Code() int {
|
||||||
|
return e.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text.
|
||||||
|
func NewErrorBadRequest(original error, helpText ...string) ErrorWithCode {
|
||||||
|
safe := "bad request"
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return errorWithCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorNotAuthorized returns an ErrorWithCode 401 with the given original error and optional help text.
|
||||||
|
func NewErrorNotAuthorized(original error, helpText ...string) ErrorWithCode {
|
||||||
|
safe := "not authorized"
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return errorWithCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusUnauthorized,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text.
|
||||||
|
func NewErrorForbidden(original error, helpText ...string) ErrorWithCode {
|
||||||
|
safe := "forbidden"
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return errorWithCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusForbidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text.
|
||||||
|
func NewErrorNotFound(original error, helpText ...string) ErrorWithCode {
|
||||||
|
safe := "404 not found"
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return errorWithCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text.
|
||||||
|
func NewErrorInternalError(original error, helpText ...string) ErrorWithCode {
|
||||||
|
safe := "internal server error"
|
||||||
|
if helpText != nil {
|
||||||
|
safe = safe + ": " + strings.Join(helpText, ": ")
|
||||||
|
}
|
||||||
|
return errorWithCode{
|
||||||
|
original: original,
|
||||||
|
safe: errors.New(safe),
|
||||||
|
code: http.StatusInternalServerError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -93,3 +95,92 @@ func (p *processor) MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentReq
|
||||||
|
|
||||||
return &mastoAttachment, nil
|
return &mastoAttachment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *processor) MediaGet(authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) {
|
||||||
|
// parse the form fields
|
||||||
|
mediaSize, err := media.ParseMediaSize(form.MediaSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("media size %s not valid", form.MediaSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaType, err := media.ParseMediaType(form.MediaType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("media type %s not valid", form.MediaType))
|
||||||
|
}
|
||||||
|
|
||||||
|
spl := strings.Split(form.FileName, ".")
|
||||||
|
if len(spl) != 2 || spl[0] == "" || spl[1] == "" {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("file name %s not parseable", form.FileName))
|
||||||
|
}
|
||||||
|
wantedMediaID := spl[0]
|
||||||
|
|
||||||
|
// get the account that owns the media and make sure it's not suspended
|
||||||
|
acct := >smodel.Account{}
|
||||||
|
if err := p.db.GetByID(form.AccountID, acct); err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("account with id %s could not be selected from the db: %s", form.AccountID, err))
|
||||||
|
}
|
||||||
|
if !acct.SuspendedAt.IsZero() {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("account with id %s is suspended", form.AccountID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the requesting account and the media account don't block each other
|
||||||
|
if authed.Account != nil {
|
||||||
|
blocked, err := p.db.Blocked(authed.Account.ID, form.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, authed.Account.ID, err))
|
||||||
|
}
|
||||||
|
if blocked {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s: %s", form.AccountID, authed.Account.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content := &apimodel.Content{}
|
||||||
|
var storagePath string
|
||||||
|
switch mediaType {
|
||||||
|
case media.Emoji:
|
||||||
|
e := >smodel.Emoji{}
|
||||||
|
if err := p.db.GetByID(wantedMediaID, e); err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("emoji %s could not be taken from the db: %s", wantedMediaID, err))
|
||||||
|
}
|
||||||
|
if e.Disabled {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("emoji %s has been disabled", wantedMediaID))
|
||||||
|
}
|
||||||
|
switch mediaSize {
|
||||||
|
case media.Original:
|
||||||
|
content.ContentType = e.ImageContentType
|
||||||
|
storagePath = e.ImagePath
|
||||||
|
case media.Static:
|
||||||
|
content.ContentType = e.ImageStaticContentType
|
||||||
|
storagePath = e.ImageStaticPath
|
||||||
|
default:
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize))
|
||||||
|
}
|
||||||
|
case media.Attachment:
|
||||||
|
a := >smodel.MediaAttachment{}
|
||||||
|
if err := p.db.GetByID(wantedMediaID, a); err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err))
|
||||||
|
}
|
||||||
|
if a.AccountID != form.AccountID {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("attachment %s is not owned by %s", wantedMediaID, form.AccountID))
|
||||||
|
}
|
||||||
|
switch mediaSize {
|
||||||
|
case media.Original:
|
||||||
|
content.ContentType = a.File.ContentType
|
||||||
|
storagePath = a.File.Path
|
||||||
|
case media.Small:
|
||||||
|
content.ContentType = a.Thumbnail.ContentType
|
||||||
|
storagePath = a.Thumbnail.Path
|
||||||
|
default:
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := p.storage.RetrieveFileFrom(storagePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewErrorNotFound(fmt.Errorf("error retrieving from storage: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
content.ContentLength = int64(len(bytes))
|
||||||
|
content.Content = bytes
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ type Processor interface {
|
||||||
|
|
||||||
// MediaCreate handles the creation of a media attachment, using the given form.
|
// MediaCreate handles the creation of a media attachment, using the given form.
|
||||||
MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error)
|
MediaCreate(authed *oauth.Auth, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error)
|
||||||
|
MediaGet(authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, error)
|
||||||
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
||||||
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
||||||
|
|
||||||
|
|
@ -93,11 +94,12 @@ type processor struct {
|
||||||
tc typeutils.TypeConverter
|
tc typeutils.TypeConverter
|
||||||
oauthServer oauth.Server
|
oauthServer oauth.Server
|
||||||
mediaHandler media.Handler
|
mediaHandler media.Handler
|
||||||
|
storage storage.Storage
|
||||||
db db.DB
|
db db.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProcessor returns a new Processor that uses the given federator and logger
|
// NewProcessor returns a new Processor that uses the given federator and logger
|
||||||
func NewProcessor(config *config.Config, tc typeutils.TypeConverter, oauthServer oauth.Server, mediaHandler media.Handler, db db.DB, log *logrus.Logger) Processor {
|
func NewProcessor(config *config.Config, tc typeutils.TypeConverter, oauthServer oauth.Server, mediaHandler media.Handler, storage storage.Storage, db db.DB, log *logrus.Logger) Processor {
|
||||||
return &processor{
|
return &processor{
|
||||||
toClientAPI: make(chan ToClientAPI, 100),
|
toClientAPI: make(chan ToClientAPI, 100),
|
||||||
toFederator: make(chan ToFederator, 100),
|
toFederator: make(chan ToFederator, 100),
|
||||||
|
|
@ -107,6 +109,7 @@ func NewProcessor(config *config.Config, tc typeutils.TypeConverter, oauthServer
|
||||||
tc: tc,
|
tc: tc,
|
||||||
oauthServer: oauthServer,
|
oauthServer: oauthServer,
|
||||||
mediaHandler: mediaHandler,
|
mediaHandler: mediaHandler,
|
||||||
|
storage: storage,
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ func (p *processor) updateAccountAvatar(avatar *multipart.FileHeader, accountID
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the setting
|
// do the setting
|
||||||
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.MediaAvatar)
|
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error processing avatar: %s", err)
|
return nil, fmt.Errorf("error processing avatar: %s", err)
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +296,7 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
|
||||||
}
|
}
|
||||||
|
|
||||||
// do the setting
|
// do the setting
|
||||||
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.MediaHeader)
|
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error processing header: %s", err)
|
return nil, fmt.Errorf("error processing header: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,5 @@ import (
|
||||||
|
|
||||||
// NewTestProcessor returns a Processor suitable for testing purposes
|
// NewTestProcessor returns a Processor suitable for testing purposes
|
||||||
func NewTestProcessor(db db.DB, storage storage.Storage) message.Processor {
|
func NewTestProcessor(db db.DB, storage storage.Storage) message.Processor {
|
||||||
return message.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), NewTestOauthServer(db), NewTestMediaHandler(db, storage), db, NewTestLog())
|
return message.NewProcessor(NewTestConfig(), NewTestTypeConverter(db), NewTestOauthServer(db), NewTestMediaHandler(db, storage), storage, db, NewTestLog())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue