diff --git a/internal/apimodule/status/statuscreate.go b/internal/apimodule/status/statuscreate.go index b814affaf..17360b125 100644 --- a/internal/apimodule/status/statuscreate.go +++ b/internal/apimodule/status/statuscreate.go @@ -149,7 +149,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { return } } - newStatus.Mentions = menchies + newStatus.GTSMentions = menchies // convert tags to *gtsmodel.Tag tags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), authed.Account.ID, thisStatusID) @@ -158,7 +158,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating hashtags from status"}) return } - newStatus.Tags = tags + newStatus.GTSTags = tags // convert emojis to *gtsmodel.Emoji emojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), authed.Account.ID, thisStatusID) @@ -167,7 +167,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating emojis from status"}) return } - newStatus.Emojis = emojis + newStatus.GTSEmojis = emojis /* FROM THIS POINT ONWARDS WE ARE HAPPY WITH THE STATUS -- it is valid and we will try to create it @@ -180,7 +180,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { } // change the status ID of the media attachments to the new status - for _, a := range newStatus.Attachments { + for _, a := range newStatus.GTSMediaAttachments { a.StatusID = newStatus.ID a.UpdatedAt = time.Now() if err := m.db.UpdateByID(a.ID, a); err != nil { @@ -207,7 +207,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { } mastoAttachments := []mastotypes.Attachment{} - for _, a := range newStatus.Attachments { + for _, a := range newStatus.GTSMediaAttachments { ma, err := m.mastoConverter.AttachmentToMasto(a) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -217,7 +217,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { } mastoMentions := []mastotypes.Mention{} - for _, gtsm := range newStatus.Mentions { + for _, gtsm := range newStatus.GTSMentions { mm, err := m.mastoConverter.MentionToMasto(gtsm) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -433,7 +433,8 @@ func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccount return nil } - attachments := []*gtsmodel.MediaAttachment{} + GTSMediaAttachments := []*gtsmodel.MediaAttachment{} + Attachments := []string{} for _, mediaID := range form.MediaIDs { // check these attachments exist a := >smodel.MediaAttachment{} @@ -448,9 +449,11 @@ func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccount if a.StatusID != "" || a.ScheduledStatusID != "" { return fmt.Errorf("media with id %s is already attached to a status", mediaID) } - attachments = append(attachments, a) + GTSMediaAttachments = append(GTSMediaAttachments, a) + Attachments = append(Attachments, a.ID) } - status.Attachments = attachments + status.GTSMediaAttachments = GTSMediaAttachments + status.Attachments = Attachments return nil } diff --git a/internal/apimodule/status/statuscreate_test.go b/internal/apimodule/status/statuscreate_test.go index 999481f66..c87ba9e36 100644 --- a/internal/apimodule/status/statuscreate_test.go +++ b/internal/apimodule/status/statuscreate_test.go @@ -116,7 +116,8 @@ func (suite *StatusCreateTestSuite) TearDownTest() { TESTING: StatusCreatePOSTHandler */ -func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() { +// Post a new status with some custom visibility settings +func (suite *StatusCreateTestSuite) TestPostNewStatus() { t := suite.testTokens["local_account_1"] oauthToken := oauth.PGTokenToOauthToken(t) @@ -160,7 +161,8 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() { assert.Equal(suite.T(), mastomodel.VisibilityPrivate, statusReply.Visibility) } -func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() { +// Try to reply to a status that doesn't exist +func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { t := suite.testTokens["local_account_1"] oauthToken := oauth.PGTokenToOauthToken(t) @@ -190,7 +192,8 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() { assert.Equal(suite.T(), `{"error":"status with id 3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50 not replyable because it doesn't exist"}`, string(b)) } -func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToLocalSuccess() { +// Post a reply to the status of a local user that allows replies. +func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { t := suite.testTokens["local_account_1"] oauthToken := oauth.PGTokenToOauthToken(t) @@ -229,6 +232,63 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToLocalSucce assert.Len(suite.T(), statusReply.Mentions, 1) } +// Take a media file which is currently not associated with a status, and attach it to a new status. +func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { + t := suite.testTokens["local_account_1"] + oauthToken := oauth.PGTokenToOauthToken(t) + + // setup + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) + ctx.Set(oauth.SessionAuthorizedToken, oauthToken) + ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) + ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) + ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting + ctx.Request.Form = url.Values{ + "status": {"here's an image attachment"}, + "media_ids": {"7a3b9f77-ab30-461e-bdd8-e64bd1db3008"}, + } + suite.statusModule.statusCreatePOSTHandler(ctx) + + // check response + suite.EqualValues(http.StatusOK, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + b, err := ioutil.ReadAll(result.Body) + assert.NoError(suite.T(), err) + + fmt.Println(string(b)) + + statusReply := &mastomodel.Status{} + err = json.Unmarshal(b, statusReply) + assert.NoError(suite.T(), err) + + assert.Equal(suite.T(), "", statusReply.SpoilerText) + assert.Equal(suite.T(), "here's an image attachment", statusReply.Content) + assert.False(suite.T(), statusReply.Sensitive) + assert.Equal(suite.T(), mastomodel.VisibilityPublic, statusReply.Visibility) + + // there should be one media attachment + assert.Len(suite.T(), statusReply.MediaAttachments, 1) + + // get the updated media attachment from the database + gtsAttachment := >smodel.MediaAttachment{} + err = suite.db.GetByID(statusReply.MediaAttachments[0].ID, gtsAttachment) + assert.NoError(suite.T(), err) + + // convert it to a masto attachment + gtsAttachmentAsMasto, err := suite.mastoConverter.AttachmentToMasto(gtsAttachment) + assert.NoError(suite.T(), err) + + // compare it with what we have now + assert.EqualValues(suite.T(), statusReply.MediaAttachments[0], gtsAttachmentAsMasto) + + // the status id of the attachment should now be set to the id of the status we just created + assert.Equal(suite.T(), statusReply.ID, gtsAttachment.StatusID) +} + func TestStatusCreateTestSuite(t *testing.T) { suite.Run(t, new(StatusCreateTestSuite)) } diff --git a/internal/config/default.go b/internal/config/default.go index b2de6a64d..16a7ec46d 100644 --- a/internal/config/default.go +++ b/internal/config/default.go @@ -117,8 +117,8 @@ func GetDefaults() Defaults { AccountsRequireApproval: true, AccountsReasonRequired: true, - MediaMaxImageSize: 1048576, //1mb - MediaMaxVideoSize: 5242880, //5mb + MediaMaxImageSize: 2097152, //2mb + MediaMaxVideoSize: 10485760, //10mb MediaMinDescriptionChars: 0, MediaMaxDescriptionChars: 500, diff --git a/internal/db/gtsmodel/mediaattachment.go b/internal/db/gtsmodel/mediaattachment.go index c0ad4f135..d2b028b18 100644 --- a/internal/db/gtsmodel/mediaattachment.go +++ b/internal/db/gtsmodel/mediaattachment.go @@ -110,7 +110,7 @@ const ( // FileTypeImage is for jpegs and pngs FileTypeImage FileType = "image" // FileTypeGif is for native gifs and soundless videos that have been converted to gifs - FileTypeGif FileType = "gifv" + FileTypeGif FileType = "gif" // FileTypeAudio is for audio-only files (no video) FileTypeAudio FileType = "audio" // FileTypeVideo is for files with audio + visual diff --git a/internal/db/gtsmodel/status.go b/internal/db/gtsmodel/status.go index ea198ae75..43792e9f7 100644 --- a/internal/db/gtsmodel/status.go +++ b/internal/db/gtsmodel/status.go @@ -30,6 +30,8 @@ type Status struct { URL string `pg:",unique"` // the html-formatted content of this status Content string + // Database IDs of any media attachments associated with this status + Attachments []string // when was this status created? CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` // when was this status updated? @@ -62,21 +64,21 @@ type Status struct { NON-DATABASE FIELDS These are for convenience while passing the status around internally, - but these fields should never be put in the db. + but these fields should *never* be put in the db. */ // Mentions created in this status - Mentions []*Mention `pg:"-"` + GTSMentions []*Mention `pg:"-"` // Hashtags used in this status - Tags []*Tag `pg:"-"` + GTSTags []*Tag `pg:"-"` // Emojis used in this status - Emojis []*Emoji `pg:"-"` - // Attachments used in this status - Attachments []*MediaAttachment `pg:"-"` + GTSEmojis []*Emoji `pg:"-"` + // MediaAttachments used in this status + GTSMediaAttachments []*MediaAttachment `pg:"-"` // Status being replied to - ReplyToStatus *Status `pg:"-"` + GTSReplyToStatus *Status `pg:"-"` // Account being replied to - ReplyToAccount *Account `pg:"-"` + GTSReplyToAccount *Account `pg:"-"` } // Visibility represents the visibility granularity of a status. diff --git a/internal/media/media.go b/internal/media/media.go index 2f877d5be..222129aca 100644 --- a/internal/media/media.go +++ b/internal/media/media.go @@ -50,7 +50,7 @@ type MediaHandler interface { // ProcessAttachment 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, // and then returns information to the caller about the attachment. - ProcessAttachment(img []byte, accountID string) (*gtsmodel.MediaAttachment, error) + ProcessAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error) } type mediaHandler struct { @@ -73,15 +73,15 @@ func New(config *config.Config, database db.DB, storage storage.Storage, log *lo INTERFACE FUNCTIONS */ -func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) { +func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(attachment []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) { l := mh.log.WithField("func", "SetHeaderForAccountID") if headerOrAvi != MediaHeader && headerOrAvi != MediaAvatar { return nil, errors.New("header or avatar not selected") } - // make sure we have an image we can handle - contentType, err := parseContentType(img) + // make sure we have a type we can handle + contentType, err := parseContentType(attachment) if err != nil { return nil, err } @@ -89,13 +89,13 @@ func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID stri return nil, fmt.Errorf("%s is not an accepted image type", contentType) } - if len(img) == 0 { + if len(attachment) == 0 { return nil, fmt.Errorf("passed reader was of size 0") } - l.Tracef("read %d bytes of file", len(img)) + l.Tracef("read %d bytes of file", len(attachment)) // process it - ma, err := mh.processHeaderOrAvi(img, contentType, headerOrAvi, accountID) + ma, err := mh.processHeaderOrAvi(attachment, contentType, headerOrAvi, accountID) if err != nil { return nil, fmt.Errorf("error processing %s: %s", headerOrAvi, err) } @@ -108,8 +108,8 @@ func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID stri return ma, nil } -func (mh *mediaHandler) ProcessAttachment(data []byte, accountID string) (*gtsmodel.MediaAttachment, error) { - contentType, err := parseContentType(data) +func (mh *mediaHandler) ProcessAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error) { + contentType, err := parseContentType(attachment) if err != nil { return nil, err } @@ -119,24 +119,24 @@ func (mh *mediaHandler) ProcessAttachment(data []byte, accountID string) (*gtsmo if !supportedVideoType(contentType) { return nil, fmt.Errorf("video type %s not supported", contentType) } - if len(data) == 0 { + if len(attachment) == 0 { return nil, errors.New("video was of size 0") } - if len(data) > mh.config.MediaConfig.MaxVideoSize { - return nil, fmt.Errorf("video size %d bytes exceeded max video size of %d bytes", len(data), mh.config.MediaConfig.MaxVideoSize) + if len(attachment) > mh.config.MediaConfig.MaxVideoSize { + return nil, fmt.Errorf("video size %d bytes exceeded max video size of %d bytes", len(attachment), mh.config.MediaConfig.MaxVideoSize) } - return mh.processVideo(data, accountID, contentType) + return mh.processVideo(attachment, accountID, contentType) case "image": if !supportedImageType(contentType) { return nil, fmt.Errorf("image type %s not supported", contentType) } - if len(data) == 0 { + if len(attachment) == 0 { return nil, errors.New("image was of size 0") } - if len(data) > mh.config.MediaConfig.MaxImageSize { - return nil, fmt.Errorf("image size %d bytes exceeded max image size of %d bytes", len(data), mh.config.MediaConfig.MaxImageSize) + if len(attachment) > mh.config.MediaConfig.MaxImageSize { + return nil, fmt.Errorf("image size %d bytes exceeded max image size of %d bytes", len(attachment), mh.config.MediaConfig.MaxImageSize) } - return mh.processImage(data, accountID, contentType) + return mh.processImage(attachment, accountID, contentType) default: break } @@ -154,28 +154,29 @@ func (mh *mediaHandler) processVideo(data []byte, accountID string, contentType func (mh *mediaHandler) processImage(data []byte, accountID string, contentType string) (*gtsmodel.MediaAttachment, error) { var clean []byte var err error + var original *imageAndMeta + var small *imageAndMeta switch contentType { - case "image/jpeg": + case "image/jpeg", "image/png": if clean, err = purgeExif(data); err != nil { return nil, fmt.Errorf("error cleaning exif data: %s", err) } - case "image/png": - if clean, err = purgeExif(data); err != nil { - return nil, fmt.Errorf("error cleaning exif data: %s", err) + original, err = deriveImage(clean, contentType) + if err != nil { + return nil, fmt.Errorf("error parsing image: %s", err) } case "image/gif": clean = data + original, err = deriveGif(clean, contentType) + if err != nil { + return nil, fmt.Errorf("error parsing gif: %s", err) + } default: return nil, errors.New("media type unrecognized") } - original, err := deriveImage(clean, contentType) - if err != nil { - return nil, fmt.Errorf("error parsing image: %s", err) - } - - small, err := deriveThumbnail(clean, contentType) + small, err = deriveThumbnail(clean, contentType) if err != nil { return nil, fmt.Errorf("error deriving thumbnail: %s", err) } @@ -186,7 +187,7 @@ func (mh *mediaHandler) processImage(data []byte, accountID string, contentType URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath) originalURL := fmt.Sprintf("%s/%s/attachment/original/%s.%s", URLbase, accountID, newMediaID, extension) - smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.%s", URLbase, accountID, newMediaID, extension) + smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.jpeg", URLbase, accountID, newMediaID) // all thumbnails/smalls are encoded as jpeg // we store the original... originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, MediaAttachment, MediaOriginal, newMediaID, extension) @@ -195,7 +196,7 @@ func (mh *mediaHandler) processImage(data []byte, accountID string, contentType } // and a thumbnail... - smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, MediaAttachment, MediaSmall, newMediaID, extension) + smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.jpeg", mh.config.StorageConfig.BasePath, accountID, MediaAttachment, MediaSmall, newMediaID) // all thumbnails/smalls are encoded as jpeg if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil { return nil, fmt.Errorf("storage error: %s", err) } @@ -235,7 +236,7 @@ func (mh *mediaHandler) processImage(data []byte, accountID string, contentType }, Thumbnail: gtsmodel.Thumbnail{ Path: smallPath, - ContentType: contentType, + ContentType: "image/jpeg", // all thumbnails/smalls are encoded as jpeg FileSize: len(small.image), UpdatedAt: time.Now(), URL: smallURL, diff --git a/internal/media/util.go b/internal/media/util.go index 5b5df7f00..518da1db8 100644 --- a/internal/media/util.go +++ b/internal/media/util.go @@ -103,6 +103,45 @@ func purgeExif(b []byte) ([]byte, error) { return clean, nil } +func deriveGif(b []byte, extension string) (*imageAndMeta, error) { + var g *gif.GIF + var err error + switch extension { + case "image/gif": + g, err = gif.DecodeAll(bytes.NewReader(b)) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("extension %s not recognised", extension) + } + + // use the first frame to get the static characteristics + width := g.Config.Width + height := g.Config.Height + size := width * height + aspect := float64(width) / float64(height) + + bh, err := blurhash.Encode(4, 3, g.Image[0]) + if err != nil || bh == "" { + return nil, err + } + + out := &bytes.Buffer{} + if err := gif.EncodeAll(out, g); err != nil { + return nil, err + } + + return &imageAndMeta{ + image: out.Bytes(), + width: width, + height: height, + size: size, + aspect: aspect, + blurhash: bh, + }, nil +} + func deriveImage(b []byte, extension string) (*imageAndMeta, error) { var i image.Image var err error @@ -118,11 +157,6 @@ func deriveImage(b []byte, extension string) (*imageAndMeta, error) { if err != nil { return nil, err } - case "image/gif": - i, err = gif.Decode(bytes.NewReader(b)) - if err != nil { - return nil, err - } default: return nil, fmt.Errorf("extension %s not recognised", extension) } @@ -131,15 +165,17 @@ func deriveImage(b []byte, extension string) (*imageAndMeta, error) { height := i.Bounds().Size().Y size := width * height aspect := float64(width) / float64(height) + bh, err := blurhash.Encode(4, 3, i) if err != nil { - return nil, fmt.Errorf("error generating blurhash: %s", err) + return nil, err } out := &bytes.Buffer{} if err := jpeg.Encode(out, i, nil); err != nil { return nil, err } + return &imageAndMeta{ image: out.Bytes(), width: width, diff --git a/testrig/media/ohyou-original.jpeg b/testrig/media/ohyou-original.jpeg new file mode 100755 index 000000000..349965160 Binary files /dev/null and b/testrig/media/ohyou-original.jpeg differ diff --git a/testrig/media/ohyou-small.jpeg b/testrig/media/ohyou-small.jpeg new file mode 100755 index 000000000..f561884d1 Binary files /dev/null and b/testrig/media/ohyou-small.jpeg differ diff --git a/testrig/media/ohyou.jpeg b/testrig/media/ohyou.jpeg new file mode 100644 index 000000000..48e39e0fb Binary files /dev/null and b/testrig/media/ohyou.jpeg differ diff --git a/testrig/media/trent-original.gif b/testrig/media/trent-original.gif new file mode 100755 index 000000000..2ba145c1a Binary files /dev/null and b/testrig/media/trent-original.gif differ diff --git a/testrig/media/trent-small.jpeg b/testrig/media/trent-small.jpeg new file mode 100755 index 000000000..726c1aed0 Binary files /dev/null and b/testrig/media/trent-small.jpeg differ diff --git a/testrig/media/trent-unprocessed.gif b/testrig/media/trent-unprocessed.gif new file mode 100644 index 000000000..a7dfe34a0 Binary files /dev/null and b/testrig/media/trent-unprocessed.gif differ diff --git a/testrig/storage.go b/testrig/storage.go index a17f6203e..c4ea9a951 100644 --- a/testrig/storage.go +++ b/testrig/storage.go @@ -21,7 +21,6 @@ package testrig import ( "fmt" "os" - "strings" "github.com/superseriousbusiness/gotosocial/internal/storage" ) @@ -39,13 +38,13 @@ func NewTestStorage() storage.Storage { func StandardStorageSetup(s storage.Storage, relativePath string) { stored := NewTestStored() a := NewTestAttachments() - for k, fileNameTemplate := range stored { + for k, paths := range stored { attachmentInfo, ok := a[k] if !ok { panic(fmt.Errorf("key %s not found in test attachments", k)) } - filenameOriginal := strings.Replace(fileNameTemplate, "*", "original", 1) - filenameSmall := strings.Replace(fileNameTemplate, "*", "small", 1) + filenameOriginal := paths.original + filenameSmall := paths.small pathOriginal := attachmentInfo.File.Path pathSmall := attachmentInfo.Thumbnail.Path bOriginal, err := os.ReadFile(fmt.Sprintf("%s/%s", relativePath, filenameOriginal)) diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 8011d3062..12c9f519a 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -511,13 +511,125 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { Avatar: false, Header: false, }, + "local_account_1_status_4_attachment_1": { + ID: "510f6033-798b-4390-81b1-c38ca2205ad3", + StatusID: "18524c05-97dc-46d7-b474-c811bd9e1e32", + URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/original/510f6033-798b-4390-81b1-c38ca2205ad3.gif", + RemoteURL: "", + CreatedAt: time.Now().Add(-1 * time.Hour), + UpdatedAt: time.Now().Add(-1 * time.Hour), + Type: gtsmodel.FileTypeGif, + FileMeta: gtsmodel.FileMeta{ + Original: gtsmodel.Original{ + Width: 400, + Height: 280, + Size: 756000, + Aspect: 1.4285714285714286, + }, + Small: gtsmodel.Small{ + Width: 256, + Height: 179, + Size: 45824, + Aspect: 1.4301675977653632, + }, + Focus: gtsmodel.Focus{ + X: 0, + Y: 0, + }, + }, + AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", + Description: "90's Trent Reznor turning to the camera", + ScheduledStatusID: "", + Blurhash: "LEDara58O=t5EMSOENEN9]}?aK%0", + Processing: 2, + File: gtsmodel.File{ + Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/original/510f6033-798b-4390-81b1-c38ca2205ad3.gif", + ContentType: "image/gif", + FileSize: 1109138, + UpdatedAt: time.Now().Add(-1 * time.Hour), + }, + Thumbnail: gtsmodel.Thumbnail{ + Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/small/510f6033-798b-4390-81b1-c38ca2205ad3.jpeg", + ContentType: "image/jpeg", + FileSize: 8803, + UpdatedAt: time.Now().Add(-1 * time.Hour), + URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/small/510f6033-798b-4390-81b1-c38ca2205ad3.jpeg", + RemoteURL: "", + }, + Avatar: false, + Header: false, + }, + "local_account_1_unattached_1": { + ID: "7a3b9f77-ab30-461e-bdd8-e64bd1db3008", + StatusID: "", // this attachment isn't connected to a status YET + URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/original/7a3b9f77-ab30-461e-bdd8-e64bd1db3008.jpeg", + RemoteURL: "", + CreatedAt: time.Now().Add(30 * time.Second), + UpdatedAt: time.Now().Add(30 * time.Second), + Type: gtsmodel.FileTypeGif, + FileMeta: gtsmodel.FileMeta{ + Original: gtsmodel.Original{ + Width: 800, + Height: 450, + Size: 360000, + Aspect: 1.7777777777777777, + }, + Small: gtsmodel.Small{ + Width: 256, + Height: 144, + Size: 36864, + Aspect: 1.7777777777777777, + }, + Focus: gtsmodel.Focus{ + X: 0, + Y: 0, + }, + }, + AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", + Description: "the oh you meme", + ScheduledStatusID: "", + Blurhash: "LSAd]9ogDge-R:M|j=xWIto0xXWX", + Processing: 2, + File: gtsmodel.File{ + Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/original/7a3b9f77-ab30-461e-bdd8-e64bd1db3008.jpeg", + ContentType: "image/jpeg", + FileSize: 27759, + UpdatedAt: time.Now().Add(30 * time.Second), + }, + Thumbnail: gtsmodel.Thumbnail{ + Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/small/7a3b9f77-ab30-461e-bdd8-e64bd1db3008.jpeg", + ContentType: "image/jpeg", + FileSize: 6177, + UpdatedAt: time.Now().Add(30 * time.Second), + URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/attachment/small/7a3b9f77-ab30-461e-bdd8-e64bd1db3008.jpeg", + RemoteURL: "", + }, + Avatar: false, + Header: false, + }, } } +type paths struct { + original string + small string +} + // NewTestStored returns a map of filenames, keyed according to which attachment they pertain to. -func NewTestStored() map[string]string { - return map[string]string{ - "admin_account_status_1_attachment_1": "welcome-*.jpeg", +func NewTestStored() map[string]paths { + return map[string]paths{ + "admin_account_status_1_attachment_1": { + original: "welcome-original.jpeg", + small: "welcome-small.jpeg", + }, + "local_account_1_status_4_attachment_1": { + original: "trent-original.gif", + small: "trent-small.jpeg", + }, + "local_account_1_unattached_1": { + original: "ohyou-original.jpeg", + small: "ohyou-small.jpeg", + }, } } @@ -530,6 +642,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { URI: "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd", URL: "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd", Content: "hello world! first post on the instance!", + Attachments: []string{"b052241b-f30f-4dc6-92fc-2bad0be1f8d8"}, CreatedAt: time.Now().Add(-71 * time.Hour), UpdatedAt: time.Now().Add(-71 * time.Hour), Local: true, @@ -640,6 +753,30 @@ func NewTestStatuses() map[string]*gtsmodel.Status { }, ActivityStreamsType: gtsmodel.ActivityStreamsNote, }, + "local_account_1_status_4": { + ID: "18524c05-97dc-46d7-b474-c811bd9e1e32", + URI: "http://localhost:8080/users/the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32", + URL: "http://localhost:8080/@the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32", + Content: "here's a little gif of trent", + Attachments: []string{"510f6033-798b-4390-81b1-c38ca2205ad3"}, + CreatedAt: time.Now().Add(-1 * time.Hour), + UpdatedAt: time.Now().Add(-1 * time.Hour), + Local: true, + AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", + InReplyToID: "", + BoostOfID: "", + ContentWarning: "eye contact, trent reznor gif", + Visibility: gtsmodel.VisibilityMutualsOnly, + Sensitive: false, + Language: "en", + VisibilityAdvanced: >smodel.VisibilityAdvanced{ + Federated: true, + Boostable: true, + Replyable: true, + Likeable: true, + }, + ActivityStreamsType: gtsmodel.ActivityStreamsNote, + }, "local_account_2_status_1": { ID: "8945ccf2-3873-45e9-aa13-fd7163f19775", URI: "http://localhost:8080/users/1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",