diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go index d4740dd96..35b754d73 100644 --- a/internal/db/bundb/timeline.go +++ b/internal/db/bundb/timeline.go @@ -20,9 +20,11 @@ import ( "context" + "time" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/uptrace/bun" "golang.org/x/exp/slices" @@ -58,11 +60,18 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI // Sort by highest ID (newest) to lowest ID (oldest) Order("status.id DESC") - if maxID != "" { - // return only statuses LOWER (ie., older) than maxID - q = q.Where("? < ?", bun.Ident("status.id"), maxID) + if maxID == "" { + var err error + // don't return statuses more than five minutes in the future + maxID, err = id.NewULIDFromTime(time.Now().Add(5 * time.Minute)) + if err != nil { + return nil, err + } } + // return only statuses LOWER (ie., older) than maxID + q = q.Where("? < ?", bun.Ident("status.id"), maxID) + if sinceID != "" { // return only statuses HIGHER (ie., newer) than sinceID q = q.Where("? > ?", bun.Ident("status.id"), sinceID) @@ -134,10 +143,18 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, accountID string, ma WhereGroup(" AND ", whereEmptyOrNull("status.boost_of_id")). Order("status.id DESC") - if maxID != "" { - q = q.Where("? < ?", bun.Ident("status.id"), maxID) + if maxID == "" { + var err error + // don't return statuses more than five minutes in the future + maxID, err = id.NewULIDFromTime(time.Now().Add(5 * time.Minute)) + if err != nil { + return nil, err + } } + // return only statuses LOWER (ie., older) than maxID + q = q.Where("? < ?", bun.Ident("status.id"), maxID) + if sinceID != "" { q = q.Where("? > ?", bun.Ident("status.id"), sinceID) } diff --git a/internal/db/bundb/timeline_test.go b/internal/db/bundb/timeline_test.go index c14d72056..8822879dd 100644 --- a/internal/db/bundb/timeline_test.go +++ b/internal/db/bundb/timeline_test.go @@ -21,8 +21,13 @@ import ( "context" "testing" + "time" "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/testrig" ) type TimelineTestSuite struct { @@ -38,6 +43,20 @@ func (suite *TimelineTestSuite) TestGetPublicTimeline() { suite.Len(s, 6) } +func (suite *TimelineTestSuite) TestGetPublicTimelineWithFutureStatus() { + viewingAccount := suite.testAccounts["local_account_1"] + + futureStatus := getFutureStatus() + if err := suite.db.Put(context.Background(), futureStatus); err != nil { + suite.FailNow(err.Error()) + } + + s, err := suite.db.GetPublicTimeline(context.Background(), viewingAccount.ID, "", "", "", 20, false) + suite.NoError(err) + + suite.Len(s, 6) +} + func (suite *TimelineTestSuite) TestGetHomeTimeline() { viewingAccount := suite.testAccounts["local_account_1"] @@ -47,6 +66,58 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() { suite.Len(s, 16) } +func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() { + viewingAccount := suite.testAccounts["local_account_1"] + + futureStatus := getFutureStatus() + if err := suite.db.Put(context.Background(), futureStatus); err != nil { + suite.FailNow(err.Error()) + } + + s, err := suite.db.GetHomeTimeline(context.Background(), viewingAccount.ID, "", "", "", 20, false) + suite.NoError(err) + + suite.Len(s, 16) +} + +func getFutureStatus() *gtsmodel.Status { + theDistantFuture := time.Now().Add(876600 * time.Hour) + id, err := id.NewULIDFromTime(theDistantFuture) + if err != nil { + panic(err) + } + + return >smodel.Status{ + ID: id, + URI: "http://localhost:8080/users/admin/statuses/" + id, + URL: "http://localhost:8080/@admin/statuses/" + id, + Content: "it's the future, wooooooooooooooooooooooooooooooooo", + Text: "it's the future, wooooooooooooooooooooooooooooooooo", + AttachmentIDs: []string{}, + TagIDs: []string{}, + MentionIDs: []string{}, + EmojiIDs: []string{}, + CreatedAt: theDistantFuture, + UpdatedAt: theDistantFuture, + Local: testrig.TrueBool(), + AccountURI: "http://localhost:8080/users/admin", + AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", + InReplyToID: "", + BoostOfID: "", + ContentWarning: "", + Visibility: gtsmodel.VisibilityPublic, + Sensitive: testrig.FalseBool(), + Language: "en", + CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F", + Pinned: testrig.FalseBool(), + Federated: testrig.TrueBool(), + Boostable: testrig.TrueBool(), + Replyable: testrig.TrueBool(), + Likeable: testrig.TrueBool(), + ActivityStreamsType: ap.ObjectNote, + } +} + func TestTimelineTestSuite(t *testing.T) { suite.Run(t, new(TimelineTestSuite)) }